refactor(infra): directory structure (#4615)

This commit is contained in:
Joooye_34
2023-10-18 23:30:08 +08:00
committed by GitHub
parent 814d552be8
commit bed9310519
1150 changed files with 539 additions and 584 deletions
@@ -0,0 +1,14 @@
import clsx from 'clsx';
import type { FC, HTMLAttributes, PropsWithChildren } from 'react';
import { authContent } from './share.css';
export const AuthContent: FC<
PropsWithChildren & HTMLAttributes<HTMLDivElement>
> = ({ children, className, ...otherProps }) => {
return (
<div className={clsx(authContent, className)} {...otherProps}>
{children}
</div>
);
};
@@ -0,0 +1,52 @@
import clsx from 'clsx';
import type { FC, HTMLAttributes } from 'react';
import { Input, type InputProps } from '../../ui/input';
import { authInputWrapper, formHint } from './share.css';
export type AuthInputProps = InputProps & {
label?: string;
error?: boolean;
errorHint?: string;
withoutHint?: boolean;
onEnter?: () => void;
wrapperProps?: HTMLAttributes<HTMLDivElement>;
};
export const AuthInput: FC<AuthInputProps> = ({
label,
error,
errorHint,
withoutHint = false,
onEnter,
wrapperProps: { className, ...otherWrapperProps } = {},
...inputProps
}) => {
return (
<div
className={clsx(authInputWrapper, className, {
'without-hint': withoutHint,
})}
{...otherWrapperProps}
>
{label ? <label>{label}</label> : null}
<Input
size="extraLarge"
status={error ? 'error' : 'default'}
onKeyDown={e => {
if (e.key === 'Enter') {
onEnter?.();
}
}}
{...inputProps}
/>
{error && errorHint && !withoutHint ? (
<div
className={clsx(formHint, {
error: error,
})}
>
{errorHint}
</div>
) : null}
</div>
);
};
@@ -0,0 +1,32 @@
import type { FC, PropsWithChildren, ReactNode } from 'react';
import { Empty } from '../../ui/empty';
import { Wrapper } from '../../ui/layout';
import { Logo } from './logo';
import { authPageContainer } from './share.css';
export const AuthPageContainer: FC<
PropsWithChildren<{ title?: ReactNode; subtitle?: ReactNode }>
> = ({ children, title, subtitle }) => {
return (
<div className={authPageContainer}>
<Wrapper
style={{
position: 'absolute',
top: 25,
left: 20,
}}
>
<Logo />
</Wrapper>
<div className="wrapper">
<div className="content">
<p className="title">{title}</p>
<p className="subtitle">{subtitle}</p>
{children}
</div>
<Empty />
</div>
</div>
);
};
@@ -0,0 +1,24 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowLeftSmallIcon } from '@blocksuite/icons';
import { Button, type ButtonProps } from '@toeverything/components/button';
import { type FC } from 'react';
export const BackButton: FC<ButtonProps> = props => {
const t = useAFFiNEI18N();
return (
<Button
type="plain"
style={{
marginTop: 12,
marginLeft: -5,
paddingLeft: 0,
paddingRight: 5,
color: 'var(--affine-text-secondary-color)',
}}
icon={<ArrowLeftSmallIcon />}
{...props}
>
{t['com.affine.backButton']()}
</Button>
);
};
@@ -0,0 +1,71 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { useCallback, useState } from 'react';
import { AuthInput } from './auth-input';
import { AuthPageContainer } from './auth-page-container';
import { emailRegex } from './utils';
export const ChangeEmailPage = ({
onChangeEmail: propsOnChangeEmail,
}: {
onChangeEmail: (email: string) => Promise<boolean>;
onOpenAffine: () => void;
}) => {
const t = useAFFiNEI18N();
const [hasSetUp, setHasSetUp] = useState(false);
const [email, setEmail] = useState('');
const [isValidEmail, setIsValidEmail] = useState(true);
const [loading, setLoading] = useState(false);
const onContinue = useCallback(
() =>
void (async () => {
if (!emailRegex.test(email)) {
setIsValidEmail(false);
return;
}
setIsValidEmail(true);
setLoading(true);
const setup = await propsOnChangeEmail(email);
setLoading(false);
setHasSetUp(setup);
})(),
[email, propsOnChangeEmail]
);
const onEmailChange = useCallback((value: string) => {
setEmail(value);
}, []);
return (
<AuthPageContainer
title={t['com.affine.auth.change.email.page.title']()}
subtitle={t['com.affine.auth.change.email.page.subtitle']()}
>
<>
<AuthInput
width={320}
label={t['com.affine.settings.email']()}
placeholder={t['com.affine.auth.sign.email.placeholder']()}
value={email}
onChange={onEmailChange}
error={!isValidEmail}
errorHint={
isValidEmail ? '' : t['com.affine.auth.sign.email.error']()
}
onEnter={onContinue}
disabled={hasSetUp}
/>
<Button
type="primary"
size="large"
onClick={onContinue}
loading={loading}
disabled={hasSetUp}
>
{t['com.affine.auth.set.email.save']()}
</Button>
</>
</AuthPageContainer>
);
};
@@ -0,0 +1,58 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import type { FC } from 'react';
import { useCallback, useState } from 'react';
import { AuthPageContainer } from './auth-page-container';
import { SetPassword } from './set-password';
type User = {
id: string;
name: string;
email: string;
image: string;
};
export const ChangePasswordPage: FC<{
user: User;
onSetPassword: (password: string) => void;
onOpenAffine: () => void;
}> = ({ user: { email }, onSetPassword: propsOnSetPassword, onOpenAffine }) => {
const t = useAFFiNEI18N();
const [hasSetUp, setHasSetUp] = useState(false);
const onSetPassword = useCallback(
(passWord: string) => {
propsOnSetPassword(passWord);
setHasSetUp(true);
},
[propsOnSetPassword]
);
return (
<AuthPageContainer
title={
hasSetUp
? t['com.affine.auth.reset.password.page.success']()
: t['com.affine.auth.reset.password.page.title']()
}
subtitle={
hasSetUp ? (
t['com.affine.auth.sent.reset.password.success.message']()
) : (
<>
{t['com.affine.auth.page.sent.email.subtitle']()}
<a href={`mailto:${email}`}>{email}</a>
</>
)
}
>
{hasSetUp ? (
<Button type="primary" size="large" onClick={onOpenAffine}>
{t['com.affine.auth.open.affine']()}
</Button>
) : (
<SetPassword onSetPassword={onSetPassword} />
)}
</AuthPageContainer>
);
};
@@ -0,0 +1,22 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import type { FC } from 'react';
import { AuthPageContainer } from './auth-page-container';
export const ConfirmChangeEmail: FC<{
onOpenAffine: () => void;
}> = ({ onOpenAffine }) => {
const t = useAFFiNEI18N();
return (
<AuthPageContainer
title={t['com.affine.auth.change.email.page.success.title']()}
subtitle={t['com.affine.auth.change.email.page.success.subtitle']()}
>
<Button type="primary" size="large" onClick={onOpenAffine}>
{t['com.affine.auth.open.affine']()}
</Button>
</AuthPageContainer>
);
};
@@ -0,0 +1,24 @@
import { forwardRef, type HTMLAttributes } from 'react';
const formatTime = (time: number): string => {
const minutes = Math.floor(time / 60);
const seconds = time % 60;
const formattedMinutes = minutes.toString().padStart(2, '0');
const formattedSeconds = seconds.toString().padStart(2, '0');
return `${formattedMinutes}:${formattedSeconds}`;
};
export const CountDownRender = forwardRef<
HTMLDivElement,
{ timeLeft: number } & HTMLAttributes<HTMLDivElement>
>(({ timeLeft, ...props }, ref) => {
return (
<div {...props} ref={ref}>
{formatTime(timeLeft)}
</div>
);
});
CountDownRender.displayName = 'CountDownRender';
@@ -0,0 +1,15 @@
export * from './auth-content';
export * from './auth-input';
export * from './auth-page-container';
export * from './back-button';
export * from './change-email-page';
export * from './change-password-page';
export * from './confirm-change-email';
export * from './count-down-render';
export * from './modal';
export * from './modal-header';
export * from './password-input';
export * from './set-password-page';
export * from './sign-in-page-container';
export * from './sign-in-success-page';
export * from './sign-up-page';
@@ -0,0 +1,18 @@
export const Logo = () => {
return (
<svg
width="149"
height="48"
viewBox="0 0 149 48"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M41.2519 35.7795C40.6446 34.7245 39.6331 32.973 38.6338 31.243C38.3306 30.718 38.0295 30.1962 37.7406 29.6957C37.1466 28.6663 36.6056 27.7277 36.2197 27.0578C33.5423 22.4283 28.2713 13.2624 25.6378 8.72996C24.8263 7.46664 22.9461 7.56672 22.248 8.87498C21.4559 10.2486 20.5842 11.7591 19.6635 13.3553C19.3715 13.8619 19.0735 14.3776 18.7724 14.8995C14.9365 21.546 10.4473 29.326 7.24932 34.8675C7.08601 35.1637 6.78183 35.6467 6.62974 35.9664C6.36333 36.5434 6.41539 37.2624 6.75121 37.7955C7.1299 38.4266 7.83828 38.7728 8.56198 38.7371C9.43267 38.7371 11.2792 38.7361 13.6667 38.7371C14.2332 38.7371 14.8303 38.7371 15.4519 38.7371C23.7535 38.7371 36.4351 38.7422 39.5473 38.7371C41.059 38.7402 42.0073 37.0816 41.254 35.7785L41.2519 35.7795ZM23.382 29.8929L21.854 27.2447C21.5855 26.779 21.9213 26.1969 22.4582 26.1969H25.5143C26.0522 26.1969 26.388 26.779 26.1186 27.2447L24.5905 29.8929C24.3221 30.3586 23.6504 30.3586 23.381 29.8929H23.382ZM20.822 24.9611C20.6954 24.6384 20.5862 24.3106 20.4964 23.9776L25.5521 24.9611H20.822ZM23.1309 31.9283C22.9155 32.1999 22.6858 32.4583 22.4429 32.7034L20.7659 27.8309L23.1299 31.9283H23.1309ZM28.0079 26.444C28.3499 26.4951 28.6888 26.5655 29.0215 26.6534L25.6429 30.5424L28.0079 26.444ZM20.1851 22.318C20.131 21.8258 20.1075 21.3284 20.1095 20.83L26.7861 24.0889L20.184 22.3191L20.1851 22.318ZM19.393 27.1967L21.1609 33.7992C20.7618 34.0923 20.3433 34.362 19.9105 34.6091L19.3919 27.1967H19.393ZM30.6139 27.214C31.0671 27.4132 31.5091 27.642 31.9398 27.8932L25.7807 32.0498L30.6139 27.214ZM20.2626 18.7283C20.3851 17.8234 20.574 16.9318 20.8026 16.077L30.9456 24.8988L20.2637 18.7283H20.2626ZM18.015 35.5293C17.1708 35.8755 16.3053 36.1574 15.4509 36.3861L18.015 23.1882V35.5293ZM33.6822 29.0758C34.4038 29.6345 35.0816 30.2442 35.7073 30.8692L23.0002 35.2464L33.6822 29.0758ZM24.4303 11.1167C26.3003 14.3633 29.0154 19.0714 31.6019 23.5528L21.9448 13.8905C22.5123 12.908 23.0502 11.9756 23.5463 11.1146C23.7433 10.7746 24.2333 10.7746 24.4303 11.1146V11.1167ZM9.34693 35.7213C9.87873 34.8031 10.6096 33.5439 10.7719 33.2539C12.3111 30.5863 14.3842 26.9945 16.5257 23.2831L12.9889 36.4883C11.7222 36.4883 10.63 36.4883 9.7889 36.4883C9.39592 36.4883 9.14993 36.0624 9.34693 35.7223V35.7213ZM38.1867 36.4903C35.2378 36.4903 29.5125 36.4903 23.828 36.4903L37.0271 32.9516C37.7192 34.1506 38.2734 35.1106 38.6287 35.7254C38.8257 36.0654 38.5797 36.4903 38.1877 36.4903H38.1867Z" />
<path d="M60.656 11.908C60.5365 11.4259 60.1037 11.0879 59.6077 11.0879H57.9326C57.4366 11.0879 57.0038 11.4259 56.8844 11.908L51.205 34.8244C51.0366 35.5056 51.551 36.1643 52.2533 36.1643H52.831C53.3332 36.1643 53.7691 35.8181 53.8824 35.3289L55.0919 30.1296C55.2052 29.6404 55.6411 29.2942 56.1433 29.2942H61.396C61.8982 29.2942 62.334 29.6404 62.4473 30.1296L63.6569 35.3289C63.7702 35.8181 64.2061 36.1643 64.7083 36.1643H65.286C65.9872 36.1643 66.5027 35.5056 66.3343 34.8244L60.6549 11.908H60.656ZM60.3344 26.8891H57.2059C56.5108 26.8891 55.9963 26.2416 56.1545 25.5645L58.245 15.4978C58.3746 14.9412 59.1667 14.9412 59.2963 15.4978L61.3868 25.5645C61.544 26.2416 61.0306 26.8891 60.3354 26.8891H60.3344Z" />
<path d="M101.52 22.0453H88.56C87.9639 22.0453 87.4801 21.5613 87.4801 20.9648V15.5439C87.4801 14.4103 88.3987 13.4911 89.5318 13.4911H95.945C96.5411 13.4911 97.0249 13.007 97.0249 12.4106V12.1665C97.0249 11.5701 96.5411 11.086 95.945 11.086H88.7672C86.5012 11.086 84.6649 12.9243 84.6649 15.1905V22.0433H73.1266C72.5304 22.0433 72.0466 21.5592 72.0466 20.9628V15.5419C72.0466 14.4082 72.9653 13.4891 74.0983 13.4891H80.5116C81.1077 13.4891 81.5915 13.005 81.5915 12.4086V12.1645C81.5915 11.5681 81.1077 11.084 80.5116 11.084H73.3338C71.0677 11.084 69.2314 12.9223 69.2314 15.1885V35.0799C69.2314 35.6763 69.7153 36.1604 70.3114 36.1604H70.9677C71.5638 36.1604 72.0476 35.6763 72.0476 35.0799V25.5269C72.0476 24.9305 72.5315 24.4464 73.1276 24.4464H84.6659V35.0799C84.6659 35.6763 85.1498 36.1604 85.7459 36.1604H86.4022C86.9983 36.1604 87.4821 35.6763 87.4821 35.0799V25.5269C87.4821 24.9305 87.966 24.4464 88.5621 24.4464H100.827C101.96 24.4464 102.879 25.3655 102.879 26.4991V35.0922C102.879 35.6886 103.363 36.1727 103.959 36.1727H104.546C105.142 36.1727 105.626 35.6886 105.626 35.0922V26.1468C105.626 23.8796 103.788 22.0423 101.523 22.0423L101.52 22.0453Z" />
<path d="M122.781 11.0879H122.19C121.595 11.0879 121.112 11.57 121.11 12.1644L121.045 32.7288L115.155 11.8743C115.023 11.4086 114.6 11.0879 114.116 11.0879H111.549C110.953 11.0879 110.469 11.572 110.469 12.1684V35.0849C110.469 35.6813 110.953 36.1654 111.549 36.1654H112.14C112.735 36.1654 113.218 35.6833 113.22 35.089L113.285 14.5245L119.175 35.379C119.306 35.8447 119.73 36.1654 120.214 36.1654H122.781C123.377 36.1654 123.861 35.6813 123.861 35.0849V12.1674C123.861 11.571 123.377 11.0869 122.781 11.0869V11.0879Z" />
<path d="M132.074 13.492H138.179C138.775 13.492 139.259 13.0079 139.259 12.4115V12.1674C139.259 11.571 138.775 11.0869 138.179 11.0869H131.345C129.079 11.0869 127.243 12.9252 127.243 15.1914V32.0578C127.243 34.325 129.08 36.1623 131.345 36.1623H138.179C138.775 36.1623 139.259 35.6782 139.259 35.0818V34.8377C139.259 34.2413 138.775 33.7572 138.179 33.7572H132.074C130.941 33.7572 130.023 32.8381 130.023 31.7044V25.1846C130.023 24.5882 130.506 24.1041 131.103 24.1041H137.835C138.431 24.1041 138.915 23.62 138.915 23.0236V22.7795C138.915 22.1831 138.431 21.699 137.835 21.699H131.103C130.506 21.699 130.023 21.2149 130.023 20.6185V15.5407C130.023 14.4071 130.941 13.4879 132.074 13.4879V13.492Z" />
<path d="M103.979 14.1446C103.499 13.6646 102.681 13.8842 102.506 14.5398L101.713 17.5025C101.537 18.1572 102.136 18.7567 102.792 18.582L105.753 17.7885C106.408 17.6128 106.626 16.7938 106.148 16.3148L103.981 14.1466L103.979 14.1446Z" />
</svg>
);
};
@@ -0,0 +1,18 @@
import { Logo1Icon } from '@blocksuite/icons';
import type { FC } from 'react';
import { modalHeaderWrapper } from './share.css';
export const ModalHeader: FC<{
title: string;
subTitle: string;
}> = ({ title, subTitle }) => {
return (
<div className={modalHeaderWrapper}>
<p>
<Logo1Icon className="logo" />
{title}
</p>
<p>{subTitle}</p>
</div>
);
};
@@ -0,0 +1,28 @@
import { Modal } from '@toeverything/components/modal';
import type { FC, PropsWithChildren } from 'react';
export type AuthModalProps = {
open: boolean;
setOpen: (value: boolean) => void;
};
export const AuthModal: FC<PropsWithChildren<AuthModalProps>> = ({
children,
open,
setOpen,
}) => {
return (
<Modal
open={open}
onOpenChange={setOpen}
width={400}
height={468}
contentOptions={{
['data-testid' as string]: 'auth-modal',
style: { padding: '44px 40px 0' },
}}
>
{children}
</Modal>
);
};
@@ -0,0 +1,30 @@
export const ErrorIcon = () => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g id="InformationFill_Duotone">
<g id="Icon (Stroke)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M1.33398 8.00065C1.33398 4.31875 4.31875 1.33398 8.00065 1.33398C11.6826 1.33398 14.6673 4.31875 14.6673 8.00065C14.6673 11.6826 11.6826 14.6673 8.00065 14.6673C4.31875 14.6673 1.33398 11.6826 1.33398 8.00065ZM7.33398 5.33398C7.33398 4.96579 7.63246 4.66732 8.00065 4.66732H8.00732C8.37551 4.66732 8.67398 4.96579 8.67398 5.33398C8.67398 5.70217 8.37551 6.00065 8.00732 6.00065H8.00065C7.63246 6.00065 7.33398 5.70217 7.33398 5.33398ZM8.00065 6.66732C8.36884 6.66732 8.66732 6.96579 8.66732 7.33398V10.6673C8.66732 11.0355 8.36884 11.334 8.00065 11.334C7.63246 11.334 7.33398 11.0355 7.33398 10.6673V7.33398C7.33398 6.96579 7.63246 6.66732 8.00065 6.66732Z"
fill="#EB4335"
/>
<path
d="M8.66732 7.33398C8.66732 6.96579 8.36884 6.66732 8.00065 6.66732C7.63246 6.66732 7.33398 6.96579 7.33398 7.33398V10.6673C7.33398 11.0355 7.63246 11.334 8.00065 11.334C8.36884 11.334 8.66732 11.0355 8.66732 10.6673V7.33398Z"
fill="white"
/>
<path
d="M8.00065 4.66732C7.63246 4.66732 7.33398 4.96579 7.33398 5.33398C7.33398 5.70217 7.63246 6.00065 8.00065 6.00065H8.00732C8.37551 6.00065 8.67398 5.70217 8.67398 5.33398C8.67398 4.96579 8.37551 4.66732 8.00732 4.66732H8.00065Z"
fill="white"
/>
</g>
</g>
</svg>
);
};
@@ -0,0 +1,103 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { passwordStrength } from 'check-password-strength';
import { type FC, useEffect } from 'react';
import { useCallback, useState } from 'react';
import { Input, type InputProps } from '../../../ui/input';
import { ErrorIcon } from './error';
import { SuccessIcon } from './success';
import { Tag } from './tag';
export type Status = 'weak' | 'medium' | 'strong' | 'maximum';
export const PasswordInput: FC<
InputProps & {
onPass: (password: string) => void;
onPrevent: () => void;
}
> = ({ onPass, onPrevent, ...inputProps }) => {
const t = useAFFiNEI18N();
const [status, setStatus] = useState<Status | null>(null);
const [confirmStatus, setConfirmStatus] = useState<
'success' | 'error' | null
>(null);
const [password, setPassWord] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const onPasswordChange = useCallback((value: string) => {
setPassWord(value);
if (!value) {
return setStatus(null);
}
if (value.length > 20) {
return setStatus('maximum');
}
switch (passwordStrength(value).id) {
case 0:
case 1:
setStatus('weak');
break;
case 2:
setStatus('medium');
break;
case 3:
setStatus('strong');
break;
}
}, []);
const onConfirmPasswordChange = useCallback((value: string) => {
setConfirmPassword(value);
}, []);
useEffect(() => {
if (!password || !confirmPassword) {
return;
}
if (password === confirmPassword) {
setConfirmStatus('success');
} else {
setConfirmStatus('error');
}
}, [confirmPassword, password]);
useEffect(() => {
if (confirmStatus === 'success' && password.length > 7) {
onPass(password);
} else {
onPrevent();
}
}, [confirmStatus, onPass, onPrevent, password]);
return (
<>
<Input
type="password"
size="extraLarge"
style={{ marginBottom: 20 }}
placeholder={t['com.affine.auth.set.password.placeholder']()}
onChange={onPasswordChange}
endFix={status ? <Tag status={status} /> : null}
{...inputProps}
/>
<Input
type="password"
size="extraLarge"
placeholder={t['com.affine.auth.set.password.placeholder.confirm']()}
onChange={onConfirmPasswordChange}
endFix={
confirmStatus ? (
confirmStatus === 'success' ? (
<SuccessIcon />
) : (
<ErrorIcon />
)
) : null
}
{...inputProps}
/>
</>
);
};
@@ -0,0 +1,28 @@
import { style } from '@vanilla-extract/css';
export const tag = style({
padding: '0 15px',
height: 20,
lineHeight: '20px',
borderRadius: 10,
fontSize: 'var(--affine-font-xs)',
selectors: {
'&.weak': {
backgroundColor: 'var(--affine-tag-red)',
color: 'var(--affine-error-color)',
},
'&.medium': {
backgroundColor: 'var(--affine-tag-orange)',
color: 'var(--affine-warning-color)',
},
'&.strong': {
backgroundColor: 'var(--affine-tag-green)',
color: 'var(--affine-success-color)',
},
'&.maximum': {
backgroundColor: 'var(--affine-tag-red)',
color: 'var(--affine-error-color)',
},
},
});
@@ -0,0 +1,28 @@
export const SuccessIcon = () => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g id="SingleSelect">
<path
id="Ellipse 2102 (Stroke)"
fillRule="evenodd"
clipRule="evenodd"
d="M1.5 8C1.5 4.41015 4.41015 1.5 8 1.5C11.5899 1.5 14.5 4.41015 14.5 8C14.5 11.5899 11.5899 14.5 8 14.5C4.41015 14.5 1.5 11.5899 1.5 8Z"
fill="#10CB86"
/>
<path
id="Icon (Stroke)"
fillRule="evenodd"
clipRule="evenodd"
d="M11.0052 5.63143C11.2087 5.81802 11.2225 6.13431 11.0359 6.33787L7.36923 10.3379C7.27708 10.4384 7.14786 10.4969 7.01151 10.4999C6.87517 10.5028 6.74353 10.45 6.6471 10.3536L4.98043 8.68689C4.78517 8.49163 4.78517 8.17505 4.98043 7.97978C5.17569 7.78452 5.49228 7.78452 5.68754 7.97978L6.98495 9.27719L10.2987 5.66214C10.4853 5.45858 10.8016 5.44483 11.0052 5.63143Z"
fill="white"
/>
</g>
</svg>
);
};
@@ -0,0 +1,28 @@
import clsx from 'clsx';
import { type FC, useMemo } from 'react';
import type { Status } from './index';
import { tag } from './style.css';
export const Tag: FC<{ status: Status }> = ({ status }) => {
const textMap = useMemo<{ [K in Status]: string }>(() => {
return {
weak: 'Weak',
medium: 'Medium',
strong: 'Strong',
maximum: 'Maximum',
};
}, []);
return (
<div
className={clsx(tag, {
weak: status === 'weak',
medium: status === 'medium',
strong: status === 'strong',
maximum: status === 'maximum',
})}
>
{textMap[status]}
</div>
);
};
@@ -0,0 +1,59 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import type { FC } from 'react';
import { useCallback, useState } from 'react';
import { AuthPageContainer } from './auth-page-container';
import { SetPassword } from './set-password';
type User = {
id: string;
name: string;
email: string;
image: string;
};
export const SetPasswordPage: FC<{
user: User;
onSetPassword: (password: string) => void;
onOpenAffine: () => void;
}> = ({ user: { email }, onSetPassword: propsOnSetPassword, onOpenAffine }) => {
const t = useAFFiNEI18N();
const [hasSetUp, setHasSetUp] = useState(false);
const onSetPassword = useCallback(
(passWord: string) => {
propsOnSetPassword(passWord);
setHasSetUp(true);
},
[propsOnSetPassword]
);
return (
<AuthPageContainer
title={
hasSetUp
? t['com.affine.auth.set.password.page.success']()
: t['com.affine.auth.set.password.page.title']()
}
subtitle={
hasSetUp ? (
t['com.affine.auth.sent.set.password.success.message']()
) : (
<>
{t['com.affine.auth.page.sent.email.subtitle']()}
<a href={`mailto:${email}`}>{email}</a>
</>
)
}
>
{hasSetUp ? (
<Button type="primary" size="large" onClick={onOpenAffine}>
{t['com.affine.auth.open.affine']()}
</Button>
) : (
<SetPassword onSetPassword={onSetPassword} />
)}
</AuthPageContainer>
);
};
@@ -0,0 +1,50 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { type FC, useCallback, useRef, useState } from 'react';
import { Wrapper } from '../../ui/layout';
import { PasswordInput } from './password-input';
export const SetPassword: FC<{
showLater?: boolean;
onLater?: () => void;
onSetPassword: (password: string) => void;
}> = ({ onLater, onSetPassword, showLater = false }) => {
const t = useAFFiNEI18N();
const [passwordPass, setPasswordPass] = useState(false);
const passwordRef = useRef('');
return (
<>
<Wrapper marginTop={30} marginBottom={42}>
<PasswordInput
width={320}
onPass={useCallback(password => {
setPasswordPass(true);
passwordRef.current = password;
}, [])}
onPrevent={useCallback(() => {
setPasswordPass(false);
}, [])}
/>
</Wrapper>
<Button
type="primary"
size="large"
disabled={!passwordPass}
style={{ marginRight: 20 }}
onClick={useCallback(() => {
onSetPassword(passwordRef.current);
}, [onSetPassword])}
>
{t['com.affine.auth.set.password.save']()}
</Button>
{showLater ? (
<Button type="plain" size="large" onClick={onLater}>
{t['com.affine.auth.later']()}
</Button>
) : null}
</>
);
};
@@ -0,0 +1,180 @@
import { globalStyle, keyframes, style } from '@vanilla-extract/css';
export const modalHeaderWrapper = style({});
globalStyle(`${modalHeaderWrapper} .logo`, {
fontSize: 'var(--affine-font-h-3)',
fontWeight: 600,
color: 'var(--affine-blue)',
marginRight: '6px',
verticalAlign: 'middle',
});
globalStyle(`${modalHeaderWrapper} > p:first-of-type`, {
fontSize: 'var(--affine-font-h-5)',
fontWeight: 600,
marginBottom: '4px',
lineHeight: '28px',
display: 'flex',
alignItems: 'center',
});
globalStyle(`${modalHeaderWrapper} > p:last-of-type`, {
fontSize: 'var(--affine-font-h-4)',
fontWeight: 600,
lineHeight: '28px',
});
export const authInputWrapper = style({
paddingBottom: '30px',
position: 'relative',
selectors: {
'&.without-hint': {
paddingBottom: '20px',
},
},
});
globalStyle(`${authInputWrapper} label`, {
display: 'block',
color: 'var(--light-text-color-text-secondary-color, #8E8D91)',
marginBottom: '4px',
fontSize: 'var(--affine-font-sm)',
fontWeight: 600,
lineHeight: '22px',
});
export const formHint = style({
fontSize: 'var(--affine-font-sm)',
position: 'absolute',
bottom: '4px',
left: 0,
lineHeight: '22px',
selectors: {
'&.error': {
color: 'var(--affine-error-color)',
},
'&.warning': {
color: 'var(--affine-warning-color)',
},
},
});
const rotate = keyframes({
'0%': { transform: 'rotate(0deg)' },
'50%': { transform: 'rotate(180deg)' },
'100%': { transform: 'rotate(360deg)' },
});
export const loading = style({
width: '15px',
height: '15px',
position: 'relative',
borderRadius: '50%',
overflow: 'hidden',
backgroundColor: 'var(--affine-border-color)',
selectors: {
'&::after': {
content: '""',
width: '12px',
height: '12px',
position: 'absolute',
left: '0',
right: '0',
top: '0',
bottom: '0',
margin: 'auto',
backgroundColor: '#fff',
zIndex: 2,
borderRadius: '50%',
},
'&::before': {
content: '""',
width: '20px',
height: '20px',
backgroundColor: 'var(--affine-blue)',
position: 'absolute',
left: '50%',
bottom: '50%',
zIndex: '1',
transformOrigin: 'left bottom',
animation: `${rotate} 1.5s infinite linear`,
},
},
});
export const authContent = style({
fontSize: 'var(--affine-font-base)',
lineHeight: 'var(--affine-font-h-3)',
marginTop: '30px',
});
globalStyle(`${authContent} a`, {
color: 'var(--affine-link-color)',
});
export const authCodeContainer = style({
paddingBottom: '40px',
position: 'relative',
});
export const authCodeWrapper = style({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
});
export const authCodeErrorMessage = style({
color: 'var(--affine-error-color)',
fontSize: 'var(--affine-font-sm)',
textAlign: 'center',
lineHeight: '1.5',
position: 'absolute',
left: 0,
right: 0,
bottom: 5,
margin: 'auto',
});
export const resendButtonWrapper = style({
height: 32,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginTop: 30,
});
globalStyle(`${resendButtonWrapper} .resend-code-hint`, {
fontWeight: 600,
fontSize: 'var(--affine-font-sm)',
marginRight: 8,
});
export const authPageContainer = style({
height: '100vh',
width: '100vw',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: 'var(--affine-font-base)',
});
globalStyle(`${authPageContainer} .wrapper`, {
display: 'flex',
alignItems: 'center',
});
globalStyle(`${authPageContainer} .content`, {
maxWidth: '700px',
minWidth: '550px',
});
globalStyle(`${authPageContainer} .title`, {
fontSize: 'var(--affine-font-title)',
fontWeight: 600,
marginBottom: '28px',
});
globalStyle(`${authPageContainer} .subtitle`, {
marginBottom: '28px',
});
globalStyle(`${authPageContainer} a`, {
color: 'var(--affine-link-color)',
});
export const signInPageContainer = style({
width: '400px',
margin: '205px auto 0',
});
@@ -0,0 +1,6 @@
import type { PropsWithChildren } from 'react';
import { signInPageContainer } from './share.css';
export const SignInPageContainer = ({ children }: PropsWithChildren) => {
return <div className={signInPageContainer}>{children}</div>;
};
@@ -0,0 +1,21 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import type { FC } from 'react';
import { AuthPageContainer } from './auth-page-container';
export const SignInSuccessPage: FC<{
onOpenAffine: () => void;
}> = ({ onOpenAffine }) => {
const t = useAFFiNEI18N();
return (
<AuthPageContainer
title={t['com.affine.auth.signed.success.title']()}
subtitle={t['com.affine.auth.signed.success.subtitle']()}
>
<Button type="primary" size="large" onClick={onOpenAffine}>
{t['com.affine.auth.open.affine']()}
</Button>
</AuthPageContainer>
);
};
@@ -0,0 +1,65 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import type { FC } from 'react';
import { useCallback, useState } from 'react';
import { AuthPageContainer } from './auth-page-container';
import { SetPassword } from './set-password';
type User = {
id: string;
name: string;
email: string;
image: string;
};
export const SignUpPage: FC<{
user: User;
onSetPassword: (password: string) => void;
onOpenAffine: () => void;
}> = ({ user: { email }, onSetPassword: propsOnSetPassword, onOpenAffine }) => {
const t = useAFFiNEI18N();
const [hasSetUp, setHasSetUp] = useState(false);
const onSetPassword = useCallback(
(passWord: string) => {
propsOnSetPassword(passWord);
setHasSetUp(true);
},
[propsOnSetPassword]
);
const onLater = useCallback(() => {
setHasSetUp(true);
}, []);
return (
<AuthPageContainer
title={
hasSetUp
? t['com.affine.auth.sign.up.success.title']()
: t['com.affine.auth.page.sent.email.title']()
}
subtitle={
hasSetUp ? (
t['com.affine.auth.sign.up.success.subtitle']()
) : (
<>
{t['com.affine.auth.page.sent.email.subtitle']()}
<a href={`mailto:${email}`}>{email}</a>
</>
)
}
>
{hasSetUp ? (
<Button type="primary" size="large" onClick={onOpenAffine}>
{t['com.affine.auth.open.affine']()}
</Button>
) : (
<SetPassword
onSetPassword={onSetPassword}
onLater={onLater}
showLater={true}
/>
)}
</AuthPageContainer>
);
};
@@ -0,0 +1,2 @@
export const emailRegex =
/^(?:(?:[^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(?:(?:\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|((?:[a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;