import React, { Suspense, useEffect } from "react";
import Login from "../../app/components/modals/Login";
import LoginForm from "../../app/components/modals/Login/LoginForm";
/**
* Storybook runs outside Next.js request context; successful "Send link" needs fetch mocked
* because `requestMagicLink` POSTs to `/api/auth/magic-link/request`.
*/
function MagicLinkFetchMock({ children }: { children: React.ReactNode }) {
useEffect(() => {
const orig = globalThis.fetch;
globalThis.fetch = async (
input: RequestInfo | URL,
init?: RequestInit,
): Promise => {
const url =
typeof input === "string"
? input
: input instanceof URL
? input.href
: input.url;
if (url.includes("/api/auth/magic-link/request")) {
return new Response("{}", {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
return orig(input as Request, init);
};
return () => {
globalThis.fetch = orig;
};
}, []);
return <>{children}>;
}
/** Fake marketing page behind the overlay (header “Log in” opens `AuthModalProvider` with this look). */
function FakeMarketingPageBehindOverlay({
children,
}: {
children: React.ReactNode;
}) {
return (
Placeholder page content — the login overlay portals above this and
uses backdrop blur (`blurredYellow`).
{children}
);
}
export default {
title: "Components/Modals/Login",
component: Login,
parameters: {
layout: "fullscreen",
nextjs: {
appDirectory: true,
navigation: {
pathname: "/",
},
},
docs: {
description: {
component:
'**Primary UX:** `AuthModalProvider` opens this as a **popup overlay** on top of the current page — `backdropVariant="blurredYellow"`, `usePortal` (default). **`/login`** is a thin full-page shell: yellow **solid** backdrop, `usePortal={false}`, same `LoginForm` inside.',
},
},
},
decorators: [
(Story: () => React.ReactNode) => (
),
],
tags: ["autodocs"],
};
/** Matches `AuthModalProvider`: blurred backdrop + portal over whatever route the user was on. */
export const HeaderOverlayBlurred = {
name: "Header overlay (blurred — default)",
parameters: {
docs: {
description: {
story:
'Same as **Log in** from the site header: `backdropVariant="blurredYellow"`, `usePortal`, card + “Back to home” below.',
},
},
},
render: () => (
{}}
backdropVariant="blurredYellow"
usePortal
ariaLabelledBy="login-modal-heading"
belowCard={
← Back to home
}
>
Loading…
}>
),
};
/** Matches `app/login/page.tsx`: dedicated route, solid yellow, no portal. */
export const FullPageRouteSolid = {
name: "Full-page route (/login — solid)",
parameters: {
nextjs: {
navigation: { pathname: "/login" },
},
docs: {
description: {
story:
"Verify-email **error** links and bookmarks use `/login` with a **solid** backdrop and `usePortal={false}`.",
},
},
},
decorators: [
(Story: () => React.ReactNode) => (
),
],
render: () => (
{}}
backdropVariant="solid"
usePortal={false}
ariaLabelledBy="login-modal-heading"
belowCard={
← Back to home
}
>
Loading…}>
),
};
export const ModalChromeOnly = {
name: "Modal chrome only (placeholder)",
render: () => (
{}}
backdropVariant="solid"
usePortal={false}
ariaLabelledBy="login-modal-heading"
belowCard={
← Back to home
}
>
Placeholder body — use "Header overlay" or "Full-page
route" for the real flow.
),
};
export const FormOnly = {
name: "Login form (card inset)",
parameters: {
docs: {
description: {
story:
"Form only, for inspecting copy and layout without overlay chrome. In the app it is always wrapped by `Login` (modal) or the `/login` page shell.",
},
},
},
decorators: [
(Story: () => React.ReactNode) => (
),
],
render: () => (
Loading…}>
),
};