Compare commits

..
10 Commits
Author SHA1 Message Date
selimaj-dev 399f7f6f57 Merge pull request #6 from nexa-sol/market-ui-and-landing-page-ux
Market UI and landing page ux
2026-02-18 10:35:20 -08:00
selimaj-dev f3a736087f Market/mint 2026-02-18 19:34:43 +01:00
selimaj-dev fef385f246 Improved card background color 2026-02-18 03:01:54 +01:00
selimaj-dev e8cb05b286 Replaced CTA again 2026-02-18 03:00:30 +01:00
selimaj-dev 5b072cd2ab Improved landing page UI/UX by centering hero 2026-02-18 02:51:35 +01:00
selimaj-dev 52f9e47cec Improved landing page UX by placing primary button on the right 2026-02-18 02:46:56 +01:00
selimaj-dev f59a6cab8f Improved landing page button UI 2026-02-18 02:42:14 +01:00
selimaj-dev c18c63377e Simple market chart 2026-02-18 02:13:49 +01:00
selimaj-dev e801be30ac Token info 2026-02-18 01:47:46 +01:00
selimaj-dev df90677975 Collapsible 2026-02-18 01:14:59 +01:00
12 changed files with 344 additions and 56 deletions
+28
View File
@@ -10,6 +10,8 @@
"dependencies": { "dependencies": {
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lightweight-charts": "^5.1.0",
"lightweight-charts-react-components": "^1.4.0",
"lucide-react": "^0.563.0", "lucide-react": "^0.563.0",
"next": "16.1.6", "next": "16.1.6",
"radix-ui": "^1.4.3", "radix-ui": "^1.4.3",
@@ -4600,6 +4602,12 @@
"express": ">= 4.11" "express": ">= 4.11"
} }
}, },
"node_modules/fancy-canvas": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-2.1.0.tgz",
"integrity": "sha512-nifxXJ95JNLFR2NgRV4/MxVP45G9909wJTEKz5fg/TZS20JJZA6hfgRVh/bC9bwl2zBtBNcYPjiBE4njQHVBwQ==",
"license": "MIT"
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -5716,6 +5724,26 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/lightweight-charts": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-5.1.0.tgz",
"integrity": "sha512-jEAYR4ODYeyNZcWUigsoLTl52rbPmgXnvd5FLIv/ZoA/2sSDw63YKnef8n4yhzum7W926yHeFwlm7ididKb7YQ==",
"license": "Apache-2.0",
"dependencies": {
"fancy-canvas": "2.1.0"
}
},
"node_modules/lightweight-charts-react-components": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/lightweight-charts-react-components/-/lightweight-charts-react-components-1.4.0.tgz",
"integrity": "sha512-FWzEiQDhQaQ04l6kV2fzsODK6jCrzbLOai+r6oEvCqC3tniXjKUAFHbTUlemII7PfiWYwyVAVgl1XRv72Q8Phw==",
"license": "MIT",
"peerDependencies": {
"lightweight-charts": ">=5 < 6",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/lines-and-columns": { "node_modules/lines-and-columns": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+2
View File
@@ -10,6 +10,8 @@
"dependencies": { "dependencies": {
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lightweight-charts": "^5.1.0",
"lightweight-charts-react-components": "^1.4.0",
"lucide-react": "^0.563.0", "lucide-react": "^0.563.0",
"next": "16.1.6", "next": "16.1.6",
"radix-ui": "^1.4.3", "radix-ui": "^1.4.3",
+1 -1
View File
@@ -60,7 +60,7 @@
--background: oklch(0.13 0.01 260); /* deep charcoal */ --background: oklch(0.13 0.01 260); /* deep charcoal */
--foreground: oklch(0.98 0 0); --foreground: oklch(0.98 0 0);
--card: oklch(0.18 0.015 260); --card: oklch(15.349% 0.00726 258.438);
--card-foreground: oklch(0.98 0 0); --card-foreground: oklch(0.98 0 0);
--popover: oklch(0.18 0.015 260); --popover: oklch(0.18 0.015 260);
@@ -0,0 +1,55 @@
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ChevronDown, ChevronUp } from "lucide-react";
import { useState } from "react";
export default function BuySell() {
const [isCollapsed, setIsCollapsed] = useState(false);
return (
<Tabs defaultValue="buy" className="px-3 border-b">
<div className="flex items-center justify-between border-b bg-transparent">
<TabsList className="h-14! p-0 px-2 bg-transparent border-none flex-1">
<TabsTrigger
value="buy"
className="h-9 bg-transparent data-[state=active]:bg-bullish data-[state=active]:text-accent-foreground"
>
Buy
</TabsTrigger>
<TabsTrigger
value="sell"
className="ml-0.5 h-9 bg-transparent data-[state=active]:bg-bearish data-[state=active]:text-accent-foreground"
>
Sell
</TabsTrigger>
</TabsList>
{/* The Toggle Button */}
<Button
variant="outline"
size="xs"
className="h-6 w-6 p-0 rounded-full"
onClick={() => setIsCollapsed(!isCollapsed)}
>
{isCollapsed ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</Button>
</div>
{/* Conditional Rendering or Height Hiding */}
{!isCollapsed && (
<>
<TabsContent value="buy">
<div className="h-72">buy placeholder</div>
</TabsContent>
<TabsContent value="sell">
<div className="h-72">sell placeholder</div>
</TabsContent>
</>
)}
</Tabs>
);
}
@@ -0,0 +1,96 @@
import { CandlestickSeries, Chart } from "lightweight-charts-react-components";
export default function MarketChart() {
return (
<Chart
containerProps={{
style: { width: "100%", height: "100%" },
}}
options={{
layout: {
background: { color: "#131722" }, // Dark background
textColor: "#d1d4dc", // Light text
},
grid: {
vertLines: { color: "#2B2B43" }, // Subtle dark grid lines
horzLines: { color: "#2B2B43" },
},
}}
>
<CandlestickSeries
data={[
{
time: "2018-12-22",
open: 32.5,
high: 33.0,
low: 31.8,
close: 32.51,
},
{
time: "2018-12-23",
open: 32.51,
high: 32.8,
low: 30.5,
close: 31.11,
},
{
time: "2018-12-24",
open: 31.11,
high: 31.5,
low: 26.9,
close: 27.02,
},
{
time: "2018-12-25",
open: 27.02,
high: 27.8,
low: 26.5,
close: 27.32,
},
{
time: "2018-12-26",
open: 27.32,
high: 26.0,
low: 24.8,
close: 25.17,
},
{
time: "2018-12-27",
open: 25.17,
high: 29.0,
low: 25.0,
close: 28.89,
},
{
time: "2018-12-28",
open: 28.89,
high: 26.0,
low: 25.4,
close: 25.46,
},
{
time: "2018-12-29",
open: 25.46,
high: 24.5,
low: 23.5,
close: 23.92,
},
{
time: "2018-12-30",
open: 23.92,
high: 23.0,
low: 22.5,
close: 22.68,
},
{
time: "2018-12-31",
open: 22.68,
high: 23.0,
low: 22.5,
close: 22.67,
},
]}
/>
</Chart>
);
}
@@ -0,0 +1,12 @@
import BuySell from "./BuySell";
import TokenInfo from "./TokenInfo";
export default function MarketSidebar() {
return (
<div className="h-full w-sm flex flex-col gap-4">
<div className="flex h-16 border-b px-3">placeholder</div>
<BuySell />
<TokenInfo />
</div>
);
}
@@ -0,0 +1,126 @@
import { cn } from "@/lib/utils";
import { Token, TokenLiveData } from "@/modules/tokens/types";
import {
BoxesIcon,
ChartCandlestick,
ChefHatIcon,
CrosshairIcon,
FlameIcon,
GhostIcon,
LucideIcon,
ShieldIcon,
UsersIcon,
UserStarIcon,
} from "lucide-react";
export default function TokenInfo() {
const info: Token = {
mint: "",
symbol: "",
name: "",
};
const liveData: TokenLiveData = {
topHolder: 67,
devHold: 20,
snipersHold: 30,
insidersHold: 10,
bundleHold: 3,
};
return (
<div className="px-3">
<div></div>
<div className="flex flex-col gap-4">
<div className="grid grid-cols-3 gap-3 border-b pb-4">
<DataCard
icon={UserStarIcon}
label="Top 10 H"
value={`${liveData.topHolder}%`}
className="text-bearish"
/>
<DataCard
icon={ChefHatIcon}
label="Top 10 H"
value={`${liveData.topHolder}%`}
className="text-bullish"
/>
<DataCard
icon={CrosshairIcon}
label="Snipers H"
value={`${liveData.topHolder}%`}
className="text-bearish"
/>
<DataCard
icon={GhostIcon}
label="Insiders"
value={`${liveData.insidersHold}%`}
className="text-bullish"
/>
<DataCard
icon={BoxesIcon}
label="Bundlers"
value={`${liveData.bundleHold}%`}
className="text-bullish"
/>
<DataCard
icon={FlameIcon}
label="LP Burned"
value={`${100}%`}
className="text-bullish"
/>
</div>
<div className="grid grid-cols-3 gap-3">
<DataCard
icon={UsersIcon}
label="Holders"
value={`56`}
className="text-bearish"
/>
<DataCard
icon={ChartCandlestick}
label="pro traders"
value={`19`}
className="text-bullish"
/>
<DataCard
icon={ShieldIcon}
label="Dex paid"
value={`Paid`}
className="text-bullish"
/>
</div>
<div>CA and DA placeholder</div>
</div>
</div>
);
}
function DataCard({
label,
value,
className,
icon: Icon,
}: {
icon: LucideIcon;
label: string;
value: string;
className: string;
}) {
return (
<div className="rounded-md border flex flex-col gap-1.5 justify-center pt-1 pb-2">
<div
className={cn(
"flex items-center justify-center gap-1 text-xs",
className,
)}
>
<Icon className="h-3 w-3" />
{value}
</div>
<div className="text-center text-xs text-muted-foreground">{label}</div>
</div>
);
}
@@ -2,7 +2,8 @@
import Navbar from "@/components/layout/Navbar"; import Navbar from "@/components/layout/Navbar";
import Statusbar from "@/components/layout/Statusbar"; import Statusbar from "@/components/layout/Statusbar";
import MarketSidebar from "./components/MarketSidebar"; import MarketSidebar from "./components/market-sidebar/MarketSidebar";
import Chart from "./components/market-sidebar/Chart";
export default function Market() { export default function Market() {
return ( return (
@@ -10,7 +11,7 @@ export default function Market() {
<Navbar /> <Navbar />
<section className="w-full h-full flex flex-row"> <section className="w-full h-full flex flex-row">
<div className="w-full h-svh border-r border-border"> <div className="w-full h-svh border-r border-border">
<Chart />
</div> </div>
<MarketSidebar /> <MarketSidebar />
</section> </section>
@@ -1,32 +0,0 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
export default function MarketSidebar() {
return (
<div className="h-full w-sm flex flex-col gap-4">
<div className="flex h-16 border-b px-3">placeholder</div>
<Tabs defaultValue="buy" className="px-3 border-b">
<TabsList className="w-full h-14! p-0 px-2 bg-transparent border">
<TabsTrigger
value="buy"
className="h-9 bg-transparent data-[state=active]:bg-bullish data-[state=active]:text-accent-foreground"
>
Buy
</TabsTrigger>
<TabsTrigger
value="sell"
className="h-9 bg-transparent data-[state=active]:bg-bearish data-[state=active]:text-accent-foreground"
>
Sell
</TabsTrigger>
</TabsList>
<TabsContent value="buy">
<div className="h-72">buy placeholder</div>
</TabsContent>
<TabsContent value="sell" className="border-b">
<div className="h-72">sell placeholder</div>
</TabsContent>
</Tabs>
</div>
);
}
+3 -3
View File
@@ -40,13 +40,13 @@ export default function TokenCard({ token }: { token: Token }) {
" "
onClick={(e) => { onClick={(e) => {
if (e.ctrlKey || e.metaKey) { if (e.ctrlKey || e.metaKey) {
window.open(`/token/${token.mint}`, "_blank", "noopener,noreferrer"); window.open(`/market/${token.mint}`, "_blank", "noopener,noreferrer");
} else { } else {
router.push(`/token/${token.mint}`); router.push(`/market/${token.mint}`);
} }
}} }}
onContextMenu={() => onContextMenu={() =>
window.open(`/token/${token.mint}`, "_blank", "noopener,noreferrer") window.open(`/market/${token.mint}`, "_blank", "noopener,noreferrer")
} }
> >
{/* Left: logo */} {/* Left: logo */}
+17 -17
View File
@@ -22,38 +22,38 @@ export default function Landing() {
</div> </div>
{/* Hero */} {/* Hero */}
<section className="relative z-10 px-6 pt-32 pb-24 max-w-7xl mx-auto"> <section className="pt-32 pb-24 w-full">
<div className="max-w-3xl"> <div className="w-full">
<h1 className="text-5xl md:text-6xl font-semibold leading-tight animate-fade-in-up"> <h1 className="text-center text-5xl md:text-6xl font-semibold leading-tight animate-fade-in-up">
Your copilot for <br /> Your copilot for <br />
<span className="text-transparent bg-clip-text bg-linear-to-r from-primary to-accent"> <span className="text-transparent bg-clip-text bg-linear-to-r from-primary to-accent">
memecoin trading memecoin trading
</span> </span>
</h1> </h1>
<p className="mt-6 text-lg text-muted-foreground max-w-xl animate-fade-in-up animate-delay-100"> <p className="mx-auto text-center mt-6 text-lg text-muted-foreground max-w-xl animate-fade-in-up animate-delay-100">
Catch rugs before they happen, execute trades at Solana Catch rugs before they happen, execute trades at Solana speed, and
speed, and automate the research pro traders do. automate the research pro traders do.
</p> </p>
<div className="mt-10 flex gap-4"> <div className="mt-10 flex gap-4 mx-auto w-max">
<Link <Link
href="/markets"
className="animate-fade-in-up animate-duration-500 animate-delay-200" className="animate-fade-in-up animate-duration-500 animate-delay-200"
>
<button className="px-6 py-3 rounded-lg bg-primary text-primary-foreground font-medium hover:opacity-90 transition">
Open Terminal
</button>
</Link>
<Link
className="animate-fade-in-up animate-duration-500 animate-delay-300"
href="https://github.com/nexa-sol/nexa-trade?tab=readme-ov-file#-nexa-roadmap--alpha-release" href="https://github.com/nexa-sol/nexa-trade?tab=readme-ov-file#-nexa-roadmap--alpha-release"
target="_blank" target="_blank"
> >
<button className="px-6 py-3 rounded-lg border border-border hover:bg-accent/10 transition"> <button className="px-6 py-3 rounded-lg border border-border hover:bg-accent/10 hover:text-accent transition">
View Roadmap View Roadmap
</button> </button>
</Link> </Link>
<Link
href="/markets"
className="animate-fade-in-up animate-duration-500 animate-delay-300"
>
<button className="px-6 py-3 rounded-lg font-medium transition border border-border outline-primary text-primary bg-primary/8 hover:border-primary hover:bg-primary/12">
Open Terminal
</button>
</Link>
</div> </div>
</div> </div>
</section> </section>
@@ -61,7 +61,7 @@ export default function Landing() {
{/* Features */} {/* Features */}
<section className="relative z-10 px-6 py-24 max-w-7xl mx-auto"> <section className="relative z-10 px-6 py-24 max-w-7xl mx-auto">
<div className="grid md:grid-cols-3 gap-8"> <div className="grid md:grid-cols-3 gap-8">
<Staggered stagger={100}> <Staggered stagger={150}>
{[ {[
{ {
icon: Shield, icon: Shield,
+1 -1
View File
@@ -3,7 +3,7 @@ export interface Token {
symbol: string; symbol: string;
name: string; name: string;
logoURI?: string; logoURI?: string;
twitterPost: { twitterPost?: {
link: string; link: string;
author: { author: {
username: string; username: string;