mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
parent
5b6116e57e
commit
df9dd2c4dd
@ -9,7 +9,7 @@ export default function Header() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fixed w-full z-50 p-4 h-12 flex items-center py-4 border-b justify-between">
|
||||
<div className="fixed w-full z-50 p-4 h-12 flex items-center py-4 border-b justify-between bg-white dark:bg-black">
|
||||
<Link href="/" className="font-semibold">
|
||||
Stack Demo
|
||||
</Link>
|
||||
|
||||
@ -7,8 +7,8 @@ import { yupObject, yupString } from '@stackframe/stack-shared/dist/schema-field
|
||||
import { generateRandomValues } from '@stackframe/stack-shared/dist/utils/crypto';
|
||||
import { throwErr } from '@stackframe/stack-shared/dist/utils/errors';
|
||||
import { runAsynchronously, runAsynchronouslyWithAlert } from '@stackframe/stack-shared/dist/utils/promises';
|
||||
import { Button, Container, EditableText, Input, Label, PasswordInput, SimpleTooltip, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from '@stackframe/stack-ui';
|
||||
import { CirclePlus, Contact, Settings, ShieldCheck } from 'lucide-react';
|
||||
import { Button, EditableText, Input, Label, PasswordInput, SimpleTooltip, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from '@stackframe/stack-ui';
|
||||
import { CirclePlus, Contact, LogOut, ShieldCheck } from 'lucide-react';
|
||||
import { TOTPController, createTOTPKeyURI } from "oslo/otp";
|
||||
import * as QRCode from 'qrcode';
|
||||
import { useEffect, useState } from 'react';
|
||||
@ -16,6 +16,7 @@ import { useForm } from 'react-hook-form';
|
||||
import * as yup from "yup";
|
||||
import { CurrentUser, MessageCard, Project, Team, useStackApp, useUser } from '..';
|
||||
import { FormWarningText } from '../components/elements/form-warning';
|
||||
import { MaybeFullPage } from "../components/elements/maybe-full-page";
|
||||
import { SidebarLayout } from '../components/elements/sidebar-layout';
|
||||
import { UserAvatar } from '../components/elements/user-avatar';
|
||||
import { TeamIcon } from '../components/team-icon';
|
||||
@ -49,10 +50,10 @@ export function AccountSettings({ fullPage=false }: { fullPage?: boolean }) {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
subpath: '/settings',
|
||||
title: 'Sign Out',
|
||||
subpath: '/sign-out',
|
||||
type: 'item',
|
||||
icon: Settings,
|
||||
icon: LogOut,
|
||||
content: <SignOutSection />,
|
||||
},
|
||||
...(teams.length > 0 || project.config.clientTeamCreationEnabled) ? [{
|
||||
@ -88,15 +89,11 @@ export function AccountSettings({ fullPage=false }: { fullPage?: boolean }) {
|
||||
basePath='/handler/account-settings'
|
||||
/>;
|
||||
|
||||
if (fullPage) {
|
||||
return (
|
||||
<Container size={1000} className='stack-scope'>
|
||||
{inner}
|
||||
</Container>
|
||||
);
|
||||
} else {
|
||||
return inner;
|
||||
}
|
||||
return (
|
||||
<MaybeFullPage fullPage={fullPage} size={800} fullVertical containerClassName="sm:border-r sm:border-l">
|
||||
{inner}
|
||||
</MaybeFullPage>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileSection() {
|
||||
@ -118,6 +115,10 @@ function EmailVerificationSection() {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const [emailSent, setEmailSent] = useState(false);
|
||||
|
||||
if (!user.primaryEmail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
@ -125,19 +126,22 @@ function EmailVerificationSection() {
|
||||
{user.primaryEmailVerified ? (
|
||||
<Typography variant='success'>Your email has been verified.</Typography>
|
||||
) : (
|
||||
<Typography variant='destructive'>Your email has not been verified.</Typography>
|
||||
<>
|
||||
<Typography variant='destructive'>Your email has not been verified.</Typography>
|
||||
<div className='flex mt-4'>
|
||||
<Button
|
||||
disabled={emailSent}
|
||||
onClick={async () => {
|
||||
await user.sendVerificationEmail();
|
||||
setEmailSent(true);
|
||||
}}
|
||||
>
|
||||
{emailSent ? 'Email sent!' : 'Send Verification Email'}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className='flex mt-4'>
|
||||
<Button
|
||||
disabled={emailSent}
|
||||
onClick={async () => {
|
||||
await user.sendVerificationEmail();
|
||||
setEmailSent(true);
|
||||
}}
|
||||
>
|
||||
{emailSent ? 'Email sent!' : 'Send Verification Email'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { Container } from "@stackframe/stack-ui";
|
||||
import { Container, cn } from "@stackframe/stack-ui";
|
||||
import React, { useId } from "react";
|
||||
import { SsrScript } from "./ssr-layout-effect";
|
||||
|
||||
export function MaybeFullPage({
|
||||
children,
|
||||
fullPage=true
|
||||
fullPage=true,
|
||||
size=380,
|
||||
fullVertical=false,
|
||||
containerClassName,
|
||||
}: {
|
||||
children: React.ReactNode,
|
||||
fullPage?: boolean,
|
||||
size?: number,
|
||||
fullVertical?: boolean,
|
||||
containerClassName?: string,
|
||||
}) {
|
||||
const uniqueId = useId();
|
||||
const id = `stack-card-frame-${uniqueId}`;
|
||||
@ -34,12 +40,12 @@ export function MaybeFullPage({
|
||||
minHeight: '100vh',
|
||||
alignSelf: 'stretch',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
alignItems: fullVertical ? 'stretch' : 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
className="stack-scope"
|
||||
>
|
||||
<Container size={380} style={{ padding: '1rem 1rem' }}>
|
||||
<Container size={size} className={cn(fullVertical ? undefined : 'p-4', containerClassName)}>
|
||||
{children}
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
@ -15,7 +15,7 @@ export type SidebarItem = {
|
||||
contentTitle?: React.ReactNode,
|
||||
}
|
||||
|
||||
export function SidebarLayout(props: { items: SidebarItem[], title?: ReactNode, basePath: string }) {
|
||||
export function SidebarLayout(props: { items: SidebarItem[], title?: ReactNode, basePath: string, className?: string }) {
|
||||
const pathname = usePathname();
|
||||
const selectedIndex = props.items.findIndex(item => item.subpath && (props.basePath + item.subpath === pathname));
|
||||
const router = useRouter();
|
||||
@ -25,10 +25,10 @@ export function SidebarLayout(props: { items: SidebarItem[], title?: ReactNode,
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="hidden sm:flex">
|
||||
<div className={cn("hidden sm:flex h-full", props.className)}>
|
||||
<DesktopLayout items={props.items} title={props.title} selectedIndex={selectedIndex} basePath={props.basePath} />
|
||||
</div>
|
||||
<div className="sm:hidden">
|
||||
<div className={cn("sm:hidden h-full", props.className)}>
|
||||
<MobileLayout items={props.items} title={props.title} selectedIndex={selectedIndex} basePath={props.basePath} />
|
||||
</div>
|
||||
</>
|
||||
@ -68,7 +68,7 @@ function DesktopLayout(props: { items: SidebarItem[], title?: ReactNode, selecte
|
||||
const selectedItem = props.items[props.selectedIndex === -1 ? 0 : props.selectedIndex];
|
||||
|
||||
return (
|
||||
<div className="stack-scope flex p-2 w-full">
|
||||
<div className="stack-scope flex w-full h-full">
|
||||
<div className="flex w-[200px] border-r flex-col items-stretch gap-2 p-2">
|
||||
{props.title && <div className='mb-2'>
|
||||
<Typography type='h2' className="text-lg font-semibold text-zinc-800 dark:text-zinc-300">{props.title}</Typography>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user