Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions website/src/components/SpotlightCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useRef, useState, useCallback } from 'react';

/**
* SpotlightCard — adds a radial glow that follows the cursor.
* Premium hover effect used by Linear, Vercel, Clerk, etc.
*/

interface SpotlightCardProps {
children: React.ReactNode;
className?: string;
}

export function SpotlightCard({ children, className = '' }: SpotlightCardProps) {
const ref = useRef<HTMLDivElement>(null);
const [position, setPosition] = useState({ x: 0, y: 0 });
const [opacity, setOpacity] = useState(0);

const handleMouseMove = useCallback((e: React.MouseEvent) => {
if (!ref.current) return;
const rect = ref.current.getBoundingClientRect();
setPosition({
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
}, []);

const handleMouseEnter = useCallback(() => setOpacity(1), []);
const handleMouseLeave = useCallback(() => setOpacity(0), []);

return (
<div
ref={ref}
className={`relative overflow-hidden ${className}`}
onMouseMove={handleMouseMove}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div
className="pointer-events-none absolute -inset-px transition-opacity duration-300"
style={{
opacity,
background: `radial-gradient(600px circle at ${position.x}px ${position.y}px, rgba(30,144,255,0.12), transparent 40%)`,
}}
/>
{children}
</div>
);
}

export default SpotlightCard;

79 changes: 79 additions & 0 deletions website/src/components/StakingCalculator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useState } from 'react';
import { TrendingUp } from 'lucide-react';

export function StakingCalculator() {
const [amount, setAmount] = useState(1000000);
const [apy, setApy] = useState(8);
const [years, setYears] = useState(1);

const reward = amount * Math.pow(1 + apy / 100, years) - amount;
const total = amount + reward;

return (
<div className="my-6 overflow-hidden rounded-xl border border-border bg-card shadow-[0_0_0_1px_rgba(0,0,0,0.08)] dark:shadow-[0_0_0_1px_rgba(255,255,255,0.08)]">
<div className="border-b border-border px-4 py-3 sm:px-5">
<div className="flex items-center gap-2">
<TrendingUp className="h-4 w-4 text-primary" />
<h3 className="text-sm font-semibold">Staking APY Calculator</h3>
</div>
</div>
<div className="space-y-4 p-4 sm:p-5">
<div>
<label className="mb-2 block text-xs font-medium text-muted-foreground">XDC Amount</label>
<input
type="range"
min="100000"
max="10000000"
step="100000"
value={amount}
onChange={(e) => setAmount(Number(e.target.value))}
className="w-full accent-primary"
/>
<div className="mt-1 text-sm font-medium">{amount.toLocaleString()} XDC</div>
</div>

<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label className="mb-2 block text-xs font-medium text-muted-foreground">APY %</label>
<input
type="range"
min="1"
max="20"
step="0.5"
value={apy}
onChange={(e) => setApy(Number(e.target.value))}
className="w-full accent-primary"
/>
<div className="mt-1 text-sm font-medium">{apy}%</div>
</div>
<div>
<label className="mb-2 block text-xs font-medium text-muted-foreground">Years</label>
<input
type="range"
min="1"
max="5"
step="1"
value={years}
onChange={(e) => setYears(Number(e.target.value))}
className="w-full accent-primary"
/>
<div className="mt-1 text-sm font-medium">{years} year{years > 1 ? 's' : ''}</div>
</div>
</div>

<div className="rounded-lg bg-muted p-4">
<div className="flex flex-col justify-between gap-1 sm:flex-row sm:items-center">
<span className="text-sm text-muted-foreground">Total Reward</span>
<span className="text-lg font-semibold text-primary">+{reward.toLocaleString(undefined, { maximumFractionDigits: 0 })} XDC</span>
</div>
<div className="mt-1 flex flex-col justify-between gap-1 text-xs text-muted-foreground sm:flex-row sm:items-center">
<span>Total Value</span>
<span>{total.toLocaleString(undefined, { maximumFractionDigits: 0 })} XDC</span>
</div>
</div>
</div>
</div>
);
}

export default StakingCalculator;
74 changes: 74 additions & 0 deletions website/src/components/TransactionLifecycle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useState } from 'react';
import { Send, CheckCircle2, Clock, Box } from 'lucide-react';

const stages = [
{ id: 'broadcasted', title: 'Broadcasted', description: 'Transaction sent to the network' },
{ id: 'included', title: 'Included', description: 'Transaction added to a block' },
{ id: 'reversible', title: 'Reversible', description: 'Block confirmed but not yet final' },
{ id: 'finalized', title: 'Finalized', description: 'Transaction is irreversible' },
];

export function TransactionLifecycle() {
const [activeStage, setActiveStage] = useState(3);

return (
<div className="my-6 overflow-hidden rounded-xl border border-border bg-card shadow-[0_0_0_1px_rgba(0,0,0,0.08)] dark:shadow-[0_0_0_1px_rgba(255,255,255,0.08)]">
<div className="border-b border-border px-5 py-3">
<div className="flex items-center gap-2">
<Send className="h-4 w-4 text-primary" />
<h3 className="text-sm font-semibold">Transaction Lifecycle</h3>
</div>
</div>
<div className="p-5">
<div className="relative flex justify-between">
{stages.map((stage, index) => (
<button
key={stage.id}
onClick={() => setActiveStage(index)}
className="group relative z-10 flex flex-col items-center gap-2"
>
<div
className={`flex h-10 w-10 items-center justify-center rounded-full border-2 transition-all ${
index <= activeStage
? 'border-primary bg-primary text-primary-foreground'
: 'border-border bg-background text-muted-foreground'
}`}
>
{index < activeStage ? (
<CheckCircle2 className="h-5 w-5" />
) : index === activeStage ? (
<Clock className="h-5 w-5" />
) : (
<Box className="h-5 w-5" />
)}
</div>
<span
className={`text-xs font-medium transition-colors ${
index <= activeStage ? 'text-foreground' : 'text-muted-foreground'
}`}
>
{stage.title}
</span>
</button>
))}
<div className="absolute left-0 right-0 top-5 h-0.5 bg-border">
<div
className="h-full bg-primary transition-all duration-500"
style={{ width: `${(activeStage / (stages.length - 1)) * 100}%` }}
/>
</div>
</div>

<div className="mt-6 rounded-lg bg-muted p-4">
<h4 className="font-semibold">{stages[activeStage].title}</h4>
<p className="mt-1 text-sm text-muted-foreground">{stages[activeStage].description}</p>
<div className="mt-3 text-xs text-muted-foreground">
{activeStage === 3 ? 'Typical time on XDC: ~2-4 seconds' : `Step ${activeStage + 1} of ${stages.length}`}
</div>
</div>
</div>
</div>
);
}

export default TransactionLifecycle;
63 changes: 63 additions & 0 deletions website/src/components/WalletSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useState } from 'react';
import { Wallet, ArrowRight, CheckCircle2 } from 'lucide-react';

const wallets = [
{ name: 'XDCPay', type: 'Browser Extension', recommended: true },
{ name: 'MetaMask', type: 'Multi-chain Wallet' },
{ name: 'Ledger', type: 'Hardware Wallet' },
{ name: 'Trust Wallet', type: 'Mobile Wallet' },
];

export function WalletSelector() {
const [selected, setSelected] = useState(wallets[0].name);

return (
<div className="my-6 overflow-hidden rounded-xl border border-border bg-card shadow-[0_0_0_1px_rgba(0,0,0,0.08)] dark:shadow-[0_0_0_1px_rgba(255,255,255,0.08)]">
<div className="border-b border-border px-5 py-3">
<div className="flex items-center gap-2">
<Wallet className="h-4 w-4 text-primary" />
<h3 className="text-sm font-semibold">Choose a Wallet</h3>
</div>
</div>
<div className="divide-y divide-border">
{wallets.map((wallet) => (
<button
key={wallet.name}
onClick={() => setSelected(wallet.name)}
className={`flex w-full items-center justify-between px-5 py-3 text-left transition-colors hover:bg-muted ${
selected === wallet.name ? 'bg-primary/5' : ''
}`}
>
<div className="flex items-center gap-3">
<div
className={`flex h-9 w-9 items-center justify-center rounded-lg ${
selected === wallet.name ? 'bg-primary/10 text-primary' : 'bg-muted text-muted-foreground'
}`}
>
{wallet.name[0]}
</div>
<div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium">{wallet.name}</span>
{wallet.recommended && (
<span className="rounded-full bg-emerald-500/10 px-2 py-0.5 text-[10px] font-medium text-emerald-500">
Recommended
</span>
)}
</div>
<div className="text-xs text-muted-foreground">{wallet.type}</div>
</div>
</div>
{selected === wallet.name ? (
<CheckCircle2 className="h-4 w-4 text-primary" />
) : (
<ArrowRight className="h-4 w-4 text-muted-foreground" />
)}
</button>
))}
</div>
</div>
);
}

export default WalletSelector;