Select a file from the repository tree to inspect its code.
resources/js/pages/welcome.tsx
Copy Code
import { Head, Link } from '@inertiajs/react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Database,
Sparkles,
Code,
ShieldCheck,
BookOpen,
ArrowLeftRight,
Copy,
Check,
Terminal,
} from 'lucide-react';
import React, { useState, useMemo } from 'react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Combobox } from '@/components/ui/combobox';
import { register } from '@/routes';
interface Currency {
id: number;
code: string;
name: string;
country: string | null;
rate: number;
}
interface WelcomeProps {
currencies?: Currency[];
}
export default function Welcome({ currencies = [] }: WelcomeProps) {
// Interactive Converter Widget State
const [amount, setAmount] = useState<string>('100');
const [fromCurrency, setFromCurrency] = useState<string>('USD');
const [toCurrency, setToCurrency] = useState<string>('INR');
const [copiedSnippet, setCopiedSnippet] = useState<boolean>(false);
const [activeTab, setActiveTab] = useState<
'curl' | 'javascript' | 'python'
>('curl');
// Prepare combobox options list
const currencyOptions = useMemo(() => {
return currencies.map((c) => ({
value: c.code,
label: `${c.code} - ${c.name}`,
searchTerms: `${c.code} ${c.name} ${c.country || ''}`,
}));
}, [currencies]);
// Live calculations
const conversionResult = useMemo(() => {
const from = currencies.find((c) => c.code === fromCurrency);
const to = currencies.find((c) => c.code === toCurrency);
const parsedAmount = parseFloat(amount);
if (!from || !to || isNaN(parsedAmount) || parsedAmount <= 0) {
return null;
}
// Convert base rates (which are stored relative to USD in the sync database)
// Amount USD = Amount / from.rate
// Converted = USD * to.rate
const converted = (parsedAmount / from.rate) * to.rate;
const exchangeRate = to.rate / from.rate;
return {
converted: converted.toFixed(4),
rate: exchangeRate.toFixed(6),
};
}, [currencies, amount, fromCurrency, toCurrency]);
// Quick swap currencies
const handleSwap = () => {
setFromCurrency(toCurrency);
setToCurrency(fromCurrency);
};
// Code snippets generator
const codeSnippets = {
curl: `curl -X GET "http://localhost:8000/api/v1" \\\n -H "Authorization: Bearer {YOUR_DEVELOPER_TOKEN}"`,
javascript: `fetch('http://localhost:8000/api/v1', {\n headers: {\n 'Authorization': 'Bearer {YOUR_DEVELOPER_TOKEN}',\n 'Accept': 'application/json'\n }\n})\n.then(res => res.json())\n.then(data => console.log(data));`,
python: `import requests\n\nurl = "http://localhost:8000/api/v1"\nheaders = {\n "Authorization": "Bearer {YOUR_DEVELOPER_TOKEN}",\n "Accept": "application/json"\n}\n\nresponse = requests.get(url, headers=headers)\nprint(response.json())`,
};
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
setCopiedSnippet(true);
toast.success('Code snippet copied to clipboard!');
setTimeout(() => setCopiedSnippet(false), 2000);
};
// Animation settings
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.15 },
},
};
const itemVariants = {
hidden: { y: 25, opacity: 0 },
visible: {
y: 0,
opacity: 1,
transition: { type: 'spring', stiffness: 80 },
},
};
return (
<>
<Head title="Developer Currency API Engine" />
<div className="mx-auto flex max-w-7xl flex-col items-center gap-12 px-4 py-12 sm:px-6 md:py-20 lg:flex-row lg:px-8">
{/* Left Column: Headline & Intro */}
<motion.div
className="flex-1 space-y-6 text-center lg:text-left"
initial="hidden"
animate="visible"
variants={containerVariants}
>
<motion.div
variants={itemVariants}
className="inline-flex items-center gap-1.5 rounded-full border border-primary/20 bg-primary/5 px-3 py-1 text-xs font-bold text-primary shadow-xs"
>
<Sparkles className="h-3.5 w-3.5" /> High Performance
Exchange API
</motion.div>
<motion.h2
variants={itemVariants}
className="text-4xl leading-[1.1] font-extrabold tracking-tight text-foreground sm:text-5xl lg:text-6xl"
>
The Developer-First <br />
<span className="bg-linear-to-r from-primary to-emerald-400 bg-clip-text text-transparent">
Exchange Rates Engine
</span>
</motion.h2>
<motion.p
variants={itemVariants}
className="mx-auto max-w-xl text-base text-muted-foreground sm:text-lg lg:mx-0"
>
A powerful, Redis-cached API serving daily-synced global
exchange rates for 150+ countries. Zero database
latency, beautiful dashboard developer keys, and clean
responses.
</motion.p>
<motion.div
variants={itemVariants}
className="flex flex-wrap justify-center gap-4 lg:justify-start"
>
<Button
asChild
size="lg"
className="bg-primary font-semibold text-primary-foreground shadow-md hover:bg-primary/90"
>
<Link href={register()}>Register Free Token</Link>
</Button>
<Button
variant="outline"
size="lg"
asChild
className="shadow-xs"
>
<a href="/docs" className="flex items-center gap-2">
<BookOpen className="h-5 w-5" /> Explorer
Reference
</a>
</Button>
</motion.div>
{/* Integration Metrics stats */}
<motion.div
variants={itemVariants}
className="mx-auto grid max-w-md grid-cols-3 gap-6 border-t border-border pt-6 lg:mx-0"
>
<div>
<h4 className="text-xl font-bold text-foreground">
150+
</h4>
<p className="text-xs text-muted-foreground">
Mapped Currencies
</p>
</div>
<div>
<h4 className="text-xl font-bold text-foreground">
< 10ms
</h4>
<p className="text-xs text-muted-foreground">
Redis Cache Speed
</p>
</div>
<div>
<h4 className="text-xl font-bold text-foreground">
100%
</h4>
<p className="text-xs text-muted-foreground">
Custom API Schema
</p>
</div>
</motion.div>
</motion.div>
{/* Right Column: Live Interactive Converter Widget */}
<motion.div
className="w-full max-w-lg"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<Card className="relative border border-border bg-card text-card-foreground shadow-xl backdrop-blur-xs">
<CardHeader className="border-b border-border/50 pb-4">
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center gap-1.5 text-lg font-bold">
<ArrowLeftRight className="h-4.5 w-4.5 text-primary" />{' '}
Converter Playground
</CardTitle>
<CardDescription>
Experience real-time calculations
mapping direct backend rates.
</CardDescription>
</div>
<span className="rounded-full border border-emerald-500/20 bg-emerald-500/10 px-2 py-0.5 text-[10px] font-bold text-emerald-600 dark:text-emerald-400">
Live Rates
</span>
</div>
</CardHeader>
<CardContent className="space-y-5 pt-6">
{/* Amount Input */}
<div className="space-y-1.5">
<label
htmlFor="amount"
className="text-xs font-semibold tracking-wider text-muted-foreground uppercase"
>
Amount to Convert
</label>
<input
id="amount"
type="number"
min="0"
placeholder="Enter amount..."
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 font-mono text-sm text-foreground shadow-xs transition-colors focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-hidden"
/>
</div>
{/* Currency selectors (From & To) */}
<div className="relative grid grid-cols-1 items-center gap-2 sm:grid-cols-9">
<div className="space-y-1.5 sm:col-span-4">
<label className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">
From
</label>
<Combobox
options={currencyOptions}
value={fromCurrency}
onChange={(val) =>
val && setFromCurrency(val)
}
placeholder="Select source..."
/>
</div>
{/* Swap Button */}
<div className="flex justify-center pt-5 sm:col-span-1">
<Button
type="button"
variant="ghost"
size="icon"
onClick={handleSwap}
className="h-8 w-8 rounded-full text-primary hover:bg-primary/10"
title="Swap currencies"
>
<ArrowLeftRight className="h-4 w-4 rotate-90 sm:rotate-0" />
</Button>
</div>
<div className="space-y-1.5 sm:col-span-4">
<label className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">
To
</label>
<Combobox
options={currencyOptions}
value={toCurrency}
onChange={(val) =>
val && setToCurrency(val)
}
placeholder="Select target..."
/>
</div>
</div>
{/* Conversion Result */}
<AnimatePresence mode="wait">
{conversionResult ? (
<motion.div
key={`${amount}-${fromCurrency}-${toCurrency}`}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="space-y-1 rounded-lg border border-primary/20 bg-primary/5 p-4 text-center"
>
<p className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">
Calculated Value
</p>
<p className="font-mono text-2xl font-black text-foreground">
{amount} {fromCurrency} ={' '}
{conversionResult.converted}{' '}
{toCurrency}
</p>
<p className="font-mono text-[11px] text-muted-foreground">
Relative Midpoint Exchange Rate: 1{' '}
{fromCurrency} ={' '}
{conversionResult.rate} {toCurrency}
</p>
</motion.div>
) : (
<div className="rounded-lg border border-border bg-muted/10 p-4 text-center text-xs text-muted-foreground">
Please provide a valid non-zero amount.
</div>
)}
</AnimatePresence>
</CardContent>
</Card>
</motion.div>
</div>
{/* API Code Integration Snippet */}
<section className="border-y border-border bg-muted/10 py-16">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mx-auto mb-10 max-w-3xl space-y-4 text-center">
<span className="rounded-full bg-primary/10 px-3 py-1 text-xs font-extrabold tracking-widest text-primary uppercase">
Developer Sandbox
</span>
<h3 className="text-3xl font-extrabold tracking-tight text-foreground">
Integrate Currency Data in Seconds
</h3>
<p className="mx-auto max-w-md text-sm text-muted-foreground">
Standard authorization utilizes standard Bearer
tokens. Copy the starter configurations below.
</p>
</div>
{/* Interactive Tabbed Code Snippet Card */}
<div className="mx-auto max-w-3xl">
<Card className="overflow-hidden border-zinc-800 bg-[#1b1b1b] text-zinc-100 shadow-lg">
<div className="flex items-center justify-between border-b border-zinc-900 bg-[#121212] px-4 py-2.5 text-xs text-zinc-400">
<div className="flex items-center gap-1.5">
<Terminal className="h-4 w-4 text-primary" />
<span className="font-mono">
ghostcompiler-api Query Explorer
</span>
</div>
<div className="flex items-center gap-2">
<button
onClick={() =>
copyToClipboard(
codeSnippets[activeTab],
)
}
className="rounded-md p-1 transition-colors hover:text-white"
title="Copy snippet"
>
{copiedSnippet ? (
<Check className="h-4 w-4 text-emerald-500" />
) : (
<Copy className="h-4 w-4" />
)}
</button>
</div>
</div>
<div className="flex border-b border-zinc-950 bg-[#151515] text-xs font-semibold">
<button
onClick={() => setActiveTab('curl')}
className={`border-r border-zinc-950 px-4 py-3 transition-colors ${activeTab === 'curl' ? 'border-t-2 border-t-primary bg-[#1b1b1b] text-primary' : 'text-zinc-400 hover:text-zinc-200'}`}
>
cURL
</button>
<button
onClick={() => setActiveTab('javascript')}
className={`border-r border-zinc-950 px-4 py-3 transition-colors ${activeTab === 'javascript' ? 'border-t-2 border-t-primary bg-[#1b1b1b] text-primary' : 'text-zinc-400 hover:text-zinc-200'}`}
>
Javascript / Fetch
</button>
<button
onClick={() => setActiveTab('python')}
className={`border-r border-zinc-950 px-4 py-3 transition-colors ${activeTab === 'python' ? 'border-t-2 border-t-primary bg-[#1b1b1b] text-primary' : 'text-zinc-400 hover:text-zinc-200'}`}
>
Python
</button>
</div>
<div className="overflow-x-auto bg-[#161616] p-5 font-mono text-xs leading-relaxed text-emerald-400">
<pre className="whitespace-pre">
{codeSnippets[activeTab]}
</pre>
</div>
</Card>
</div>
</div>
</section>
{/* Features Section */}
<section className="mx-auto max-w-7xl px-4 py-20 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 gap-8 md:grid-cols-3">
<Card className="border border-border bg-card text-card-foreground shadow-xs transition-colors hover:border-muted">
<CardHeader>
<span className="mb-2 inline-block w-fit rounded-lg bg-primary/10 p-2 text-primary">
<Database className="h-5 w-5" />
</span>
<CardTitle className="text-base font-bold">
Redis-Speed Caching
</CardTitle>
<CardDescription>
Data is cached directly inside Redis to reduce
query latency to milliseconds.
</CardDescription>
</CardHeader>
</Card>
<Card className="border border-border bg-card text-card-foreground shadow-xs transition-colors hover:border-muted">
<CardHeader>
<span className="mb-2 inline-block w-fit rounded-lg bg-emerald-500/10 p-2 text-emerald-600 dark:text-emerald-400">
<ShieldCheck className="h-5 w-5" />
</span>
<CardTitle className="text-base font-bold">
Secure Access Keys
</CardTitle>
<CardDescription>
Rotate, revoke, and toggle state toggles
dynamically inside your dashboard portal.
</CardDescription>
</CardHeader>
</Card>
<Card className="border border-border bg-card text-card-foreground shadow-xs transition-colors hover:border-muted">
<CardHeader>
<span className="mb-2 inline-block w-fit rounded-lg bg-amber-500/10 p-2 text-amber-600 dark:text-amber-400">
<Code className="h-5 w-5" />
</span>
<CardTitle className="text-base font-bold">
Developer-Friendly Schema
</CardTitle>
<CardDescription>
Returns proper status indicators, country
descriptions, and currency names alongside raw
rates.
</CardDescription>
</CardHeader>
</Card>
</div>
</section>
</>
);
}