Skip to content
Merged
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
20 changes: 1 addition & 19 deletions app/connect/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
Zap,
Globe,
ArrowLeft,
Copy,
Check,
ExternalLink,
AlertCircle,
Expand Down Expand Up @@ -60,7 +59,6 @@ export default function ConnectWalletPage() {
error,
} = useStellar();

const [copied, setCopied] = useState(false);
const [step, setStep] = useState<Step>("intro");
const [showDisconnectConfirm, setShowDisconnectConfirm] = useState(false);
const [errorExpanded, setErrorExpanded] = useState(false);
Expand Down Expand Up @@ -92,13 +90,6 @@ export default function ConnectWalletPage() {
await connect();
};

const handleCopy = async () => {
if (publicKey) {
await navigator.clipboard.writeText(publicKey);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};

const handleDisconnect = () => {
disconnect();
Expand Down Expand Up @@ -494,7 +485,7 @@ export default function ConnectWalletPage() {
<CopyButton
value={publicKey}
label="Copy wallet address"
iconSize={18}
size={18}
className="!p-3 rounded-xl glass hover:bg-white/10 transition-colors shrink-0"
/>
<a
Expand All @@ -509,15 +500,6 @@ export default function ConnectWalletPage() {
</a>
</div>

{copied && (
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-xs text-accent-400 text-center"
>
Address copied to clipboard
</motion.p>
)}
</div>

{/* Fund testnet */}
Expand Down
11 changes: 2 additions & 9 deletions app/escrow/success/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import { useEffect, useState, Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import confetti from "canvas-confetti";
import { CheckCircle2, Copy, Share2, ArrowRight } from "lucide-react";
import { motion } from "framer-motion";
import Confetti from "@/components/ui/confetti";

function SuccessContent() {
const searchParams = useSearchParams();
Expand All @@ -21,14 +21,6 @@ function SuccessContent() {
return;
}

// Trigger confetti animation
void confetti({
particleCount: 150,
spread: 70,
origin: { y: 0.6 },
colors: ["#22c55e", "#3b82f6", "#eab308", "#a855f7"],
});

// Auto redirect logic
const timer = setInterval(() => {
setCountdown((prev) => {
Expand Down Expand Up @@ -78,6 +70,7 @@ function SuccessContent() {

return (
<main aria-label="Escrow Creation Success" className="flex min-h-[80vh] items-center justify-center p-4">
<Confetti />
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
Expand Down
13 changes: 8 additions & 5 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "@/lib/env";
import { AppShell } from "@/components/ui/app-shell";
import { PageTransition } from "@/components/ui/page-transition";
import { StellarProvider } from "@/context/StellarContext";
import { ToastProvider } from "@/components/ui/toast-provider";
import "./globals.css";

const inter = Inter({
Expand Down Expand Up @@ -56,11 +57,13 @@ export default function RootLayout({
<body
className={`${inter.variable} ${spaceGrotesk.variable} ${jetBrainsMono.variable} font-sans`}
>
<StellarProvider>
<AppShell>
<PageTransition>{children}</PageTransition>
</AppShell>
</StellarProvider>
<ToastProvider>
<StellarProvider>
<AppShell>
<PageTransition>{children}</PageTransition>
</AppShell>
</StellarProvider>
</ToastProvider>
</body>
</html>
);
Expand Down
13 changes: 5 additions & 8 deletions components/ui/app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import React, { useState, useEffect } from "react";
import { ThemeProvider } from "@/components/ui/theme-provider";
import { DottedSurface } from "@/components/ui/dotted-surface";
import { useStellar } from "@/context/StellarContext";
import { ToastProvider } from "@/components/ui/toast-provider";
import { SkipLink } from "@/components/ui/skip-link";
import { ErrorBoundary } from "@/components/ui/error-boundary";
import OfflineBanner from "./offline-banner";
Expand Down Expand Up @@ -57,13 +56,11 @@ export function AppShell({ children }: { children: React.ReactNode }) {
)}
<div className="mesh-gradient" aria-hidden="true" />
<SkipLink />
<ToastProvider>
<ErrorBoundary>
<OfflineBanner />
<AccountChangedBanner />
<div className="relative z-10">{children}</div>
</ErrorBoundary>
</ToastProvider>
<ErrorBoundary>
<OfflineBanner />
<AccountChangedBanner />
<div className="relative z-10">{children}</div>
</ErrorBoundary>
</ThemeProvider>
);
}
Expand Down
39 changes: 39 additions & 0 deletions components/ui/confetti.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";

import { useEffect } from "react";
import confetti from "canvas-confetti";

interface ConfettiProps {
/**
* Optional delay before the confetti starts (in ms)
*/
delay?: number;
}

/**
* Confetti component that triggers a burst animation on mount.
* Respects 'prefers-reduced-motion' accessibility setting.
*/
export default function Confetti({ delay = 0 }: ConfettiProps) {
useEffect(() => {
// Skip if user prefers reduced motion
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (prefersReducedMotion) return;

const colors = ["#5c7cfa", "#20c997", "#748ffc"];

const timer = setTimeout(() => {
void confetti({
particleCount: 150,
spread: 70,
origin: { y: 0.6 },
colors: colors,
disableForReducedMotion: true,
});
}, delay);

return () => clearTimeout(timer);
}, [delay]);

return null;
}
8 changes: 4 additions & 4 deletions components/ui/copy-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ interface CopyButtonProps {
value: string;
label?: string;
className?: string;
iconSize?: number;
size?: number;
}

export default function CopyButton({
value,
label = "Copy to clipboard",
className = "",
iconSize = 16,
size = 16,
}: CopyButtonProps) {
const [copied, setCopied] = useState(false);
const timeoutRef = useRef<number | null>(null);
Expand Down Expand Up @@ -62,7 +62,7 @@ export default function CopyButton({
exit={{ opacity: 0, scale: 0.5, rotate: 45 }}
transition={{ duration: 0.15 }}
>
<Check size={iconSize} className="text-accent-400" />
<Check size={size} className="text-accent-400" />
</motion.div>
) : (
<motion.div
Expand All @@ -72,7 +72,7 @@ export default function CopyButton({
exit={{ opacity: 0, scale: 0.5, rotate: -45 }}
transition={{ duration: 0.15 }}
>
<Copy size={iconSize} />
<Copy size={size} />
</motion.div>
)}
</AnimatePresence>
Expand Down
2 changes: 1 addition & 1 deletion components/wallet/ConnectWalletButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import React, { useState, useRef, useEffect } from "react";
import { useRouter } from "next/navigation";
import { motion, AnimatePresence } from "framer-motion";
import { Wallet, LogOut, Copy, Check, ChevronDown, ExternalLink, AlertCircle, Loader2, Coins, AlertTriangle, Maximize2, Minimize2 } from "lucide-react";
import { Wallet, LogOut, Copy, ChevronDown, ExternalLink, AlertCircle, Loader2, Coins, AlertTriangle, Maximize2, Minimize2 } from "lucide-react";
import { useStellarAuth } from "@/context/StellarContext";
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import { BottomSheet } from "@/components/ui/bottom-sheet";
Expand Down
4 changes: 4 additions & 0 deletions context/StellarContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
checkConnection
} from "@/lib/stellar/wallet";
import { getWalletError, type WalletError } from "@/lib/stellar/errors";
import { useToast } from "@/hooks/useToast";

interface StellarContextType {
publicKey: string | null;
Expand Down Expand Up @@ -35,6 +36,7 @@ export function StellarProvider({ children }: { children: React.ReactNode }) {
const [error, setError] = useState<WalletError | null>(null);
const [hasAccountChanged, setHasAccountChanged] = useState(false);
const [announcement, setAnnouncement] = useState<string | null>(null);
const toast = useToast();

const announce = useCallback((message: string) => {
setAnnouncement(message);
Expand Down Expand Up @@ -109,6 +111,8 @@ export function StellarProvider({ children }: { children: React.ReactNode }) {
if (key) {
setPublicKey(key);
setIsConnected(true);
const truncatedKey = `${key.slice(0, 4)}...${key.slice(-4)}`;
toast.success("Wallet connected — " + truncatedKey);
announce("Wallet connected successfully.");
} else {
throw new Error("User rejected connection or failed to retrieve public key.");
Expand Down