diff --git a/app/layout.tsx b/app/layout.tsx index 7958675..9c7bc45 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import Providers from "@/providers/Providers"; +import AppHeader from "@/components/Header/AppHeader"; import "./globals.css"; const geistSans = Geist({ @@ -32,7 +33,14 @@ export default function RootLayout({ - {children} + +
+ +
+ {children} +
+
+
); diff --git a/components/ApiDocsLink.tsx b/components/ApiDocs/ApiDocsLink.tsx similarity index 100% rename from components/ApiDocsLink.tsx rename to components/ApiDocs/ApiDocsLink.tsx diff --git a/components/Header/AppHeader.tsx b/components/Header/AppHeader.tsx new file mode 100644 index 0000000..4c3e591 --- /dev/null +++ b/components/Header/AppHeader.tsx @@ -0,0 +1,14 @@ +import LoginButton from "@/components/Login/LoginButton"; +import { HideToggle } from "@/components/Header/HideToggle"; + +export default function AppHeader() { + return ( +
+

Recoup Admin

+
+ + +
+
+ ); +} diff --git a/components/Header/HideToggle.tsx b/components/Header/HideToggle.tsx new file mode 100644 index 0000000..1e842c0 --- /dev/null +++ b/components/Header/HideToggle.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { useState, useCallback, useRef } from "react"; +import { Eye, EyeOff } from "lucide-react"; +import { useHide } from "@/providers/HideProvider"; + +const BLINK_MS = 150; + +export function HideToggle() { + const { isHidden, toggle } = useHide(); + const [phase, setPhase] = useState<"idle" | "closing" | "opening">("idle"); + const iconRef = useRef(null); + + const handleClick = useCallback(() => { + if (phase !== "idle") return; + + // Phase 1: close the eye + setPhase("closing"); + + setTimeout(() => { + // Phase 2: swap icon while still closed, then open + toggle(); + setPhase("opening"); + + // Wait for next frame so the browser registers scaleY(0) before animating to scaleY(1) + requestAnimationFrame(() => { + requestAnimationFrame(() => { + setPhase("idle"); + }); + }); + }, BLINK_MS); + }, [toggle, phase]); + + const Icon = isHidden ? EyeOff : Eye; + + const scaleY = phase === "closing" || phase === "opening" ? 0 : 1; + + return ( + + ); +} diff --git a/components/Home/AdminDashboard.tsx b/components/Home/AdminDashboard.tsx index 32ac9e8..2fbc5ed 100644 --- a/components/Home/AdminDashboard.tsx +++ b/components/Home/AdminDashboard.tsx @@ -1,5 +1,5 @@ import NavButton from "@/components/Home/NavButton"; -import ApiDocsLink from "@/components/ApiDocsLink"; +import ApiDocsLink from "@/components/ApiDocs/ApiDocsLink"; export default function AdminDashboard() { return ( diff --git a/components/Home/HomePage.tsx b/components/Home/HomePage.tsx index b62c45d..0b36227 100644 --- a/components/Home/HomePage.tsx +++ b/components/Home/HomePage.tsx @@ -1,16 +1,9 @@ -import LoginButton from "@/components/Login/LoginButton"; import HomeContent from "@/components/Home/HomeContent"; export default function HomePage() { return ( -
-
-

Recoup Admin

- -
-
- -
+
+
); } diff --git a/components/Login/LoginButton.tsx b/components/Login/LoginButton.tsx index 0da9404..4bb18e4 100644 --- a/components/Login/LoginButton.tsx +++ b/components/Login/LoginButton.tsx @@ -2,10 +2,12 @@ import { usePrivy } from "@privy-io/react-auth"; import { Button } from "@/components/ui/button"; +import { useDisplayEmail } from "@/lib/hide/useDisplayEmail"; import LoginButtonSkeleton from "./LoginButtonSkeleton"; export default function LoginButton() { const { login, logout, authenticated, ready, user } = usePrivy(); + const displayEmail = useDisplayEmail(user?.email?.address ?? null); if (!ready) { return ; @@ -15,7 +17,7 @@ export default function LoginButton() { return (
- {user?.email?.address ?? "Signed in"} + {displayEmail ?? "Signed in"}