diff --git a/apps/cjs-test/app/handler/[...stack]/page.tsx b/apps/cjs-test/app/handler/[...stack]/page.tsx index 4128578f6..1b248eb98 100644 --- a/apps/cjs-test/app/handler/[...stack]/page.tsx +++ b/apps/cjs-test/app/handler/[...stack]/page.tsx @@ -2,7 +2,7 @@ import { StackHandler } from "@stackframe/stack"; const { stackServerApp } = require("../../../stack"); function Handler(props: any) { - return ; + return ; } module.exports = Handler; diff --git a/apps/custom-pages-example/src/app/handler/[...stack]/page.tsx b/apps/custom-pages-example/src/app/handler/[...stack]/page.tsx index c64c9efe9..fd67e8407 100644 --- a/apps/custom-pages-example/src/app/handler/[...stack]/page.tsx +++ b/apps/custom-pages-example/src/app/handler/[...stack]/page.tsx @@ -2,5 +2,5 @@ import { StackHandler } from "@stackframe/stack"; import { stackServerApp } from "src/stack"; export default function Handler(props) { - return ; + return ; } diff --git a/apps/demo/src/app/fullpage-demos/flex-col/page.tsx b/apps/demo/src/app/fullpage-demos/flex-col/page.tsx new file mode 100644 index 000000000..dccddcb2d --- /dev/null +++ b/apps/demo/src/app/fullpage-demos/flex-col/page.tsx @@ -0,0 +1,9 @@ +import { SignIn } from "@stackframe/stack"; + +export default function SimpleDivFullPageDemo() { + return ( +
+ +
+ ); +} diff --git a/apps/demo/src/app/fullpage-demos/simple-div/page.tsx b/apps/demo/src/app/fullpage-demos/simple-div/page.tsx new file mode 100644 index 000000000..8308d0b29 --- /dev/null +++ b/apps/demo/src/app/fullpage-demos/simple-div/page.tsx @@ -0,0 +1,9 @@ +import { SignIn } from "@stackframe/stack"; + +export default function SimpleDivFullPageDemo() { + return ( +
+ +
+ ); +} diff --git a/apps/demo/src/app/handler/[...stack]/page.tsx b/apps/demo/src/app/handler/[...stack]/page.tsx index c64c9efe9..fd67e8407 100644 --- a/apps/demo/src/app/handler/[...stack]/page.tsx +++ b/apps/demo/src/app/handler/[...stack]/page.tsx @@ -2,5 +2,5 @@ import { StackHandler } from "@stackframe/stack"; import { stackServerApp } from "src/stack"; export default function Handler(props) { - return ; + return ; } diff --git a/apps/middleware/src/app/handler/[...stack]/page.tsx b/apps/middleware/src/app/handler/[...stack]/page.tsx index bb1bc2ebc..decc881e8 100644 --- a/apps/middleware/src/app/handler/[...stack]/page.tsx +++ b/apps/middleware/src/app/handler/[...stack]/page.tsx @@ -4,7 +4,7 @@ import { stackServerApp } from "../../../stack"; export default function Handler(props: any) { return (
- +
); } diff --git a/apps/partial-prerendering/src/app/handler/[...stack]/page.tsx b/apps/partial-prerendering/src/app/handler/[...stack]/page.tsx index bb1bc2ebc..decc881e8 100644 --- a/apps/partial-prerendering/src/app/handler/[...stack]/page.tsx +++ b/apps/partial-prerendering/src/app/handler/[...stack]/page.tsx @@ -4,7 +4,7 @@ import { stackServerApp } from "../../../stack"; export default function Handler(props: any) { return (
- +
); } diff --git a/docs/docs/01-getting-started/02-setup.md b/docs/docs/01-getting-started/02-setup.md index 887955877..62260ad16 100644 --- a/docs/docs/01-getting-started/02-setup.md +++ b/docs/docs/01-getting-started/02-setup.md @@ -76,7 +76,7 @@ npm install @stackframe/stack import { stackServerApp } from "@/stack"; export default function Handler(props: any) { - return ; + return ; } ``` diff --git a/packages/init-stack/index.mjs b/packages/init-stack/index.mjs index a402a6df1..cfac88ef2 100644 --- a/packages/init-stack/index.mjs +++ b/packages/init-stack/index.mjs @@ -96,7 +96,7 @@ async function main() { console.log("Writing files..."); await writeFileIfNotExists(envLocalPath, "NEXT_PUBLIC_STACK_PROJECT_ID=\nNEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=\nSTACK_SECRET_SERVER_KEY=\n"); await writeFileIfNotExists(loadingPath, `export default function Loading() {\n${ind}// Stack uses React Suspense, which will render this page while user data is being fetched.\n${ind}// See: https://nextjs.org/docs/app/api-reference/file-conventions/loading\n${ind}return <>;\n}\n`); - await writeFileIfNotExists(handlerPath, `import { StackHandler } from "@stackframe/stack";\nimport { stackServerApp } from "../../../stack";\n\nexport default function Handler(props${handlerFileExtension.includes("ts") ? ": any" : ""}) {\n${ind}return ;\n}\n`); + await writeFileIfNotExists(handlerPath, `import { StackHandler } from "@stackframe/stack";\nimport { stackServerApp } from "../../../stack";\n\nexport default function Handler(props${handlerFileExtension.includes("ts") ? ": any" : ""}) {\n${ind}return ;\n}\n`); await writeFileIfNotExists(stackAppPath, `import "server-only";\n\nimport { StackServerApp } from "@stackframe/stack";\n\nexport const stackServerApp = new StackServerApp({\n${ind}tokenStore: "nextjs-cookie",\n});\n`); await writeFile(layoutPath, updatedLayoutContent); console.log("Files written successfully!"); diff --git a/packages/stack-server/src/app/(main)/handler/[...stack]/page.tsx b/packages/stack-server/src/app/(main)/handler/[...stack]/page.tsx index 431a6b9d5..b91a2d687 100644 --- a/packages/stack-server/src/app/(main)/handler/[...stack]/page.tsx +++ b/packages/stack-server/src/app/(main)/handler/[...stack]/page.tsx @@ -2,5 +2,5 @@ import { StackHandler } from "@stackframe/stack"; import { stackServerApp } from "@/stack"; export default function Handler(props: any) { - return ; -} \ No newline at end of file + return ; +} diff --git a/packages/stack/src/components-page/auth-page.tsx b/packages/stack/src/components-page/auth-page.tsx index 7f746ed3e..741911259 100644 --- a/packages/stack/src/components-page/auth-page.tsx +++ b/packages/stack/src/components-page/auth-page.tsx @@ -3,7 +3,7 @@ import CredentialSignIn from '../components/credential-sign-in'; import SeparatorWithText from '../components/separator-with-text'; import OAuthGroup from '../components/oauth-group'; -import CardFrame from '../components/card-frame'; +import MaybeFullPage from '../components/maybe-full-page'; import { useUser, useStackApp, CredentialSignUp } from '..'; import RedirectMessageCard from '../components/redirect-message-card'; import { Link, Tabs, TabsContent, TabsList, TabsTrigger, Text } from "../components-core"; @@ -21,7 +21,8 @@ export default function AuthPage({ }) { const stackApp = useStackApp(); const user = useUser(); - const project = mockProject || stackApp.useProject(); + const projectFromHook = stackApp.useProject(); + const project = mockProject || projectFromHook; if (user && !mockProject) { return ; @@ -30,7 +31,7 @@ export default function AuthPage({ const enableSeparator = (project.credentialEnabled || project.magicLinkEnabled) && project.oauthProviders.filter(p => p.enabled).length > 0; return ( - +
{type === 'sign-in' ? 'Sign in to your account' : 'Create a new account'} @@ -71,6 +72,6 @@ export default function AuthPage({ ) : project.magicLinkEnabled ? ( ) : null} - + ); } diff --git a/packages/stack/src/components-page/forgot-password.tsx b/packages/stack/src/components-page/forgot-password.tsx index 1d503cfc4..f89adf61b 100644 --- a/packages/stack/src/components-page/forgot-password.tsx +++ b/packages/stack/src/components-page/forgot-password.tsx @@ -1,7 +1,7 @@ 'use client'; import ForgotPasswordElement from "../components/forgot-password"; -import CardFrame from "../components/card-frame"; +import MaybeFullPage from "../components/maybe-full-page"; import { useUser, useStackApp } from ".."; import RedirectMessageCard from "../components/redirect-message-card"; import { useState } from "react"; @@ -22,7 +22,7 @@ export default function ForgotPassword({ fullPage=false }: { fullPage?: boolean } return ( - +
Reset Your Password @@ -33,6 +33,6 @@ export default function ForgotPassword({ fullPage=false }: { fullPage?: boolean
setSent(true)} /> -
+ ); }; diff --git a/packages/stack/src/components-page/oauth-callback.tsx b/packages/stack/src/components-page/oauth-callback.tsx index 2c2f3424d..3915e35fe 100644 --- a/packages/stack/src/components-page/oauth-callback.tsx +++ b/packages/stack/src/components-page/oauth-callback.tsx @@ -5,7 +5,7 @@ import { useStackApp } from ".."; import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises"; import MessageCard from "../components/message-card"; -export default function OAuthCallback () { +export default function OAuthCallback(props: { fullPage?: boolean }) { const app = useStackApp(); const called = useRef(false); const [error, setError] = useState(null); @@ -29,7 +29,7 @@ export default function OAuthCallback () { setTimeout(() => setShowRedirectLink(true), 3000); }, []); - return + return {showRedirectLink ?

If you are not redirected automatically, click here.

: null} {error ?

Something went wrong while processing the OAuth callback:

diff --git a/packages/stack/src/components-page/sign-in.tsx b/packages/stack/src/components-page/sign-in.tsx index 58695f29c..6e32aa6a9 100644 --- a/packages/stack/src/components-page/sign-in.tsx +++ b/packages/stack/src/components-page/sign-in.tsx @@ -1,4 +1,3 @@ -'use client'; import AuthPage from './auth-page'; export default function SignIn({ fullPage=false }: { fullPage?: boolean }) { diff --git a/packages/stack/src/components-page/sign-out.tsx b/packages/stack/src/components-page/sign-out.tsx index 8387d048f..ebcfe3117 100644 --- a/packages/stack/src/components-page/sign-out.tsx +++ b/packages/stack/src/components-page/sign-out.tsx @@ -4,12 +4,12 @@ import { use } from "react"; import { useUser } from ".."; import GoHomeMessageCard from "../components/redirect-message-card"; -export default function Signout() { +export default function SignOut(props: { fullPage?: boolean }) { const user = useUser(); if (user) { use(user.signOut()); } - return ; -} \ No newline at end of file + return ; +} diff --git a/packages/stack/src/components-page/stack-handler.tsx b/packages/stack/src/components-page/stack-handler.tsx index e147def02..98d94d98c 100644 --- a/packages/stack/src/components-page/stack-handler.tsx +++ b/packages/stack/src/components-page/stack-handler.tsx @@ -15,14 +15,22 @@ export default async function StackHandler({ app, params: { stack } = {}, searchParams = {}, + // TODO set default to false like on the other components (may break old code) + fullPage = "deprecated-unset", }: { app: StackServerApp, params?: { stack?: string[] }, searchParams?: Record, + fullPage?: boolean | "deprecated-unset", }) { + if (fullPage === "deprecated-unset") { + console.warn("You are not passing `fullPage` to Stack's Handler. The default behaviour will soon change from `true` to `false`. Please update your Handler component in handler/[...stack]/page.tsx by adding the `fullPage` prop."); + fullPage = true; + } + if (!stack) { return ( - +

Can't use Stack handler at this location. Make sure that the file is in a folder called [...stack].

); @@ -52,40 +60,40 @@ export default async function StackHandler({ case 'signin': { redirectIfNotHandler('signIn'); await redirectIfHasUser(); - return ; + return ; } case 'signup': { redirectIfNotHandler('signUp'); await redirectIfHasUser(); - return ; + return ; } case 'email-verification': { redirectIfNotHandler('emailVerification'); - return ; + return ; } case 'password-reset': { redirectIfNotHandler('passwordReset'); - return ; + return ; } case 'forgot-password': { redirectIfNotHandler('forgotPassword'); - return ; + return ; } case 'signout': { redirectIfNotHandler('signOut'); - return ; + return ; } case 'oauth-callback': { redirectIfNotHandler('oauthCallback'); - return ; + return ; } case 'account-settings': { redirectIfNotHandler('accountSettings'); - return ; + return ; } case 'magic-link-callback': { redirectIfNotHandler('magicLinkCallback'); - return ; + return ; } default: { return notFound(); diff --git a/packages/stack/src/components/card-frame.tsx b/packages/stack/src/components/card-frame.tsx deleted file mode 100644 index 1ddef5d22..000000000 --- a/packages/stack/src/components/card-frame.tsx +++ /dev/null @@ -1,43 +0,0 @@ -'use client'; - -import { Container } from "../components-core"; -import React, { useEffect, useState } from "react"; - -export default function CardFrame({ - children, - fullPage=true -}: { - children: React.ReactNode, - fullPage?: boolean, -}) { - const [hasNoParent, setHasNoParent] = useState(false); - useEffect(() => { - const component = document.getElementById('stack-card-frame'); - setHasNoParent( - !component?.parentElement || - component?.parentElement === document.body || - component?.parentElement === document.documentElement - ); - }, []); - - if (fullPage) { - return ( -
- - {children} - -
- ); - } else { - return children; - } - -} diff --git a/packages/stack/src/components/maybe-full-page.tsx b/packages/stack/src/components/maybe-full-page.tsx new file mode 100644 index 000000000..c01343f82 --- /dev/null +++ b/packages/stack/src/components/maybe-full-page.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { Container } from "../components-core"; +import React, { useEffect, useId } from "react"; + +export default function MaybeFullPage({ + children, + fullPage=true +}: { + children: React.ReactNode, + fullPage?: boolean, +}) { + const uniqueId = useId(); + const id = `stack-card-frame-${uniqueId}`; + + const scriptString = `(([id]) => { + const el = document.getElementById(id); + const offset = el.getBoundingClientRect().top + document.documentElement.scrollTop; + el.style.minHeight = \`calc(100vh - \${offset}px)\`; + })(${JSON.stringify([id])})`; + + useEffect(() => { + // React has a bug where it doesn't run the script on the first CSR render if SSR has been skipped due to suspense + // As a workaround, we run the script in the