mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
parent
207d85f5d3
commit
9423a2dc1a
@ -2,7 +2,7 @@ import { StackHandler } from "@stackframe/stack";
|
||||
const { stackServerApp } = require("../../../stack");
|
||||
|
||||
function Handler(props: any) {
|
||||
return <StackHandler app={stackServerApp} {...props} />;
|
||||
return <StackHandler fullPage app={stackServerApp} {...props} />;
|
||||
}
|
||||
|
||||
module.exports = Handler;
|
||||
|
||||
@ -2,5 +2,5 @@ import { StackHandler } from "@stackframe/stack";
|
||||
import { stackServerApp } from "src/stack";
|
||||
|
||||
export default function Handler(props) {
|
||||
return <StackHandler app={stackServerApp} {...props} />;
|
||||
return <StackHandler fullPage app={stackServerApp} {...props} />;
|
||||
}
|
||||
|
||||
9
apps/demo/src/app/fullpage-demos/flex-col/page.tsx
Normal file
9
apps/demo/src/app/fullpage-demos/flex-col/page.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { SignIn } from "@stackframe/stack";
|
||||
|
||||
export default function SimpleDivFullPageDemo() {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
|
||||
<SignIn fullPage />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
apps/demo/src/app/fullpage-demos/simple-div/page.tsx
Normal file
9
apps/demo/src/app/fullpage-demos/simple-div/page.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { SignIn } from "@stackframe/stack";
|
||||
|
||||
export default function SimpleDivFullPageDemo() {
|
||||
return (
|
||||
<div>
|
||||
<SignIn fullPage />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -2,5 +2,5 @@ import { StackHandler } from "@stackframe/stack";
|
||||
import { stackServerApp } from "src/stack";
|
||||
|
||||
export default function Handler(props) {
|
||||
return <StackHandler app={stackServerApp} {...props} />;
|
||||
return <StackHandler fullPage app={stackServerApp} {...props} />;
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import { stackServerApp } from "../../../stack";
|
||||
export default function Handler(props: any) {
|
||||
return (
|
||||
<div style={{ backgroundColor: "white", borderRadius: 4 }}>
|
||||
<StackHandler app={stackServerApp} {...props} />
|
||||
<StackHandler fullPage app={stackServerApp} {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import { stackServerApp } from "../../../stack";
|
||||
export default function Handler(props: any) {
|
||||
return (
|
||||
<div style={{ backgroundColor: "white", borderRadius: 4 }}>
|
||||
<StackHandler app={stackServerApp} {...props} />
|
||||
<StackHandler fullPage app={stackServerApp} {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ npm install @stackframe/stack
|
||||
import { stackServerApp } from "@/stack";
|
||||
|
||||
export default function Handler(props: any) {
|
||||
return <StackHandler app={stackServerApp} {...props} />;
|
||||
return <StackHandler fullPage app={stackServerApp} {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -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 <StackHandler app={stackServerApp} {...props} />;\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 <StackHandler fullPage app={stackServerApp} {...props} />;\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!");
|
||||
|
||||
@ -2,5 +2,5 @@ import { StackHandler } from "@stackframe/stack";
|
||||
import { stackServerApp } from "@/stack";
|
||||
|
||||
export default function Handler(props: any) {
|
||||
return <StackHandler {...props} app={stackServerApp} />;
|
||||
}
|
||||
return <StackHandler fullPage {...props} app={stackServerApp} />;
|
||||
}
|
||||
|
||||
@ -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 <RedirectMessageCard type='signedIn' fullPage={fullPage} />;
|
||||
@ -30,7 +31,7 @@ export default function AuthPage({
|
||||
const enableSeparator = (project.credentialEnabled || project.magicLinkEnabled) && project.oauthProviders.filter(p => p.enabled).length > 0;
|
||||
|
||||
return (
|
||||
<CardFrame fullPage={fullPage}>
|
||||
<MaybeFullPage fullPage={fullPage}>
|
||||
<div style={{ textAlign: 'center', marginBottom: '1.5rem' }}>
|
||||
<Text size="xl" as='h2' style={{ fontWeight: 500 }}>
|
||||
{type === 'sign-in' ? 'Sign in to your account' : 'Create a new account'}
|
||||
@ -71,6 +72,6 @@ export default function AuthPage({
|
||||
) : project.magicLinkEnabled ? (
|
||||
<MagicLinkSignIn/>
|
||||
) : null}
|
||||
</CardFrame>
|
||||
</MaybeFullPage>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<CardFrame fullPage={fullPage}>
|
||||
<MaybeFullPage fullPage={fullPage}>
|
||||
<div style={{ textAlign: 'center', marginBottom: '1.5rem' }}>
|
||||
<Text size="xl" as='h2'>Reset Your Password</Text>
|
||||
<Text>
|
||||
@ -33,6 +33,6 @@ export default function ForgotPassword({ fullPage=false }: { fullPage?: boolean
|
||||
</Text>
|
||||
</div>
|
||||
<ForgotPasswordElement onSent={() => setSent(true)} />
|
||||
</CardFrame>
|
||||
</MaybeFullPage>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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<unknown>(null);
|
||||
@ -29,7 +29,7 @@ export default function OAuthCallback () {
|
||||
setTimeout(() => setShowRedirectLink(true), 3000);
|
||||
}, []);
|
||||
|
||||
return <MessageCard title='Redirecting...' fullPage>
|
||||
return <MessageCard title='Redirecting...' fullPage={props.fullPage}>
|
||||
{showRedirectLink ? <p>If you are not redirected automatically, <a href={app.urls.home}>click here</a>.</p> : null}
|
||||
{error ? <div>
|
||||
<p>Something went wrong while processing the OAuth callback:</p>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
'use client';
|
||||
import AuthPage from './auth-page';
|
||||
|
||||
export default function SignIn({ fullPage=false }: { fullPage?: boolean }) {
|
||||
|
||||
@ -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 <GoHomeMessageCard type='signedOut' fullPage />;
|
||||
}
|
||||
return <GoHomeMessageCard type='signedOut' fullPage={props.fullPage} />;
|
||||
}
|
||||
|
||||
@ -15,14 +15,22 @@ export default async function StackHandler<HasTokenStore extends boolean>({
|
||||
app,
|
||||
params: { stack } = {},
|
||||
searchParams = {},
|
||||
// TODO set default to false like on the other components (may break old code)
|
||||
fullPage = "deprecated-unset",
|
||||
}: {
|
||||
app: StackServerApp<HasTokenStore>,
|
||||
params?: { stack?: string[] },
|
||||
searchParams?: Record<string, string>,
|
||||
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 (
|
||||
<MessageCard title="Invalid Stack Handler Setup" fullPage>
|
||||
<MessageCard title="Invalid Stack Handler Setup" fullPage={fullPage}>
|
||||
<p>Can't use Stack handler at this location. Make sure that the file is in a folder called [...stack].</p>
|
||||
</MessageCard>
|
||||
);
|
||||
@ -52,40 +60,40 @@ export default async function StackHandler<HasTokenStore extends boolean>({
|
||||
case 'signin': {
|
||||
redirectIfNotHandler('signIn');
|
||||
await redirectIfHasUser();
|
||||
return <SignIn fullPage/>;
|
||||
return <SignIn fullPage={fullPage} />;
|
||||
}
|
||||
case 'signup': {
|
||||
redirectIfNotHandler('signUp');
|
||||
await redirectIfHasUser();
|
||||
return <SignUp fullPage/>;
|
||||
return <SignUp fullPage={fullPage} />;
|
||||
}
|
||||
case 'email-verification': {
|
||||
redirectIfNotHandler('emailVerification');
|
||||
return <EmailVerification searchParams={searchParams} fullPage/>;
|
||||
return <EmailVerification searchParams={searchParams} fullPage={fullPage} />;
|
||||
}
|
||||
case 'password-reset': {
|
||||
redirectIfNotHandler('passwordReset');
|
||||
return <PasswordReset searchParams={searchParams} fullPage />;
|
||||
return <PasswordReset searchParams={searchParams} fullPage={fullPage} />;
|
||||
}
|
||||
case 'forgot-password': {
|
||||
redirectIfNotHandler('forgotPassword');
|
||||
return <ForgotPassword fullPage />;
|
||||
return <ForgotPassword fullPage={fullPage} />;
|
||||
}
|
||||
case 'signout': {
|
||||
redirectIfNotHandler('signOut');
|
||||
return <SignOut/>;
|
||||
return <SignOut fullPage={fullPage} />;
|
||||
}
|
||||
case 'oauth-callback': {
|
||||
redirectIfNotHandler('oauthCallback');
|
||||
return <OAuthCallback />;
|
||||
return <OAuthCallback fullPage={fullPage} />;
|
||||
}
|
||||
case 'account-settings': {
|
||||
redirectIfNotHandler('accountSettings');
|
||||
return <AccountSettings fullPage />;
|
||||
return <AccountSettings fullPage={fullPage} />;
|
||||
}
|
||||
case 'magic-link-callback': {
|
||||
redirectIfNotHandler('magicLinkCallback');
|
||||
return <MagicLinkCallback searchParams={searchParams} fullPage />;
|
||||
return <MagicLinkCallback searchParams={searchParams} fullPage={fullPage} />;
|
||||
}
|
||||
default: {
|
||||
return notFound();
|
||||
|
||||
@ -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 (
|
||||
<div
|
||||
id='stack-card-frame'
|
||||
style={{
|
||||
height: hasNoParent ? '100vh' : '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Container size={380} style={{ padding: '1rem 1rem' }}>
|
||||
{children}
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return children;
|
||||
}
|
||||
|
||||
}
|
||||
57
packages/stack/src/components/maybe-full-page.tsx
Normal file
57
packages/stack/src/components/maybe-full-page.tsx
Normal file
@ -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 <script> tag again after the first render
|
||||
eval(scriptString);
|
||||
}, []);
|
||||
|
||||
if (fullPage) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id={id}
|
||||
suppressHydrationWarning
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
alignSelf: 'stretch',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Container size={380} style={{ padding: '1rem 1rem' }}>
|
||||
{children}
|
||||
</Container>
|
||||
</div>
|
||||
<script dangerouslySetInnerHTML={{
|
||||
__html: scriptString,
|
||||
}} />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return <>
|
||||
{children}
|
||||
</>;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import CardFrame from "./card-frame";
|
||||
import MaybeFullPage from "./maybe-full-page";
|
||||
import { Text } from "../components-core";
|
||||
|
||||
export default function MessageCard(
|
||||
@ -9,11 +9,11 @@ export default function MessageCard(
|
||||
{ children?: React.ReactNode, title: string, fullPage?: boolean}
|
||||
) {
|
||||
return (
|
||||
<CardFrame fullPage={fullPage}>
|
||||
<MaybeFullPage fullPage={fullPage}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Text size="xl" as='h2' style={{ marginBottom: '24px' }}>{title}</Text>
|
||||
{children}
|
||||
</div>
|
||||
</CardFrame>
|
||||
</MaybeFullPage>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import PasswordField from "./password-field";
|
||||
import FormWarningText from "./form-warning";
|
||||
import RedirectMessageCard from "./redirect-message-card";
|
||||
import MessageCard from "./message-card";
|
||||
import CardFrame from "./card-frame";
|
||||
import MaybeFullPage from "./maybe-full-page";
|
||||
import { Button, Label, Text } from "../components-core";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
|
||||
@ -64,7 +64,7 @@ export default function PasswordResetInner(
|
||||
}
|
||||
|
||||
return (
|
||||
<CardFrame fullPage={fullPage}>
|
||||
<MaybeFullPage fullPage={fullPage}>
|
||||
<div style={{ textAlign: 'center', marginBottom: '1.5rem' }}>
|
||||
<Text size="xl" as='h2'>Reset Your Password</Text>
|
||||
</div>
|
||||
@ -100,6 +100,6 @@ export default function PasswordResetInner(
|
||||
Reset Password
|
||||
</Button>
|
||||
</form>
|
||||
</CardFrame>
|
||||
</MaybeFullPage>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user