diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index b8713991..30cb21c6 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; import { useTranslations } from "next-intl"; import { motion } from "framer-motion"; +import { memo, useCallback, useMemo } from "react"; function getNavItems(t: ReturnType) { return [ @@ -70,7 +71,7 @@ interface SidebarProps { onMobileOpenChange: (open: boolean) => void; } -function NavLinks({ +const NavLinks = memo(function NavLinks({ pathname, t, onNavigate, @@ -79,7 +80,7 @@ function NavLinks({ t: ReturnType; onNavigate?: () => void; }) { - const navItems = getNavItems(t); + const navItems = useMemo(() => getNavItems(t), [t]); return ( ); -} +}); + +NavLinks.displayName = "NavLinks"; export default function Sidebar({ mobileOpen, @@ -139,6 +142,7 @@ export default function Sidebar({ }: SidebarProps) { const t = useTranslations("sidebar"); const pathname = usePathname(); + const handleNavigate = useCallback(() => onMobileOpenChange(false), [onMobileOpenChange]); // Note: Collapsible logic removed to fix linting as it's not currently used in the UI. @@ -153,7 +157,7 @@ export default function Sidebar({ onMobileOpenChange(false)} + onNavigate={handleNavigate} /> ); diff --git a/frontend/src/components/WalletSelector.tsx b/frontend/src/components/WalletSelector.tsx index 89853a67..6d82a335 100644 --- a/frontend/src/components/WalletSelector.tsx +++ b/frontend/src/components/WalletSelector.tsx @@ -31,6 +31,16 @@ function WalletConnectIcon() { ); } +const ICONS: Record = { + freighter: , + walletconnect: , +}; + +const SUBTITLES: Record = { + freighter: "Browser extension wallet", + walletconnect: "Mobile & desktop wallets", +}; + export default function WalletSelector({ networkPassphrase, onConnected }: WalletSelectorProps) { const t = useTranslations("walletSelector"); const { providers, activeProvider, selectProvider } = useWallet(); @@ -99,16 +109,6 @@ export default function WalletSelector({ networkPassphrase, onConnected }: Walle } } - const ICONS: Record = { - freighter: , - walletconnect: , - }; - - const SUBTITLES: Record = { - freighter: "Browser extension wallet", - walletconnect: "Mobile & desktop wallets", - }; - return (
@@ -130,10 +130,10 @@ export default function WalletSelector({ networkPassphrase, onConnected }: Walle type="button" disabled={isDisabled || connecting !== null} onClick={() => handleSelect(p.id)} - className="group relative flex h-16 w-full items-center gap-4 rounded-2xl border border-[#E8E8E8] bg-white px-5 text-left shadow-sm transition-all hover:border-[var(--pluto-400)] hover:shadow-[0_4px_20px_rgba(74,111,165,0.12)] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-40" + className="group relative flex h-16 w-full items-center gap-4 rounded-2xl border border-[#E8E8E8] bg-white px-5 text-left shadow-sm transition-[border-color,box-shadow,transform] duration-200 hover:-translate-y-px hover:border-[var(--pluto-400)] hover:shadow-[0_6px_22px_rgba(74,111,165,0.12)] active:translate-y-0 active:scale-[0.99] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pluto-300)] disabled:cursor-not-allowed disabled:opacity-40" > {/* Icon */} -
+
{ICONS[p.id] ?? ( @@ -162,7 +162,7 @@ export default function WalletSelector({ networkPassphrase, onConnected }: Walle {/* Arrow */} {!isConnecting && !isDisabled && ( - + )} @@ -194,7 +194,7 @@ export default function WalletSelector({ networkPassphrase, onConnected }: Walle href="https://freighter.app" target="_blank" rel="noopener noreferrer" - className="text-center text-[10px] font-bold uppercase tracking-widest text-[var(--pluto-500)] hover:text-[var(--pluto-700)] transition-colors" + className="text-center text-[10px] font-bold uppercase tracking-widest text-[var(--pluto-500)] transition-colors duration-150 hover:text-[var(--pluto-700)]" > Don't have Freighter? Install it → diff --git a/frontend/src/components/ui/Button.tsx b/frontend/src/components/ui/Button.tsx index 6823c4f1..88f240ac 100644 --- a/frontend/src/components/ui/Button.tsx +++ b/frontend/src/components/ui/Button.tsx @@ -6,7 +6,16 @@ interface ButtonProps extends React.ButtonHTMLAttributes { isLoading?: boolean; } -export const Button = React.forwardRef( +const BASE_CLASSES = + "group relative flex items-center justify-center rounded-xl px-6 font-bold transition-colors duration-200 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:ring-2 focus-visible:ring-mint focus-visible:ring-offset-2 focus-visible:ring-offset-night"; + +const VARIANT_CLASSES: Record, string> = { + primary: "h-12 bg-mint text-black hover:bg-glow", + secondary: + "h-12 border border-white/10 bg-white/5 text-slate-400 hover:border-white/20 hover:text-white", +}; + +const ButtonBase = React.forwardRef( ( { className = "", @@ -18,22 +27,14 @@ export const Button = React.forwardRef( }, ref, ) => { - const baseClasses = - "group relative flex items-center justify-center rounded-xl px-6 font-bold transition-all disabled:cursor-not-allowed disabled:opacity-50 focus-visible:ring-2 focus-visible:ring-mint focus-visible:ring-offset-2 focus-visible:ring-offset-night"; - - // For primary button, height 12 (h-12) was used typically, but let's allow override or set default - const primaryClasses = "h-12 bg-mint text-black hover:bg-glow"; - const secondaryClasses = - "h-12 border border-white/10 bg-white/5 text-slate-400 hover:border-white/20 hover:text-white"; - - const variantClasses = - variant === "primary" ? primaryClasses : secondaryClasses; + const variantClasses = VARIANT_CLASSES[variant]; + const showPrimaryGlow = variant === "primary"; return ( ); }, ); +ButtonBase.displayName = "Button"; + +export const Button = React.memo(ButtonBase); Button.displayName = "Button"; diff --git a/frontend/tests/e2e/checkout.visual.spec.ts b/frontend/tests/e2e/checkout.visual.spec.ts index 91ecad8b..81ad977b 100644 --- a/frontend/tests/e2e/checkout.visual.spec.ts +++ b/frontend/tests/e2e/checkout.visual.spec.ts @@ -16,8 +16,8 @@ test.describe("Checkout Visual Regression", () => { test("checkout layout remains stable across viewports", async ({ page }) => { const checkoutMain = page.locator("main"); await expect(checkoutMain).toBeVisible(); - await expect(page.getByText("Complete Payment")).toBeVisible(); await expect(page.getByText("Styled payment")).toBeVisible(); + await expect(page.locator("code", { hasText: "GRECIPIENTADDRESS" })).toBeVisible(); const noOverflow = await expectNoHorizontalOverflow(page); expect(noOverflow).toBeTruthy();