initial checkin

This commit is contained in:
tim
2025-10-12 19:17:51 -04:00
commit bb2f7c9ce8
44 changed files with 10741 additions and 0 deletions

BIN
src/app/apple-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

60
src/app/globals.css Normal file
View File

@@ -0,0 +1,60 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 217 91% 60%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 217 91% 60%;
--radius: 0.5rem;
}
.dark {
--background: 224 71% 4%;
--foreground: 213 31% 91%;
--card: 224 71% 6%;
--card-foreground: 213 31% 91%;
--popover: 224 71% 6%;
--popover-foreground: 213 31% 91%;
--primary: 217 91% 60%;
--primary-foreground: 213 31% 91%;
--secondary: 222 47% 11%;
--secondary-foreground: 213 31% 91%;
--muted: 223 47% 11%;
--muted-foreground: 215.4 16.3% 56.9%;
--accent: 216 34% 17%;
--accent-foreground: 213 31% 91%;
--destructive: 0 63% 31%;
--destructive-foreground: 213 31% 91%;
--border: 216 34% 17%;
--input: 216 34% 17%;
--ring: 217 91% 60%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
font-feature-settings: "rlig" 1, "calt" 1;
}
}

36
src/app/icon0.svg Normal file
View File

@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><g clip-path="url(#SvgjsClipPath1209)"><rect width="1000" height="1000" fill="#ffffff"></rect><g transform="matrix(9.25540316264299,0,0,9.25540316264299,100,100)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="86.435997" height="86.435997"><svg version="1.1" viewBox="0 0 86.435997 86.435997" id="svg188" sodipodi:docname="logo-flower.svg" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs192"><clipPath id="SvgjsClipPath1209"><rect width="1000" height="1000" x="0" y="0" rx="500" ry="500"></rect></clipPath></defs>
<sodipodi:namedview id="namedview190" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" showgrid="false" inkscape:zoom="5.6893399" inkscape:cx="83.489475" inkscape:cy="69.515973" inkscape:window-width="1864" inkscape:window-height="1131" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="svg188"></sodipodi:namedview>
<!-- Flower -->
<g fill="#000000" id="g186" transform="translate(-1.782,-1.782)">
<g id="g184">
<circle fill="#000000" cx="45" cy="44.999001" r="5.277" id="circle148"></circle>
<g id="g182">
<g id="g152">
<path fill="#0d47a1" class="blue1" d="m 45.214,33.672 c 24.117,-31.271 0.369,-31.89 0.369,-31.89 0,0 -23.756,0.069 -0.369,31.89 z" id="path150"></path>
</g>
<g id="g156">
<path fill="#1976d2" class="blue2" d="M 37.141,36.837 C 32.082,-2.327 14.853,14.027 14.853,14.027 c 0,0 -16.749,16.847 22.288,22.81 z" id="path154"></path>
</g>
<g id="g160">
<path fill="#2196f3" class="blue3" d="M 33.67,44.786 C 2.401,20.667 1.782,44.416 1.782,44.416 c 0,0 0.069,23.757 31.888,0.37 z" id="path158"></path>
</g>
<g id="g164">
<path fill="#00bcd4" class="blue4" d="m 36.837,52.859 c -39.166,5.059 -22.81,22.288 -22.81,22.288 0,0 16.847,16.752 22.81,-22.288 z" id="path162"></path>
</g>
<g id="g168">
<path fill="#4fc3f7" class="blue5" d="m 44.785,56.33 c -24.118,31.271 -0.369,31.888 -0.369,31.888 0,0 23.755,-0.069 0.369,-31.888 z" id="path166"></path>
</g>
<g id="g172">
<path fill="#3f51b5" class="blue6" d="m 52.859,53.164 c 5.057,39.164 22.287,22.81 22.287,22.81 0,0 16.75,-16.847 -22.287,-22.81 z" id="path170"></path>
</g>
<g id="g176">
<path fill="#039be5" class="blue7" d="m 56.33,45.216 c 31.271,24.117 31.888,0.369 31.888,0.369 0,0 -0.07,-23.756 -31.888,-0.369 z" id="path174"></path>
</g>
<g id="g180">
<path fill="#64b5f6" class="blue8" d="m 53.162,37.142 c 39.166,-5.061 22.811,-22.289 22.811,-22.289 0,0 -16.847,-16.749 -22.811,22.289 z" id="path178"></path>
</g>
</g>
</g>
</g>
</svg></svg></g></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
src/app/icon1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

21
src/app/layout.tsx Normal file
View File

@@ -0,0 +1,21 @@
import '@rainbow-me/rainbowkit/styles.css';
import '@/app/globals.css';
import { Providers } from '@/components/providers';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<title>Liquidity Party</title>
<meta name="description" content="Decentralized Exchange for Multi-Asset AMM Pools" />
</head>
<body className="min-h-screen">
<Providers>{children}</Providers>
</body>
</html>
);
}

21
src/app/manifest.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "Liquidity Party",
"short_name": "Liquidity Party",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

31
src/app/page.tsx Normal file
View File

@@ -0,0 +1,31 @@
'use client';
import { useTranslation } from 'react-i18next';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { SwapForm } from '@/components/swap-form';
export default function HomePage() {
const { t } = useTranslation();
return (
<div className="max-w-2xl mx-auto">
<Tabs defaultValue="swap" className="w-full">
<TabsList className="grid w-full grid-cols-2 mb-8">
<TabsTrigger value="swap">{t('nav.swap')}</TabsTrigger>
<TabsTrigger value="stake">{t('nav.stake')}</TabsTrigger>
</TabsList>
<TabsContent value="swap">
<SwapForm />
</TabsContent>
<TabsContent value="stake">
<div className="text-center py-12 text-muted-foreground">
<h2 className="text-2xl font-semibold mb-2">{t('stake.title')}</h2>
<p>{t('stake.comingSoon')}</p>
</div>
</TabsContent>
</Tabs>
</div>
);
}

56
src/components/header.tsx Normal file
View File

@@ -0,0 +1,56 @@
'use client';
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useTheme } from 'next-themes';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button';
import { LanguageSelector } from '@/components/language-selector';
import { Moon, Sun } from 'lucide-react';
import { useEffect, useState } from 'react';
export function Header() {
const { theme, setTheme } = useTheme();
const { t } = useTranslation();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
const toggleTheme = () => {
console.log('Toggle clicked, current theme:', theme);
const newTheme = theme === 'dark' ? 'light' : 'dark';
console.log('Setting theme to:', newTheme);
setTheme(newTheme);
};
const logoSrc = !mounted ? '/logo-dark.svg' : theme === 'dark' ? '/logo-dark.svg' : '/logo-light.svg';
return (
<header className="border-b">
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<img
src={logoSrc}
alt="Liquidity Party"
className="h-8 w-auto"
/>
</div>
<div className="flex items-center gap-2">
{/* Language Selector */}
<LanguageSelector />
{/* Theme Toggle */}
<Button variant="ghost" size="icon" onClick={toggleTheme} type="button">
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
{/* Wallet Connect */}
<ConnectButton />
</div>
</div>
</header>
);
}

View File

@@ -0,0 +1,54 @@
'use client';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { languages, type Language } from '@/lib/i18n';
import { Globe } from 'lucide-react';
export function LanguageSelector() {
const { i18n } = useTranslation();
const changeLanguage = (lng: Language) => {
i18n.changeLanguage(lng);
if (typeof window !== 'undefined') {
localStorage.setItem('language', lng);
}
};
const currentLanguage = (i18n.language as Language) || 'en';
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="relative" type="button">
<Globe className="h-5 w-5" />
<span className="absolute -bottom-1 -right-1 text-xs">
{languages[currentLanguage]?.flag || '🇬🇧'}
</span>
<span className="sr-only">Change language</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[160px]">
{Object.entries(languages).map(([code, { name, flag }]) => (
<DropdownMenuItem
key={code}
onClick={() => changeLanguage(code as Language)}
className="cursor-pointer"
>
<span className="mr-2 text-lg">{flag}</span>
<span>{name}</span>
{currentLanguage === code && (
<span className="ml-auto text-primary"></span>
)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -0,0 +1,72 @@
'use client';
import { getDefaultConfig, RainbowKitProvider, darkTheme, lightTheme } from '@rainbow-me/rainbowkit';
import { WagmiProvider } from 'wagmi';
import { mainnet, polygon, optimism, arbitrum, base } from 'wagmi/chains';
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
import { ThemeProvider, useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
import { TranslationsProvider } from '@/providers/translations-provider';
import { Header } from '@/components/header';
const config = getDefaultConfig({
appName: 'Liquidity Party',
projectId: 'YOUR_PROJECT_ID', // Get this from https://cloud.walletconnect.com
chains: [mainnet, polygon, optimism, arbitrum, base],
ssr: false,
});
const queryClient = new QueryClient();
function Web3Provider({ children }: { children: React.ReactNode }) {
const { theme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
// Use dark theme by default until mounted, then follow the actual theme
const rainbowKitTheme = !mounted || theme === 'dark'
? darkTheme({
accentColor: '#a855f7', // Purple to match your primary color
accentColorForeground: 'white',
})
: lightTheme({
accentColor: '#a855f7',
accentColorForeground: 'white',
});
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider theme={rainbowKitTheme}>
{children}
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
);
}
export function Providers({ children }: { children: React.ReactNode }) {
return (
<TranslationsProvider>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem={false}
storageKey="liquidity-party-theme"
>
<Web3Provider>
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1 container mx-auto px-4 py-8">
{children}
</main>
</div>
</Web3Provider>
</ThemeProvider>
</TranslationsProvider>
);
}

View File

@@ -0,0 +1,97 @@
'use client';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { ArrowDownUp } from 'lucide-react';
import { useAccount } from 'wagmi';
export function SwapForm() {
const { t } = useTranslation();
const { isConnected } = useAccount();
const [fromAmount, setFromAmount] = useState('');
const [toAmount, setToAmount] = useState('');
const handleSwap = () => {
// Swap logic will be implemented later
console.log('Swap clicked');
};
const switchTokens = () => {
// Switch tokens logic
setFromAmount(toAmount);
setToAmount(fromAmount);
};
return (
<Card className="w-full max-w-md mx-auto">
<CardHeader>
<CardTitle>{t('swap.title')}</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* From Token */}
<div className="space-y-2">
<div className="flex justify-between text-sm">
<label className="text-muted-foreground">{t('swap.youPay')}</label>
<span className="text-muted-foreground">{t('swap.balance')}: 0.00</span>
</div>
<div className="flex gap-2">
<Input
type="number"
placeholder="0.0"
value={fromAmount}
onChange={(e) => setFromAmount(e.target.value)}
className="text-2xl h-16"
/>
<Button variant="secondary" className="min-w-[120px]">
{t('swap.selectToken')}
</Button>
</div>
</div>
{/* Switch Button */}
<div className="flex justify-center">
<Button
variant="ghost"
size="icon"
onClick={switchTokens}
className="rounded-full"
>
<ArrowDownUp className="h-4 w-4" />
</Button>
</div>
{/* To Token */}
<div className="space-y-2">
<div className="flex justify-between text-sm">
<label className="text-muted-foreground">{t('swap.youReceive')}</label>
<span className="text-muted-foreground">{t('swap.balance')}: 0.00</span>
</div>
<div className="flex gap-2">
<Input
type="number"
placeholder="0.0"
value={toAmount}
onChange={(e) => setToAmount(e.target.value)}
className="text-2xl h-16"
/>
<Button variant="secondary" className="min-w-[120px]">
{t('swap.selectToken')}
</Button>
</div>
</div>
{/* Swap Button */}
<Button
className="w-full h-14 text-lg"
onClick={handleSwap}
disabled={!isConnected || !fromAmount || !toAmount}
>
{!isConnected ? t('swap.connectWalletToSwap') : t('swap.swapButton')}
</Button>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,55 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -0,0 +1,78 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -0,0 +1,182 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
)}
{...props}
>
{children}
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View File

@@ -0,0 +1,24 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View File

@@ -0,0 +1,54 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

46
src/lib/i18n.ts Normal file
View File

@@ -0,0 +1,46 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import enTranslations from '@/locales/en.json';
import esTranslations from '@/locales/es.json';
import frTranslations from '@/locales/fr.json';
import deTranslations from '@/locales/de.json';
import zhTranslations from '@/locales/zh.json';
import jaTranslations from '@/locales/ja.json';
export const languages = {
en: { name: 'English', flag: '🇬🇧' },
es: { name: 'Español', flag: '🇪🇸' },
fr: { name: 'Français', flag: '🇫🇷' },
de: { name: 'Deutsch', flag: '🇩🇪' },
zh: { name: '中文', flag: '🇨🇳' },
ja: { name: '日本語', flag: '🇯🇵' },
} as const;
export type Language = keyof typeof languages;
const resources = {
en: { translation: enTranslations },
es: { translation: esTranslations },
fr: { translation: frTranslations },
de: { translation: deTranslations },
zh: { translation: zhTranslations },
ja: { translation: jaTranslations },
};
// Initialize i18next
i18n
.use(initReactI18next)
.init({
resources,
lng: typeof window !== 'undefined' ? localStorage.getItem('language') || 'en' : 'en',
fallbackLng: 'en',
interpolation: {
escapeValue: false, // React already escapes values
},
react: {
useSuspense: false, // Disable suspense for client-side only
},
});
export default i18n;

6
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

25
src/locales/de.json Normal file
View File

@@ -0,0 +1,25 @@
{
"common": {
"liquidityParty": "Liquidity Party",
"connectWallet": "Wallet Verbinden"
},
"nav": {
"swap": "Tauschen",
"stake": "Staken"
},
"swap": {
"title": "Tauschen",
"from": "Von",
"to": "Zu",
"youPay": "Sie zahlen",
"youReceive": "Sie erhalten",
"balance": "Guthaben",
"selectToken": "Token auswählen",
"swapButton": "Tauschen",
"connectWalletToSwap": "Wallet verbinden zum Tauschen"
},
"stake": {
"title": "Staken",
"comingSoon": "Demnächst verfügbar..."
}
}

25
src/locales/en.json Normal file
View File

@@ -0,0 +1,25 @@
{
"common": {
"liquidityParty": "Liquidity Party",
"connectWallet": "Connect Wallet"
},
"nav": {
"swap": "Swap",
"stake": "Stake"
},
"swap": {
"title": "Swap",
"from": "From",
"to": "To",
"youPay": "You pay",
"youReceive": "You receive",
"balance": "Balance",
"selectToken": "Select token",
"swapButton": "Swap",
"connectWalletToSwap": "Connect wallet to swap"
},
"stake": {
"title": "Stake",
"comingSoon": "Coming soon..."
}
}

25
src/locales/es.json Normal file
View File

@@ -0,0 +1,25 @@
{
"common": {
"liquidityParty": "Liquidity Party",
"connectWallet": "Conectar Billetera"
},
"nav": {
"swap": "Intercambiar",
"stake": "Apostar"
},
"swap": {
"title": "Intercambiar",
"from": "Desde",
"to": "Hasta",
"youPay": "Pagas",
"youReceive": "Recibes",
"balance": "Saldo",
"selectToken": "Seleccionar token",
"swapButton": "Intercambiar",
"connectWalletToSwap": "Conecta tu billetera para intercambiar"
},
"stake": {
"title": "Apostar",
"comingSoon": "Próximamente..."
}
}

25
src/locales/fr.json Normal file
View File

@@ -0,0 +1,25 @@
{
"common": {
"liquidityParty": "Liquidity Party",
"connectWallet": "Connecter le Portefeuille"
},
"nav": {
"swap": "Échanger",
"stake": "Staker"
},
"swap": {
"title": "Échanger",
"from": "De",
"to": "À",
"youPay": "Vous payez",
"youReceive": "Vous recevez",
"balance": "Solde",
"selectToken": "Sélectionner un token",
"swapButton": "Échanger",
"connectWalletToSwap": "Connectez votre portefeuille pour échanger"
},
"stake": {
"title": "Staker",
"comingSoon": "Bientôt disponible..."
}
}

25
src/locales/ja.json Normal file
View File

@@ -0,0 +1,25 @@
{
"common": {
"liquidityParty": "Liquidity Party",
"connectWallet": "ウォレット接続"
},
"nav": {
"swap": "スワップ",
"stake": "ステーキング"
},
"swap": {
"title": "スワップ",
"from": "から",
"to": "へ",
"youPay": "支払い",
"youReceive": "受取り",
"balance": "残高",
"selectToken": "トークンを選択",
"swapButton": "スワップ",
"connectWalletToSwap": "スワップするにはウォレットを接続してください"
},
"stake": {
"title": "ステーキング",
"comingSoon": "近日公開..."
}
}

25
src/locales/zh.json Normal file
View File

@@ -0,0 +1,25 @@
{
"common": {
"liquidityParty": "Liquidity Party",
"connectWallet": "连接钱包"
},
"nav": {
"swap": "兑换",
"stake": "质押"
},
"swap": {
"title": "兑换",
"from": "从",
"to": "到",
"youPay": "您支付",
"youReceive": "您收到",
"balance": "余额",
"selectToken": "选择代币",
"swapButton": "兑换",
"connectWalletToSwap": "连接钱包以进行兑换"
},
"stake": {
"title": "质押",
"comingSoon": "即将推出..."
}
}

View File

@@ -0,0 +1,9 @@
'use client';
import * as React from 'react';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { type ThemeProviderProps } from 'next-themes/dist/types';
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

View File

@@ -0,0 +1,26 @@
'use client';
import { ReactNode, useEffect, useState } from 'react';
import { I18nextProvider } from 'react-i18next';
import i18n from '@/lib/i18n';
export function TranslationsProvider({ children }: { children: ReactNode }) {
const [isInitialized, setIsInitialized] = useState(false);
useEffect(() => {
// Ensure i18n is initialized on the client side
if (!isInitialized) {
setIsInitialized(true);
}
}, [isInitialized]);
if (!isInitialized) {
return null;
}
return (
<I18nextProvider i18n={i18n}>
{children}
</I18nextProvider>
);
}

View File

@@ -0,0 +1,26 @@
'use client';
import '@rainbow-me/rainbowkit/styles.css';
import { getDefaultConfig, RainbowKitProvider } from '@rainbow-me/rainbowkit';
import { WagmiProvider } from 'wagmi';
import { mainnet, polygon, optimism, arbitrum, base } from 'wagmi/chains';
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
const config = getDefaultConfig({
appName: 'Liquidity Party',
projectId: 'YOUR_PROJECT_ID', // Get this from https://cloud.walletconnect.com
chains: [mainnet, polygon, optimism, arbitrum, base],
ssr: false,
});
const queryClient = new QueryClient();
export function Web3Provider({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>{children}</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
);
}