Auth Components
Ready-to-use authentication components for sign-in, user menus, and session management
NowStack comes with all the components you need to easily manage authentication. They are located in src/features/auth/.
SignInButton
Client component that displays a Sign in button. It opens the sign-in modal — it sets the modal: "signin" search param and masks the URL to /auth/signin (unmaskOnReload), so the visitor can authenticate without a full-page redirect. See Sign-in & sign-up modals below.
Props: VariantProps<typeof buttonVariants> (accepts button variants like size, variant, etc.)
import { SignInButton } from "@/features/auth/sign-in-button";
export default function Home() {
return (
<div>
<SignInButton size="lg" variant="default" />
</div>
);
}LoggedInButton
Client component that displays the user's avatar with a dropdown menu. When the user clicks on it, they see the user's name/email, an Admin link (admins only), a Theme switcher (Dark / Light / System), and Logout.
Props:
{
user: {
name?: string | null;
email?: string | null;
image?: string | null;
}
}import { LoggedInButton } from "@/features/auth/sign-in-button";
export default function Home() {
const user = await getUser();
if (!user) return null;
return (
<div>
<LoggedInButton user={user} />
</div>
);
}AuthButton (Server Side)
Server component that automatically displays LoggedInButton or SignInButton based on user authentication status.
Props: None
Features:
- Uses
SuspensewithSkeletonfallback - Fetches user server-side
- Ideal for layouts and server pages
import { AuthButton } from "@/features/auth/auth-button";
export default function Header() {
return (
<div>
<AuthButton />
</div>
);
}AuthButtonClient (Client Side)
Client component with the same behavior as AuthButton. It automatically displays LoggedInButton or SignInButton based on the user session.
Props: None
Features:
- Uses the
useSessionhook client-side - Use when you need a client component
import { AuthButtonClient } from "@/features/auth/auth-button-client";
export default function ClientHeader() {
return (
<div>
<AuthButtonClient />
</div>
);
}Logout
Logout is handled via the dropdown menu in LoggedInButton. UserDropdownLogout signs the user out and then does a full reload to the marketing home / (in production, https://trueroyalties.com/) — not to /auth/signin. Because signOut already clears the auth cookie, the server renders the public landing directly, with no auth-page flash.
To avoid the protected /app page briefly throwing "You need to be signed in" while the session tears down, a global LogoutOverlay (backed by a small Zustand store, logout-store.ts, and mounted in __root.tsx above every route error boundary) raises a full-screen curtain the instant Logout is clicked and keeps it up until the reload lands.
Sign-in & sign-up modals
Sign-in and sign-up are also available as intercepted modals so visitors can authenticate without leaving the page they're on.
SignInDialogandSignUpDialog(src/features/auth/) are mounted once, globally, insrc/routes/__root.tsx.- Any
<Link>opens them by setting themodalsearch param and masking the URL:- sign-in →
search: { modal: "signin" },mask={{ to: "/auth/signin" }} - sign-up →
search: { modal: "signup" },mask={{ to: "/auth/signup" }}
- sign-in →
- On reload the mask resolves to the real
/auth/signinor/auth/signuppage. - The switch links swap modal-to-modal: "Sign up" inside the sign-in modal opens the sign-up modal, and vice-versa (
SignInProvidersaccepts avariant="modal"for this). The standalone/auth/signinpage keeps its page-to-page link (variant="page", the default).
SignInButton and the landing "Start free" CTAs use this modal pattern.
Email verification & WelcomeCelebration
After sign-up, email/password accounts land on /auth/verify to enter the emailed 6-digit code. On success, instead of a toast, the page shows a full-screen WelcomeCelebration (src/features/auth/welcome-celebration.tsx): a rainbow canvas-confetti burst plus a large spring-animated "Welcome" (via motion). It holds briefly, fades out, then calls onComplete to navigate into the app.
Props:
{
title?: string; // default "Welcome"
subtitle?: string; // default product tagline
onComplete?: () => void; // fired once, after the fade-out
durationMs?: number; // hold before the fade — default 2600
}OtpPasteButton
Icon-only "paste the code from clipboard" button shared by the sign-in OTP form and the /auth/verify page (src/features/auth/otp-paste-button.tsx). It reads the clipboard, keeps digits only (max 6), and calls onPaste(digits) — the caller fills the boxes and auto-verifies at 6 digits.
import { OtpPasteButton } from "@/features/auth/otp-paste-button";
<OtpPasteButton onPaste={(digits) => setOtp(digits)} />;