Files
lobe-chat/src/app/[variants]/(auth)/signin/SignInEmailStep.tsx
T

213 lines
6.1 KiB
TypeScript
Raw Normal View History

2025-12-24 17:52:22 +08:00
import { BRANDING_NAME } from '@lobechat/business-const';
import { Alert, Button, Flexbox, Icon, Input, Skeleton, Text } from '@lobehub/ui';
2025-12-20 21:44:57 +08:00
import { Divider, Form } from 'antd';
import type { FormInstance, InputRef } from 'antd';
import { createStaticStyles } from 'antd-style';
2025-12-20 21:44:57 +08:00
import { ChevronRight, Mail } from 'lucide-react';
import { useEffect, useRef } from 'react';
import { Trans, useTranslation } from 'react-i18next';
2026-01-23 23:57:08 +08:00
import AuthIcons from '@/components/AuthIcons';
2025-12-20 21:44:57 +08:00
import { PRIVACY_URL, TERMS_URL } from '@/const/url';
import AuthCard from '../../../../features/AuthCard';
const styles = createStaticStyles(({ css, cssVar }) => ({
setPasswordLink: css`
cursor: pointer;
color: ${cssVar.colorPrimary};
text-decoration: underline;
`,
}));
2025-12-20 21:44:57 +08:00
export const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export const USERNAME_REGEX = /^\w+$/;
export interface SignInEmailStepProps {
disableEmailPassword?: boolean;
2025-12-20 21:44:57 +08:00
form: FormInstance<{ email: string }>;
isSocialOnly: boolean;
2025-12-20 21:44:57 +08:00
loading: boolean;
oAuthSSOProviders: string[];
onCheckUser: (values: { email: string }) => Promise<void>;
onSetPassword: () => void;
2025-12-20 21:44:57 +08:00
onSocialSignIn: (provider: string) => void;
serverConfigInit: boolean;
socialLoading: string | null;
}
export const SignInEmailStep = ({
disableEmailPassword,
2025-12-20 21:44:57 +08:00
form,
isSocialOnly,
2025-12-20 21:44:57 +08:00
loading,
oAuthSSOProviders,
serverConfigInit,
socialLoading,
onCheckUser,
onSetPassword,
2025-12-20 21:44:57 +08:00
onSocialSignIn,
}: SignInEmailStepProps) => {
const { t } = useTranslation('auth');
const emailInputRef = useRef<InputRef>(null);
useEffect(() => {
emailInputRef.current?.focus();
}, []);
const divider = (
<Divider>
<Text fontSize={12} type={'secondary'}>
{t('betterAuth.signin.orContinueWith')}
</Text>
</Divider>
);
const getProviderLabel = (provider: string) => {
const normalized = provider
.toLowerCase()
.replaceAll(/(^|[_-])([a-z])/g, (_, __, c) => c.toUpperCase());
const normalizedKey = normalized.replaceAll(/[^\dA-Za-z]/g, '');
const key = `betterAuth.signin.continueWith${normalizedKey}`;
return t(key, { defaultValue: `Continue with ${normalized}` });
};
const footer = (
<Text fontSize={13} type={'secondary'}>
<Trans
components={{
privacy: (
<a
href={PRIVACY_URL}
style={{ color: 'inherit', cursor: 'pointer', textDecoration: 'underline' }}
>
{t('footer.terms')}
</a>
),
terms: (
<a
href={TERMS_URL}
style={{ color: 'inherit', cursor: 'pointer', textDecoration: 'underline' }}
>
{t('footer.privacy')}
</a>
),
}}
i18nKey={'footer.agreement'}
ns={'auth'}
/>
</Text>
);
return (
<AuthCard
footer={footer}
subtitle={t('signin.subtitle', { appName: BRANDING_NAME })}
2026-01-31 00:50:11 +08:00
title={'Agent teammates that grow with you'}
2025-12-20 21:44:57 +08:00
>
{!serverConfigInit && (
<Flexbox gap={12}>
<Skeleton.Button active block size="large" />
<Skeleton.Button active block size="large" />
{divider}
</Flexbox>
)}
{serverConfigInit && oAuthSSOProviders.length > 0 && (
<Flexbox gap={12}>
{oAuthSSOProviders.map((provider) => (
<Button
block
icon={
<Icon
icon={AuthIcons(provider, 18)}
style={{
left: 12,
position: 'absolute',
top: 13,
}}
/>
}
key={provider}
loading={socialLoading === provider}
onClick={() => onSocialSignIn(provider)}
size="large"
>
{getProviderLabel(provider)}
</Button>
))}
{!disableEmailPassword && divider}
2025-12-20 21:44:57 +08:00
</Flexbox>
)}
{serverConfigInit && disableEmailPassword && oAuthSSOProviders.length === 0 && (
<Alert description={t('betterAuth.signin.ssoOnlyNoProviders')} showIcon type="warning" />
)}
{!disableEmailPassword && (
<Form
form={form}
layout="vertical"
onFinish={(values) => onCheckUser(values as { email: string })}
2025-12-20 21:44:57 +08:00
>
<Form.Item
name="email"
rules={[
{ message: t('betterAuth.errors.emailRequired'), required: true },
{
validator: (_, value) => {
if (!value) return Promise.resolve();
const trimmedValue = (value as string).trim();
if (EMAIL_REGEX.test(trimmedValue) || USERNAME_REGEX.test(trimmedValue)) {
return Promise.resolve();
}
return Promise.reject(new Error(t('betterAuth.errors.emailInvalid')));
},
},
]}
style={{ marginBottom: 0 }}
>
<Input
placeholder={t('betterAuth.signin.emailPlaceholder')}
prefix={
<Icon
icon={Mail}
style={{
marginInline: 6,
}}
/>
}
ref={emailInputRef}
size="large"
style={{
padding: 6,
}}
suffix={
<Button
icon={ChevronRight}
loading={loading}
onClick={() => form.submit()}
title={t('betterAuth.signin.nextStep')}
variant={'filled'}
/>
}
/>
</Form.Item>
</Form>
)}
{isSocialOnly && (
<Alert
description={
<>
{t('betterAuth.signin.socialOnlyHint')}{' '}
<a className={styles.setPasswordLink} onClick={onSetPassword}>
{t('betterAuth.signin.setPassword')}
</a>
</>
}
showIcon
style={{ marginTop: 12 }}
type="info"
/>
)}
2025-12-20 21:44:57 +08:00
</AuthCard>
);
};