diff --git a/package-lock.json b/package-lock.json index 59921e1..8860fa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,14 @@ "dependencies": { "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "lightweight-charts": "^5.1.0", + "lightweight-charts-react-components": "^1.4.0", "lucide-react": "^0.563.0", "next": "16.1.6", "radix-ui": "^1.4.3", "react": "19.2.3", "react-dom": "19.2.3", + "react-resizable-panels": "^4.6.4", "tailwind-animations": "^1.0.1", "tailwind-merge": "^3.4.0", "tailwind-motlib": "^1.0.4" @@ -4599,6 +4602,12 @@ "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": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5715,6 +5724,26 @@ "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": { "version": "1.2.4", "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": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", diff --git a/package.json b/package.json index 8b3d99b..cbe478e 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,14 @@ "dependencies": { "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "lightweight-charts": "^5.1.0", + "lightweight-charts-react-components": "^1.4.0", "lucide-react": "^0.563.0", "next": "16.1.6", "radix-ui": "^1.4.3", "react": "19.2.3", "react-dom": "19.2.3", + "react-resizable-panels": "^4.6.4", "tailwind-animations": "^1.0.1", "tailwind-merge": "^3.4.0", "tailwind-motlib": "^1.0.4" diff --git a/src/app/globals.css b/src/app/globals.css index 5b22053..16bfb74 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -48,6 +48,9 @@ --radius-2xl: calc(var(--radius) + 8px); --radius-3xl: calc(var(--radius) + 12px); --radius-4xl: calc(var(--radius) + 16px); + + --color-bullish: var(--bullish); + --color-bearish: var(--bearish); } :root { @@ -57,7 +60,7 @@ --background: oklch(0.13 0.01 260); /* deep charcoal */ --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); --popover: oklch(0.18 0.015 260); @@ -106,6 +109,12 @@ --sidebar-border: oklch(1 0 0 / 10%); --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 { diff --git a/src/app/market/[mint]/components/market-sidebar/BuySell.tsx b/src/app/market/[mint]/components/market-sidebar/BuySell.tsx new file mode 100644 index 0000000..d8209aa --- /dev/null +++ b/src/app/market/[mint]/components/market-sidebar/BuySell.tsx @@ -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 ( + +
+ + + Buy + + + Sell + + + + {/* The Toggle Button */} + +
+ + {/* Conditional Rendering or Height Hiding */} + {!isCollapsed && ( + <> + +
buy placeholder
+
+ +
sell placeholder
+
+ + )} +
+ ); +} diff --git a/src/app/market/[mint]/components/market-sidebar/Chart.tsx b/src/app/market/[mint]/components/market-sidebar/Chart.tsx new file mode 100644 index 0000000..5d6bde0 --- /dev/null +++ b/src/app/market/[mint]/components/market-sidebar/Chart.tsx @@ -0,0 +1,96 @@ +import { CandlestickSeries, Chart } from "lightweight-charts-react-components"; + +export default function MarketChart() { + return ( + + + + ); +} diff --git a/src/app/market/[mint]/components/market-sidebar/MarketSidebar.tsx b/src/app/market/[mint]/components/market-sidebar/MarketSidebar.tsx new file mode 100644 index 0000000..3e2f192 --- /dev/null +++ b/src/app/market/[mint]/components/market-sidebar/MarketSidebar.tsx @@ -0,0 +1,12 @@ +import BuySell from "./BuySell"; +import TokenInfo from "./TokenInfo"; + +export default function MarketSidebar() { + return ( +
+
placeholder
+ + +
+ ); +} diff --git a/src/app/market/[mint]/components/market-sidebar/TokenInfo.tsx b/src/app/market/[mint]/components/market-sidebar/TokenInfo.tsx new file mode 100644 index 0000000..b8e1fb9 --- /dev/null +++ b/src/app/market/[mint]/components/market-sidebar/TokenInfo.tsx @@ -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 ( +
+
+
+
+ + + + + + +
+ +
+ + + +
+ +
CA and DA placeholder
+
+
+ ); +} + +function DataCard({ + label, + value, + className, + icon: Icon, +}: { + icon: LucideIcon; + label: string; + value: string; + className: string; +}) { + return ( +
+
+ + {value} +
+
{label}
+
+ ); +} diff --git a/src/app/market/[mint]/page.tsx b/src/app/market/[mint]/page.tsx new file mode 100644 index 0000000..bb40490 --- /dev/null +++ b/src/app/market/[mint]/page.tsx @@ -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 ( +
+ +
+
+ +
+ +
+ +
+ ); +} diff --git a/src/app/markets/components/TokenCard.tsx b/src/app/markets/components/TokenCard.tsx index 51e0fdd..bf0c96a 100644 --- a/src/app/markets/components/TokenCard.tsx +++ b/src/app/markets/components/TokenCard.tsx @@ -40,13 +40,13 @@ export default function TokenCard({ token }: { token: Token }) { " onClick={(e) => { if (e.ctrlKey || e.metaKey) { - window.open(`/token/${token.mint}`, "_blank", "noopener,noreferrer"); + window.open(`/market/${token.mint}`, "_blank", "noopener,noreferrer"); } else { - router.push(`/token/${token.mint}`); + router.push(`/market/${token.mint}`); } }} onContextMenu={() => - window.open(`/token/${token.mint}`, "_blank", "noopener,noreferrer") + window.open(`/market/${token.mint}`, "_blank", "noopener,noreferrer") } > {/* Left: logo */} diff --git a/src/app/page.tsx b/src/app/page.tsx index fc98459..950006a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -22,38 +22,38 @@ export default function Landing() { {/* Hero */} -
-
-

+
+
+

Your copilot for
memecoin trading

-

- Catch rugs before they happen, execute trades at Solana - speed, and automate the research pro traders do. +

+ Catch rugs before they happen, execute trades at Solana speed, and + automate the research pro traders do.

-
+
- - - - + + +
@@ -61,7 +61,7 @@ export default function Landing() { {/* Features */}
- + {[ { icon: Shield, diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..8916905 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/src/components/ui/resizable.tsx b/src/components/ui/resizable.tsx new file mode 100644 index 0000000..bbd25cc --- /dev/null +++ b/src/components/ui/resizable.tsx @@ -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 ( + + ) +} + +function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) { + return +} + +function ResizableHandle({ + withHandle, + className, + ...props +}: ResizablePrimitive.SeparatorProps & { + withHandle?: boolean +}) { + return ( + div]:rotate-90", + className + )} + {...props} + > + {withHandle && ( +
+ +
+ )} +
+ ) +} + +export { ResizableHandle, ResizablePanel, ResizablePanelGroup } diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx new file mode 100644 index 0000000..4c24b2a --- /dev/null +++ b/src/components/ui/separator.tsx @@ -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) { + return ( + + ) +} + +export { Separator } diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 0000000..7f73dcd --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -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) { + return ( + + ) +} + +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 & + VariantProps) { + return ( + + ) +} + +function TabsTrigger({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants } diff --git a/src/modules/tokens/types.ts b/src/modules/tokens/types.ts index 8a3f3fb..3ef2e0d 100644 --- a/src/modules/tokens/types.ts +++ b/src/modules/tokens/types.ts @@ -3,7 +3,7 @@ export interface Token { symbol: string; name: string; logoURI?: string; - twitterPost: { + twitterPost?: { link: string; author: { username: string;