From 3ffecd37e01d1241c31e3c7db00cccc5495fd146 Mon Sep 17 00:00:00 2001 From: Shreyas Thakur Date: Mon, 13 Apr 2026 04:05:35 +0530 Subject: [PATCH 1/3] feat: add Try Now button + card tokenization demo modal - Add 'Try Now' button to navigation (desktop + mobile) - New TryNowModal component with real web2 card validation (Luhn, expiry, CVV) - Live network detection (Visa/Mastercard/RuPay/Amex) as user types - AES-256-GCM encryption using Web Crypto API (no wallet required) - Simulated on-chain TX with dummy hash for demo purposes - Animated 5-step progress flow and success card view with copyable token ID --- src/components/TryNowModal.tsx | 603 +++++++++++++++++++++++++++++++++ src/components/navigation.tsx | 22 +- 2 files changed, 621 insertions(+), 4 deletions(-) create mode 100644 src/components/TryNowModal.tsx diff --git a/src/components/TryNowModal.tsx b/src/components/TryNowModal.tsx new file mode 100644 index 0000000..ee2d36b --- /dev/null +++ b/src/components/TryNowModal.tsx @@ -0,0 +1,603 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { + X, + CreditCard, + KeyRound, + Loader2, + AlertCircle, + CheckCircle2, + Lock, + Zap, + Copy, + Check, +} from "lucide-react"; +import { CardTokenizer, type CardData } from "@/lib/tychee-client"; + +/* ───────────────────────────────────────────────────────── + Types +───────────────────────────────────────────────────────── */ + +interface CardFormData { + cardNumber: string; + expiry: string; + cvv: string; + cardholderName: string; +} + +interface TokenResult { + tokenId: string; + maskedPan: string; + network: string; + last4: string; + expiry: string; + demoTxHash: string; + encryptedSize: number; +} + +interface TryNowModalProps { + onClose: () => void; +} + +/* ───────────────────────────────────────────────────────── + Helpers +───────────────────────────────────────────────────────── */ + +/** Generate a pseudo-random 32-byte demo encryption key (no wallet needed) */ +async function getDemoKey(): Promise { + const seed = new TextEncoder().encode("tychee:demo:v1:no-wallet-required"); + const buf = await crypto.subtle.digest("SHA-256", seed); + return new Uint8Array(buf); +} + +/** Simulate a short on-chain write with a fake tx hash */ +async function simulateOnChainStore(tokenHash: string): Promise { + // Artificial latency to mimic a real blockchain round-trip + await new Promise((r) => setTimeout(r, 1_400)); + // Deterministic but visually plausible dummy hash + const raw = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(`demo:${tokenHash}:${Date.now()}`) + ); + const hex = Array.from(new Uint8Array(raw)) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + return `0x${hex}`; +} + +function formatCardNumber(value: string): string { + const v = value.replace(/\s+/g, "").replace(/[^0-9]/gi, ""); + const parts: string[] = []; + for (let i = 0; i < v.length; i += 4) parts.push(v.substring(i, i + 4)); + return parts.length ? parts.join(" ") : value; +} + +function formatExpiry(value: string): string { + const v = value.replace(/\s+/g, "").replace(/[^0-9]/gi, ""); + return v.length >= 2 ? v.substring(0, 2) + "/" + v.substring(2, 4) : v; +} + +const NETWORK_GRADIENT: Record = { + visa: "from-blue-600 to-blue-800", + mastercard: "from-orange-500 to-red-600", + rupay: "from-green-500 to-teal-600", + amex: "from-gray-600 to-gray-800", + unknown: "from-primary to-accent", +}; + +const STEPS = [ + "Validating card details…", + "Detecting card network…", + "Encrypting with AES-256-GCM…", + "Submitting token on-chain…", + "Token confirmed ✓", +]; + +/* ───────────────────────────────────────────────────────── + Component +───────────────────────────────────────────────────────── */ + +export function TryNowModal({ onClose }: TryNowModalProps) { + const [form, setForm] = useState({ + cardNumber: "", + expiry: "", + cvv: "", + cardholderName: "", + }); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [result, setResult] = useState(null); + const [copied, setCopied] = useState(false); + + /* Close on Escape */ + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === "Escape" && !isLoading) onClose(); + }, + [isLoading, onClose] + ); + useEffect(() => { + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, [handleKeyDown]); + + /* Field helpers */ + const handleChange = (field: keyof CardFormData, raw: string) => { + let v = raw; + if (field === "cardNumber") v = formatCardNumber(raw); + else if (field === "expiry") v = formatExpiry(raw); + else if (field === "cvv") v = raw.replace(/[^0-9]/g, "").substring(0, 4); + else if (field === "cardholderName") v = raw.toUpperCase(); + setForm((p) => ({ ...p, [field]: v })); + }; + + /* Live network detection */ + const cleaned = form.cardNumber.replace(/\s/g, ""); + const liveNetwork = cleaned.length > 0 ? CardTokenizer.detectCardNetwork(cleaned) : null; + const isLuhnValid = cleaned.length >= 13 ? CardTokenizer.validateCardNumber(cleaned) : null; + + /* ── Main tokenization handler ── */ + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + + const pan = form.cardNumber.replace(/\s/g, ""); + + // ── Web2 validation (real logic, unchanged) ── + if (!CardTokenizer.validateCardNumber(pan)) { + setError("Invalid card number — failed Luhn check."); + return; + } + const [month, year] = form.expiry.split("/"); + if (!month || !year || +month < 1 || +month > 12) { + setError("Invalid expiry date."); + return; + } + // Check card is not expired + const expYear = 2000 + parseInt(year, 10); + const expMonth = parseInt(month, 10); + const now = new Date(); + if (expYear < now.getFullYear() || (expYear === now.getFullYear() && expMonth < now.getMonth() + 1)) { + setError("This card has expired."); + return; + } + if (form.cvv.length < 3) { + setError("CVV must be at least 3 digits."); + return; + } + if (form.cardholderName.trim().length < 2) { + setError("Please enter the cardholder name."); + return; + } + + setIsLoading(true); + + try { + // Step 1 + setCurrentStep(0); + await new Promise((r) => setTimeout(r, 600)); + + // Step 2 — Detect network + setCurrentStep(1); + const network = CardTokenizer.detectCardNetwork(pan) as CardData["network"]; + await new Promise((r) => setTimeout(r, 400)); + + // Step 3 — AES-256-GCM encryption (real, just no wallet key) + setCurrentStep(2); + const demoKey = await getDemoKey(); + const cardData: CardData = { + pan, + cvv: form.cvv, + expiryMonth: month, + expiryYear: year, + cardholderName: form.cardholderName, + network, + }; + const { encryptedPayload, tokenHash, last4Digits } = + await CardTokenizer.encryptCard(cardData, demoKey); + await new Promise((r) => setTimeout(r, 200)); + + // Step 4 — Simulated on-chain write (dummy tx hash, no wallet) + setCurrentStep(3); + const demoTxHash = await simulateOnChainStore(tokenHash); + + // Step 5 — Done + setCurrentStep(4); + await new Promise((r) => setTimeout(r, 400)); + + setResult({ + tokenId: tokenHash.substring(0, 16), + maskedPan: CardTokenizer.maskCardNumber(pan), + network, + last4: last4Digits, + expiry: form.expiry, + demoTxHash, + encryptedSize: encryptedPayload.length, + }); + } catch (err: any) { + setError(err.message || "Tokenization failed."); + } finally { + setIsLoading(false); + } + }; + + const copyToken = async () => { + if (!result) return; + await navigator.clipboard.writeText(result.tokenId); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + const reset = () => { + setResult(null); + setForm({ cardNumber: "", expiry: "", cvv: "", cardholderName: "" }); + setError(null); + setCurrentStep(0); + }; + + /* ── Render ── */ + return ( +
{ + if (e.target === e.currentTarget && !isLoading) onClose(); + }} + > + {/* Backdrop */} +
+ + {/* Panel */} +
+ {/* Glow ring */} +
+ +
+ {/* ── Header ── */} +
+
+
+ +
+
+

+ Live Card Demo +

+

+ Try real tokenization — no wallet required +

+
+
+ + {!isLoading && ( + + )} +
+ + {/* ── Body ── */} +
+ {/* Success state */} + {result ? ( + + ) : ( + <> + {/* Error */} + {error && ( +
+ + {error} +
+ )} + + {/* Loading steps */} + {isLoading && ( + + )} + + {/* Form */} + {!isLoading && ( +
+ {/* Card number */} +
+ +
+ + handleChange("cardNumber", e.target.value) + } + placeholder="1234 5678 9012 3456" + className="w-full px-4 py-3 pr-28 bg-white/5 border border-white/10 rounded-xl text-sm text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all" + maxLength={19} + required + autoComplete="cc-number" + /> + {liveNetwork && ( +
+ + {liveNetwork} + + {isLuhnValid === true && ( + + )} + {isLuhnValid === false && ( + + )} +
+ )} +
+
+ + {/* Expiry + CVV */} +
+
+ + + handleChange("expiry", e.target.value) + } + placeholder="MM/YY" + className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all" + maxLength={5} + required + autoComplete="cc-exp" + /> +
+
+ + + handleChange("cvv", e.target.value) + } + placeholder="•••" + className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all" + maxLength={4} + required + autoComplete="cc-csc" + /> +
+
+ + {/* Cardholder name */} +
+ + + handleChange("cardholderName", e.target.value) + } + placeholder="JOHN DOE" + className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-sm text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all" + required + autoComplete="cc-name" + /> +
+ + {/* Submit */} + + + {/* Footer note */} +

+ Card data is encrypted in-browser with AES-256-GCM and never sent + to any server. Blockchain TX is simulated. +

+
+ )} + + )} +
+
+
+
+ ); +} + +/* ───────────────────────────────────────────────────────── + Sub-components +───────────────────────────────────────────────────────── */ + +function StepsProgress({ steps, current }: { steps: string[]; current: number }) { + return ( +
+ {steps.map((label, i) => { + const done = i < current; + const active = i === current; + return ( +
+
+ {done ? ( + + ) : active ? ( + + ) : ( + + )} +
+ + {label} + +
+ ); + })} +
+ ); +} + +function SuccessView({ + result, + copied, + onCopy, + onReset, + onClose, +}: { + result: TokenResult; + copied: boolean; + onCopy: () => void; + onReset: () => void; + onClose: () => void; +}) { + const gradient = + NETWORK_GRADIENT[result.network] ?? NETWORK_GRADIENT.unknown; + + return ( +
+ {/* Success badge */} +
+ + + Card successfully tokenized! + +
+ + {/* Mini card visual */} +
+ {/* Subtle pattern */} +
+
+
+
+ + {result.encryptedSize}B encrypted +
+ + {result.network} + +
+
TOKENIZED CARD
+
+ •••• •••• •••• {result.last4} +
+
+
+
Expires
+
{result.expiry}
+
+ +
+
+
+ + {/* Token ID row */} +
+
+
+
+ Token ID +
+
+ {result.tokenId} +
+
+ +
+ + {/* Dummy TX hash */} +
+
+ Simulated TX Hash +
+
+ {result.demoTxHash.substring(0, 42)}… +
+
+
+ + {/* Disclaimer */} +

+ This is a demo. The token is not + stored anywhere. Connect your wallet to tokenize & store cards on-chain. +

+ + {/* Actions */} +
+ + +
+
+ ); +} diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index 92b464a..82862bd 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -3,9 +3,10 @@ import { useState } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { CreditCard, TrendingUp, Gift, Store, Ticket, Users, Wallet, LogOut, Menu, X } from "lucide-react"; +import { CreditCard, TrendingUp, Gift, Store, Ticket, Users, Wallet, LogOut, Menu, X, Zap } from "lucide-react"; import { cn } from "@/lib/utils"; import { useWallet } from "@/context/WalletContext"; +import { TryNowModal } from "@/components/TryNowModal"; const navigation = [ { name: "Cards", href: "/cards", icon: CreditCard }, @@ -20,12 +21,13 @@ export function Navigation() { const pathname = usePathname(); const { publicKey, isConnected, isConnecting, connect, disconnect } = useWallet(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + const [tryNowOpen, setTryNowOpen] = useState(false); const truncateAddress = (address: string) => { return `${address.slice(0, 4)}...${address.slice(-4)}`; }; - return ( + return (<> - ); + + {/* Try Now Modal — portal-style, rendered outside
{/* Wallet Connection Warning */} - {!isConnected && ( + {!isConnected && !isSimulationMode && (
@@ -351,7 +381,7 @@ export default function CardsPage() { )} {/* On-chain config warning */} - {isConnected && !isOnChainConfigured() && ( + {isConnected && !isOnChainConfigured() && !isSimulationMode && (
@@ -468,12 +498,12 @@ export default function CardsPage() { {/* Add New Card Placeholder */}
diff --git a/src/components/TryNowModal.tsx b/src/components/TryNowModal.tsx deleted file mode 100644 index ee2d36b..0000000 --- a/src/components/TryNowModal.tsx +++ /dev/null @@ -1,603 +0,0 @@ -"use client"; - -import { useState, useEffect, useCallback } from "react"; -import { - X, - CreditCard, - KeyRound, - Loader2, - AlertCircle, - CheckCircle2, - Lock, - Zap, - Copy, - Check, -} from "lucide-react"; -import { CardTokenizer, type CardData } from "@/lib/tychee-client"; - -/* ───────────────────────────────────────────────────────── - Types -───────────────────────────────────────────────────────── */ - -interface CardFormData { - cardNumber: string; - expiry: string; - cvv: string; - cardholderName: string; -} - -interface TokenResult { - tokenId: string; - maskedPan: string; - network: string; - last4: string; - expiry: string; - demoTxHash: string; - encryptedSize: number; -} - -interface TryNowModalProps { - onClose: () => void; -} - -/* ───────────────────────────────────────────────────────── - Helpers -───────────────────────────────────────────────────────── */ - -/** Generate a pseudo-random 32-byte demo encryption key (no wallet needed) */ -async function getDemoKey(): Promise { - const seed = new TextEncoder().encode("tychee:demo:v1:no-wallet-required"); - const buf = await crypto.subtle.digest("SHA-256", seed); - return new Uint8Array(buf); -} - -/** Simulate a short on-chain write with a fake tx hash */ -async function simulateOnChainStore(tokenHash: string): Promise { - // Artificial latency to mimic a real blockchain round-trip - await new Promise((r) => setTimeout(r, 1_400)); - // Deterministic but visually plausible dummy hash - const raw = await crypto.subtle.digest( - "SHA-256", - new TextEncoder().encode(`demo:${tokenHash}:${Date.now()}`) - ); - const hex = Array.from(new Uint8Array(raw)) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); - return `0x${hex}`; -} - -function formatCardNumber(value: string): string { - const v = value.replace(/\s+/g, "").replace(/[^0-9]/gi, ""); - const parts: string[] = []; - for (let i = 0; i < v.length; i += 4) parts.push(v.substring(i, i + 4)); - return parts.length ? parts.join(" ") : value; -} - -function formatExpiry(value: string): string { - const v = value.replace(/\s+/g, "").replace(/[^0-9]/gi, ""); - return v.length >= 2 ? v.substring(0, 2) + "/" + v.substring(2, 4) : v; -} - -const NETWORK_GRADIENT: Record = { - visa: "from-blue-600 to-blue-800", - mastercard: "from-orange-500 to-red-600", - rupay: "from-green-500 to-teal-600", - amex: "from-gray-600 to-gray-800", - unknown: "from-primary to-accent", -}; - -const STEPS = [ - "Validating card details…", - "Detecting card network…", - "Encrypting with AES-256-GCM…", - "Submitting token on-chain…", - "Token confirmed ✓", -]; - -/* ───────────────────────────────────────────────────────── - Component -───────────────────────────────────────────────────────── */ - -export function TryNowModal({ onClose }: TryNowModalProps) { - const [form, setForm] = useState({ - cardNumber: "", - expiry: "", - cvv: "", - cardholderName: "", - }); - const [error, setError] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [currentStep, setCurrentStep] = useState(0); - const [result, setResult] = useState(null); - const [copied, setCopied] = useState(false); - - /* Close on Escape */ - const handleKeyDown = useCallback( - (e: KeyboardEvent) => { - if (e.key === "Escape" && !isLoading) onClose(); - }, - [isLoading, onClose] - ); - useEffect(() => { - document.addEventListener("keydown", handleKeyDown); - return () => document.removeEventListener("keydown", handleKeyDown); - }, [handleKeyDown]); - - /* Field helpers */ - const handleChange = (field: keyof CardFormData, raw: string) => { - let v = raw; - if (field === "cardNumber") v = formatCardNumber(raw); - else if (field === "expiry") v = formatExpiry(raw); - else if (field === "cvv") v = raw.replace(/[^0-9]/g, "").substring(0, 4); - else if (field === "cardholderName") v = raw.toUpperCase(); - setForm((p) => ({ ...p, [field]: v })); - }; - - /* Live network detection */ - const cleaned = form.cardNumber.replace(/\s/g, ""); - const liveNetwork = cleaned.length > 0 ? CardTokenizer.detectCardNetwork(cleaned) : null; - const isLuhnValid = cleaned.length >= 13 ? CardTokenizer.validateCardNumber(cleaned) : null; - - /* ── Main tokenization handler ── */ - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(null); - - const pan = form.cardNumber.replace(/\s/g, ""); - - // ── Web2 validation (real logic, unchanged) ── - if (!CardTokenizer.validateCardNumber(pan)) { - setError("Invalid card number — failed Luhn check."); - return; - } - const [month, year] = form.expiry.split("/"); - if (!month || !year || +month < 1 || +month > 12) { - setError("Invalid expiry date."); - return; - } - // Check card is not expired - const expYear = 2000 + parseInt(year, 10); - const expMonth = parseInt(month, 10); - const now = new Date(); - if (expYear < now.getFullYear() || (expYear === now.getFullYear() && expMonth < now.getMonth() + 1)) { - setError("This card has expired."); - return; - } - if (form.cvv.length < 3) { - setError("CVV must be at least 3 digits."); - return; - } - if (form.cardholderName.trim().length < 2) { - setError("Please enter the cardholder name."); - return; - } - - setIsLoading(true); - - try { - // Step 1 - setCurrentStep(0); - await new Promise((r) => setTimeout(r, 600)); - - // Step 2 — Detect network - setCurrentStep(1); - const network = CardTokenizer.detectCardNetwork(pan) as CardData["network"]; - await new Promise((r) => setTimeout(r, 400)); - - // Step 3 — AES-256-GCM encryption (real, just no wallet key) - setCurrentStep(2); - const demoKey = await getDemoKey(); - const cardData: CardData = { - pan, - cvv: form.cvv, - expiryMonth: month, - expiryYear: year, - cardholderName: form.cardholderName, - network, - }; - const { encryptedPayload, tokenHash, last4Digits } = - await CardTokenizer.encryptCard(cardData, demoKey); - await new Promise((r) => setTimeout(r, 200)); - - // Step 4 — Simulated on-chain write (dummy tx hash, no wallet) - setCurrentStep(3); - const demoTxHash = await simulateOnChainStore(tokenHash); - - // Step 5 — Done - setCurrentStep(4); - await new Promise((r) => setTimeout(r, 400)); - - setResult({ - tokenId: tokenHash.substring(0, 16), - maskedPan: CardTokenizer.maskCardNumber(pan), - network, - last4: last4Digits, - expiry: form.expiry, - demoTxHash, - encryptedSize: encryptedPayload.length, - }); - } catch (err: any) { - setError(err.message || "Tokenization failed."); - } finally { - setIsLoading(false); - } - }; - - const copyToken = async () => { - if (!result) return; - await navigator.clipboard.writeText(result.tokenId); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; - - const reset = () => { - setResult(null); - setForm({ cardNumber: "", expiry: "", cvv: "", cardholderName: "" }); - setError(null); - setCurrentStep(0); - }; - - /* ── Render ── */ - return ( -
{ - if (e.target === e.currentTarget && !isLoading) onClose(); - }} - > - {/* Backdrop */} -
- - {/* Panel */} -
- {/* Glow ring */} -
- -
- {/* ── Header ── */} -
-
-
- -
-
-

- Live Card Demo -

-

- Try real tokenization — no wallet required -

-
-
- - {!isLoading && ( - - )} -
- - {/* ── Body ── */} -
- {/* Success state */} - {result ? ( - - ) : ( - <> - {/* Error */} - {error && ( -
- - {error} -
- )} - - {/* Loading steps */} - {isLoading && ( - - )} - - {/* Form */} - {!isLoading && ( -
- {/* Card number */} -
- -
- - handleChange("cardNumber", e.target.value) - } - placeholder="1234 5678 9012 3456" - className="w-full px-4 py-3 pr-28 bg-white/5 border border-white/10 rounded-xl text-sm text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all" - maxLength={19} - required - autoComplete="cc-number" - /> - {liveNetwork && ( -
- - {liveNetwork} - - {isLuhnValid === true && ( - - )} - {isLuhnValid === false && ( - - )} -
- )} -
-
- - {/* Expiry + CVV */} -
-
- - - handleChange("expiry", e.target.value) - } - placeholder="MM/YY" - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all" - maxLength={5} - required - autoComplete="cc-exp" - /> -
-
- - - handleChange("cvv", e.target.value) - } - placeholder="•••" - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all" - maxLength={4} - required - autoComplete="cc-csc" - /> -
-
- - {/* Cardholder name */} -
- - - handleChange("cardholderName", e.target.value) - } - placeholder="JOHN DOE" - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-sm text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all" - required - autoComplete="cc-name" - /> -
- - {/* Submit */} - - - {/* Footer note */} -

- Card data is encrypted in-browser with AES-256-GCM and never sent - to any server. Blockchain TX is simulated. -

-
- )} - - )} -
-
-
-
- ); -} - -/* ───────────────────────────────────────────────────────── - Sub-components -───────────────────────────────────────────────────────── */ - -function StepsProgress({ steps, current }: { steps: string[]; current: number }) { - return ( -
- {steps.map((label, i) => { - const done = i < current; - const active = i === current; - return ( -
-
- {done ? ( - - ) : active ? ( - - ) : ( - - )} -
- - {label} - -
- ); - })} -
- ); -} - -function SuccessView({ - result, - copied, - onCopy, - onReset, - onClose, -}: { - result: TokenResult; - copied: boolean; - onCopy: () => void; - onReset: () => void; - onClose: () => void; -}) { - const gradient = - NETWORK_GRADIENT[result.network] ?? NETWORK_GRADIENT.unknown; - - return ( -
- {/* Success badge */} -
- - - Card successfully tokenized! - -
- - {/* Mini card visual */} -
- {/* Subtle pattern */} -
-
-
-
- - {result.encryptedSize}B encrypted -
- - {result.network} - -
-
TOKENIZED CARD
-
- •••• •••• •••• {result.last4} -
-
-
-
Expires
-
{result.expiry}
-
- -
-
-
- - {/* Token ID row */} -
-
-
-
- Token ID -
-
- {result.tokenId} -
-
- -
- - {/* Dummy TX hash */} -
-
- Simulated TX Hash -
-
- {result.demoTxHash.substring(0, 42)}… -
-
-
- - {/* Disclaimer */} -

- This is a demo. The token is not - stored anywhere. Connect your wallet to tokenize & store cards on-chain. -

- - {/* Actions */} -
- - -
-
- ); -} diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index 82862bd..5cec32c 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -6,7 +6,6 @@ import { usePathname } from "next/navigation"; import { CreditCard, TrendingUp, Gift, Store, Ticket, Users, Wallet, LogOut, Menu, X, Zap } from "lucide-react"; import { cn } from "@/lib/utils"; import { useWallet } from "@/context/WalletContext"; -import { TryNowModal } from "@/components/TryNowModal"; const navigation = [ { name: "Cards", href: "/cards", icon: CreditCard }, @@ -19,9 +18,8 @@ const navigation = [ export function Navigation() { const pathname = usePathname(); - const { publicKey, isConnected, isConnecting, connect, disconnect } = useWallet(); + const { publicKey, isConnected, isConnecting, connect, disconnect, isSimulationMode, toggleSimulationMode } = useWallet(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - const [tryNowOpen, setTryNowOpen] = useState(false); const truncateAddress = (address: string) => { return `${address.slice(0, 4)}...${address.slice(-4)}`; @@ -71,18 +69,33 @@ export function Navigation() { })}
- {/* Right side: Try Now + wallet + mobile menu button */} + {/* Right side: Simulation Mode + wallet + mobile menu button */}
- {/* Try Now — always visible */} + {/* Simulation Mode button */} - {isConnected ? ( + {isSimulationMode ? ( +
+ + + + + Simulation Active +
+ ) : isConnected ? (
{truncateAddress(publicKey!)} @@ -144,8 +157,5 @@ export function Navigation() { )}
- - {/* Try Now Modal — portal-style, rendered outside