Merge pull request #6 from nexa-sol/market-ui-and-landing-page-ux
Market UI and landing page ux
This commit is contained in:
Generated
+39
@@ -10,11 +10,14 @@
|
|||||||
"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",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
|
"react-resizable-panels": "^4.6.4",
|
||||||
"tailwind-animations": "^1.0.1",
|
"tailwind-animations": "^1.0.1",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwind-motlib": "^1.0.4"
|
"tailwind-motlib": "^1.0.4"
|
||||||
@@ -4599,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",
|
||||||
@@ -5715,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",
|
||||||
@@ -6753,6 +6782,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-resizable-panels": {
|
||||||
|
"version": "4.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.6.4.tgz",
|
||||||
|
"integrity": "sha512-E7Szs1xyaMZ7xOI2gG4TECNz4r/gmpV1AsXyZRnER6OQnfFf9uclFmrHHZR3h/iF8vQS+nQ1LKyZv9bzwGxPSg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-style-singleton": {
|
"node_modules/react-style-singleton": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||||
|
|||||||
@@ -10,11 +10,14 @@
|
|||||||
"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",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
|
"react-resizable-panels": "^4.6.4",
|
||||||
"tailwind-animations": "^1.0.1",
|
"tailwind-animations": "^1.0.1",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwind-motlib": "^1.0.4"
|
"tailwind-motlib": "^1.0.4"
|
||||||
|
|||||||
+10
-1
@@ -48,6 +48,9 @@
|
|||||||
--radius-2xl: calc(var(--radius) + 8px);
|
--radius-2xl: calc(var(--radius) + 8px);
|
||||||
--radius-3xl: calc(var(--radius) + 12px);
|
--radius-3xl: calc(var(--radius) + 12px);
|
||||||
--radius-4xl: calc(var(--radius) + 16px);
|
--radius-4xl: calc(var(--radius) + 16px);
|
||||||
|
|
||||||
|
--color-bullish: var(--bullish);
|
||||||
|
--color-bearish: var(--bearish);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -57,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);
|
||||||
@@ -106,6 +109,12 @@
|
|||||||
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
--sidebar-ring: oklch(0.6 0.12 285);
|
--sidebar-ring: oklch(0.6 0.12 285);
|
||||||
|
|
||||||
|
/* Bullish - vibrant SOL mint */
|
||||||
|
--bullish: oklch(0.72 0.18 165);
|
||||||
|
|
||||||
|
/* Bearish - destructive/coral red */
|
||||||
|
--bearish: oklch(63.35% 0.2171 4.38);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Navbar from "@/components/layout/Navbar";
|
||||||
|
import Statusbar from "@/components/layout/Statusbar";
|
||||||
|
import MarketSidebar from "./components/market-sidebar/MarketSidebar";
|
||||||
|
import Chart from "./components/market-sidebar/Chart";
|
||||||
|
|
||||||
|
export default function Market() {
|
||||||
|
return (
|
||||||
|
<main className="w-full h-full">
|
||||||
|
<Navbar />
|
||||||
|
<section className="w-full h-full flex flex-row">
|
||||||
|
<div className="w-full h-svh border-r border-border">
|
||||||
|
<Chart />
|
||||||
|
</div>
|
||||||
|
<MarketSidebar />
|
||||||
|
</section>
|
||||||
|
<Statusbar />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
data-slot="input"
|
||||||
|
className={cn(
|
||||||
|
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Input }
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { GripVerticalIcon } from "lucide-react"
|
||||||
|
import * as ResizablePrimitive from "react-resizable-panels"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function ResizablePanelGroup({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: ResizablePrimitive.GroupProps) {
|
||||||
|
return (
|
||||||
|
<ResizablePrimitive.Group
|
||||||
|
data-slot="resizable-panel-group"
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full aria-[orientation=vertical]:flex-col",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
|
||||||
|
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResizableHandle({
|
||||||
|
withHandle,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: ResizablePrimitive.SeparatorProps & {
|
||||||
|
withHandle?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ResizablePrimitive.Separator
|
||||||
|
data-slot="resizable-handle"
|
||||||
|
className={cn(
|
||||||
|
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{withHandle && (
|
||||||
|
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
|
||||||
|
<GripVerticalIcon className="size-2.5" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ResizablePrimitive.Separator>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ResizableHandle, ResizablePanel, ResizablePanelGroup }
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Separator as SeparatorPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Separator({
|
||||||
|
className,
|
||||||
|
orientation = "horizontal",
|
||||||
|
decorative = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
data-slot="separator"
|
||||||
|
decorative={decorative}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Separator }
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
import { Tabs as TabsPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Tabs({
|
||||||
|
className,
|
||||||
|
orientation = "horizontal",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Root
|
||||||
|
data-slot="tabs"
|
||||||
|
data-orientation={orientation}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabsListVariants = cva(
|
||||||
|
"rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-muted",
|
||||||
|
line: "gap-1 bg-transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function TabsList({
|
||||||
|
className,
|
||||||
|
variant = "default",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.List> &
|
||||||
|
VariantProps<typeof tabsListVariants>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
data-slot="tabs-list"
|
||||||
|
data-variant={variant}
|
||||||
|
className={cn(tabsListVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsTrigger({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
data-slot="tabs-trigger"
|
||||||
|
className={cn(
|
||||||
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
||||||
|
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground",
|
||||||
|
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsContent({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
data-slot="tabs-content"
|
||||||
|
className={cn("flex-1 outline-none", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user