Currency Api Dashboard

TypeScript

Modern currency exchange rate dashboard with real-time market data, conversion tools, analytics, historical trends, and responsive admin interface.

Stars
18
Forks
1
Downloads
N/A
Open Issues
0
Files main

Repository Files

Loading file structure...
resources/js/pages/welcome.tsx
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">
                                &lt; 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>
        </>
    );
}