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/dashboard/converter.tsx
import { Head } from '@inertiajs/react';
import {
    ArrowRightLeft,
    Terminal,
    Code,
    Globe,
    Copy,
    Check,
} 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';

interface Currency {
    id: number;
    code: string;
    name: string;
    country: string | null;
    rate: number;
    active: boolean;
}

interface Token {
    id: number;
    name: string;
    token: string;
    default_currency: string;
    active: boolean;
    created_at: string;
    updated_at: string;
}

interface ConverterPageProps {
    tokens: Token[];
    currencies: Currency[];
}

export default function ConverterPage({
    tokens = [],
    currencies = [],
}: ConverterPageProps) {
    const [calcAmount, setCalcAmount] = useState<number>(100);
    const [calcFrom, setCalcFrom] = useState<string>('USD');
    const [calcTo, setCalcTo] = useState<string>('INR');
    const [copiedText, setCopiedText] = useState<string | null>(null);

    // Prepare options for searchable Combobox
    const currencyOptions = useMemo(() => {
        return currencies.map((c) => ({
            value: c.code,
            label: `${c.code} - ${c.name}`,
            searchTerms: `${c.code} ${c.name} ${c.country || ''}`,
        }));
    }, [currencies]);

    // Map currency code to rate
    const ratesMap = useMemo(() => {
        const map: Record<string, number> = {};
        currencies.forEach((c) => {
            map[c.code] = c.rate;
        });

        return map;
    }, [currencies]);

    // Live conversion value
    const calculatedValue = useMemo(() => {
        if (!ratesMap[calcFrom] || !ratesMap[calcTo]) {
            return 0;
        }

        const fromRate = ratesMap[calcFrom];
        const toRate = ratesMap[calcTo];

        return (calcAmount / fromRate) * toRate;
    }, [calcAmount, calcFrom, calcTo, ratesMap]);

    // Get active token string for code snippets
    const activeTokenStr = useMemo(() => {
        const activeToken = tokens.find((t) => t.active);

        return activeToken ? activeToken.token : 'YOUR_API_TOKEN';
    }, [tokens]);

    // Copy snippet to clipboard
    const copyCodeSnippet = (text: string, label: string) => {
        navigator.clipboard.writeText(text);
        setCopiedText(label);
        toast.success(`${label} snippet copied!`);
        setTimeout(() => setCopiedText(null), 2000);
    };

    // Code snippets
    const curlSnippet = `curl -X POST \\
  -H "Authorization: Bearer ${activeTokenStr}" \\
  -H "Content-Type: application/json" \\
  -d '{"amount": ${calcAmount}}' \\
  "${window.location.origin}/api/v1/${calcFrom}/${calcTo}"`;

    const jsSnippet = `fetch('${window.location.origin}/api/v1/${calcFrom}/${calcTo}', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ${activeTokenStr}',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ amount: ${calcAmount} })
})
.then(res => res.json())
.then(data => console.log(data));`;

    return (
        <>
            <Head title="Currency Converter Calculator" />
            <div className="min-h-screen w-full space-y-6 bg-background p-4 text-foreground md:p-6">
                {/* Header */}
                <div className="flex flex-col gap-2 border-b border-border pb-6">
                    <div className="flex items-center gap-2">
                        <span className="rounded-lg bg-primary/10 p-1.5 text-primary">
                            <ArrowRightLeft className="h-5 w-5" />
                        </span>
                        <h1 className="text-3xl font-extrabold tracking-tight text-foreground">
                            Currency Converter
                        </h1>
                    </div>
                    <p className="text-sm text-muted-foreground">
                        Test and verify conversion rates instantly. Copy
                        integration code snippets to query rates in your apps.
                    </p>
                </div>

                {/* Page Content Layout (Full Width Grid) */}
                <div className="grid gap-6 lg:grid-cols-3">
                    {/* Live Calculator Widget */}
                    <div className="lg:col-span-1">
                        <Card className="border border-border bg-card text-card-foreground shadow-xs">
                            <CardHeader>
                                <CardTitle className="flex items-center gap-2 text-lg font-bold">
                                    <ArrowRightLeft className="h-5 w-5 text-primary" />{' '}
                                    Live Test Converter
                                </CardTitle>
                                <CardDescription>
                                    Test conversion outputs relative to local
                                    rates.
                                </CardDescription>
                            </CardHeader>
                            <CardContent className="space-y-4">
                                <div className="space-y-2">
                                    <label className="text-xs font-semibold text-muted-foreground uppercase">
                                        Amount
                                    </label>
                                    <input
                                        type="number"
                                        value={calcAmount}
                                        onChange={(e) =>
                                            setCalcAmount(
                                                parseFloat(e.target.value) || 0,
                                            )
                                        }
                                        className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm text-foreground shadow-xs transition-colors focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-hidden"
                                    />
                                </div>

                                <div className="grid grid-cols-2 gap-3">
                                    <div className="space-y-2">
                                        <label className="text-xs font-semibold text-muted-foreground uppercase">
                                            From
                                        </label>
                                        <Combobox
                                            options={currencyOptions}
                                            value={calcFrom}
                                            onChange={setCalcFrom}
                                            placeholder="From..."
                                        />
                                    </div>
                                    <div className="space-y-2">
                                        <label className="text-xs font-semibold text-muted-foreground uppercase">
                                            To
                                        </label>
                                        <Combobox
                                            options={currencyOptions}
                                            value={calcTo}
                                            onChange={setCalcTo}
                                            placeholder="To..."
                                        />
                                    </div>
                                </div>

                                {/* Conversion Display Panel */}
                                <div className="mt-2 flex flex-col items-center justify-center gap-1.5 rounded-lg border border-border bg-muted p-4 text-center">
                                    <span className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">
                                        Conversion Result
                                    </span>
                                    <span className="text-xl font-bold text-foreground">
                                        {calcAmount.toLocaleString(undefined, {
                                            minimumFractionDigits: 2,
                                        })}{' '}
                                        {calcFrom} =
                                    </span>
                                    <span className="text-2xl font-black text-primary">
                                        {calculatedValue.toLocaleString(
                                            undefined,
                                            {
                                                minimumFractionDigits: 2,
                                                maximumFractionDigits: 4,
                                            },
                                        )}{' '}
                                        {calcTo}
                                    </span>
                                    <span className="mt-1 flex items-center gap-1 font-mono text-[10px] text-muted-foreground">
                                        1 {calcFrom} ={' '}
                                        {(
                                            ratesMap[calcTo] /
                                            ratesMap[calcFrom]
                                        ).toFixed(6)}{' '}
                                        {calcTo}
                                    </span>
                                </div>
                            </CardContent>
                        </Card>
                    </div>

                    {/* Developer Code Snippets */}
                    <div className="lg:col-span-2">
                        <Card className="border border-border bg-card text-card-foreground shadow-xs">
                            <CardHeader>
                                <CardTitle className="flex items-center gap-2 text-lg font-bold">
                                    <Terminal className="h-5 w-5 text-primary" />{' '}
                                    API Explorer
                                </CardTitle>
                                <CardDescription>
                                    Integrate the conversion service directly
                                    into your codebases.
                                </CardDescription>
                            </CardHeader>
                            <CardContent className="space-y-4">
                                <div className="space-y-3">
                                    <div className="flex items-center justify-between">
                                        <span className="flex items-center gap-1 text-xs font-semibold text-muted-foreground uppercase">
                                            <Code className="h-3.5 w-3.5" />{' '}
                                            cURL Command
                                        </span>
                                        <Button
                                            variant="ghost"
                                            size="sm"
                                            className="h-7 text-xs text-primary"
                                            onClick={() =>
                                                copyCodeSnippet(
                                                    curlSnippet,
                                                    'cURL',
                                                )
                                            }
                                        >
                                            {copiedText === 'cURL' ? (
                                                <Check className="mr-1 h-3.5 w-3.5 text-emerald-500" />
                                            ) : (
                                                <Copy className="mr-1 h-3.5 w-3.5" />
                                            )}
                                            {copiedText === 'cURL'
                                                ? 'Copied'
                                                : 'Copy'}
                                        </Button>
                                    </div>
                                    <pre className="overflow-x-auto rounded-lg border border-neutral-800 bg-neutral-900 p-3 font-mono text-xs leading-relaxed text-neutral-100 select-all">
                                        {curlSnippet}
                                    </pre>

                                    <div className="flex items-center justify-between pt-1">
                                        <span className="flex items-center gap-1 text-xs font-semibold text-muted-foreground uppercase">
                                            <Globe className="h-3.5 w-3.5" />{' '}
                                            JavaScript (Fetch)
                                        </span>
                                        <Button
                                            variant="ghost"
                                            size="sm"
                                            className="h-7 text-xs text-primary"
                                            onClick={() =>
                                                copyCodeSnippet(
                                                    jsSnippet,
                                                    'JavaScript',
                                                )
                                            }
                                        >
                                            {copiedText === 'JavaScript' ? (
                                                <Check className="mr-1 h-3.5 w-3.5 text-emerald-500" />
                                            ) : (
                                                <Copy className="mr-1 h-3.5 w-3.5" />
                                            )}
                                            {copiedText === 'JavaScript'
                                                ? 'Copied'
                                                : 'Copy'}
                                        </Button>
                                    </div>
                                    <pre className="overflow-x-auto rounded-lg border border-neutral-800 bg-neutral-900 p-3 font-mono text-xs leading-relaxed text-neutral-100 select-all">
                                        {jsSnippet}
                                    </pre>
                                </div>
                            </CardContent>
                        </Card>
                    </div>
                </div>
            </div>
        </>
    );
}

ConverterPage.layout = {
    breadcrumbs: [
        {
            title: 'Dashboard',
            href: '/dashboard',
        },
        {
            title: 'Currency Converter',
            href: '/dashboard/converter',
        },
    ],
};