feat: add user agreement and privacy policy to login page

This commit is contained in:
キュビビイ
2025-10-09 22:21:56 +08:00
parent 0992f834da
commit b7725d7ae1
2 changed files with 117 additions and 5 deletions
+113 -1
View File
@@ -37,7 +37,7 @@ import {
isPasskeySupported, isPasskeySupported,
} from '../../helpers'; } from '../../helpers';
import Turnstile from 'react-turnstile'; import Turnstile from 'react-turnstile';
import { Button, Card, Divider, Form, Icon, Modal } from '@douyinfe/semi-ui'; import { Button, Card, Checkbox, Divider, Form, Icon, Modal } from '@douyinfe/semi-ui';
import Title from '@douyinfe/semi-ui/lib/es/typography/title'; import Title from '@douyinfe/semi-ui/lib/es/typography/title';
import Text from '@douyinfe/semi-ui/lib/es/typography/text'; import Text from '@douyinfe/semi-ui/lib/es/typography/text';
import TelegramLoginButton from 'react-telegram-login'; import TelegramLoginButton from 'react-telegram-login';
@@ -84,6 +84,9 @@ const LoginForm = () => {
const [showTwoFA, setShowTwoFA] = useState(false); const [showTwoFA, setShowTwoFA] = useState(false);
const [passkeySupported, setPasskeySupported] = useState(false); const [passkeySupported, setPasskeySupported] = useState(false);
const [passkeyLoading, setPasskeyLoading] = useState(false); const [passkeyLoading, setPasskeyLoading] = useState(false);
const [agreedToTerms, setAgreedToTerms] = useState(false);
const [hasUserAgreement, setHasUserAgreement] = useState(false);
const [hasPrivacyPolicy, setHasPrivacyPolicy] = useState(false);
const logo = getLogo(); const logo = getLogo();
const systemName = getSystemName(); const systemName = getSystemName();
@@ -103,6 +106,10 @@ const LoginForm = () => {
setTurnstileEnabled(true); setTurnstileEnabled(true);
setTurnstileSiteKey(status.turnstile_site_key); setTurnstileSiteKey(status.turnstile_site_key);
} }
// 从 status 获取用户协议和隐私政策的启用状态
setHasUserAgreement(status.user_agreement_enabled || false);
setHasPrivacyPolicy(status.privacy_policy_enabled || false);
}, [status]); }, [status]);
useEffect(() => { useEffect(() => {
@@ -118,6 +125,10 @@ const LoginForm = () => {
}, []); }, []);
const onWeChatLoginClicked = () => { const onWeChatLoginClicked = () => {
if ((hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms) {
showInfo(t('请先阅读并同意用户协议和隐私政策'));
return;
}
setWechatLoading(true); setWechatLoading(true);
setShowWeChatLoginModal(true); setShowWeChatLoginModal(true);
setWechatLoading(false); setWechatLoading(false);
@@ -157,6 +168,10 @@ const LoginForm = () => {
} }
async function handleSubmit(e) { async function handleSubmit(e) {
if ((hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms) {
showInfo(t('请先阅读并同意用户协议和隐私政策'));
return;
}
if (turnstileEnabled && turnstileToken === '') { if (turnstileEnabled && turnstileToken === '') {
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
return; return;
@@ -208,6 +223,10 @@ const LoginForm = () => {
// 添加Telegram登录处理函数 // 添加Telegram登录处理函数
const onTelegramLoginClicked = async (response) => { const onTelegramLoginClicked = async (response) => {
if ((hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms) {
showInfo(t('请先阅读并同意用户协议和隐私政策'));
return;
}
const fields = [ const fields = [
'id', 'id',
'first_name', 'first_name',
@@ -244,6 +263,10 @@ const LoginForm = () => {
// 包装的GitHub登录点击处理 // 包装的GitHub登录点击处理
const handleGitHubClick = () => { const handleGitHubClick = () => {
if ((hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms) {
showInfo(t('请先阅读并同意用户协议和隐私政策'));
return;
}
setGithubLoading(true); setGithubLoading(true);
try { try {
onGitHubOAuthClicked(status.github_client_id); onGitHubOAuthClicked(status.github_client_id);
@@ -255,6 +278,10 @@ const LoginForm = () => {
// 包装的OIDC登录点击处理 // 包装的OIDC登录点击处理
const handleOIDCClick = () => { const handleOIDCClick = () => {
if ((hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms) {
showInfo(t('请先阅读并同意用户协议和隐私政策'));
return;
}
setOidcLoading(true); setOidcLoading(true);
try { try {
onOIDCClicked(status.oidc_authorization_endpoint, status.oidc_client_id); onOIDCClicked(status.oidc_authorization_endpoint, status.oidc_client_id);
@@ -266,6 +293,10 @@ const LoginForm = () => {
// 包装的LinuxDO登录点击处理 // 包装的LinuxDO登录点击处理
const handleLinuxDOClick = () => { const handleLinuxDOClick = () => {
if ((hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms) {
showInfo(t('请先阅读并同意用户协议和隐私政策'));
return;
}
setLinuxdoLoading(true); setLinuxdoLoading(true);
try { try {
onLinuxDOOAuthClicked(status.linuxdo_client_id); onLinuxDOOAuthClicked(status.linuxdo_client_id);
@@ -283,6 +314,10 @@ const LoginForm = () => {
}; };
const handlePasskeyLogin = async () => { const handlePasskeyLogin = async () => {
if ((hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms) {
showInfo(t('请先阅读并同意用户协议和隐私政策'));
return;
}
if (!passkeySupported) { if (!passkeySupported) {
showInfo('当前环境无法使用 Passkey 登录'); showInfo('当前环境无法使用 Passkey 登录');
return; return;
@@ -486,6 +521,44 @@ const LoginForm = () => {
</Button> </Button>
</div> </div>
{(hasUserAgreement || hasPrivacyPolicy) && (
<div className='mt-6'>
<Checkbox
checked={agreedToTerms}
onChange={(e) => setAgreedToTerms(e.target.checked)}
>
<Text size='small' className='text-gray-600'>
{t('我已阅读并同意')}
{hasUserAgreement && (
<>
<a
href='/user-agreement'
target='_blank'
rel='noopener noreferrer'
className='text-blue-600 hover:text-blue-800 mx-1'
>
{t('用户协议')}
</a>
</>
)}
{hasUserAgreement && hasPrivacyPolicy && t('和')}
{hasPrivacyPolicy && (
<>
<a
href='/privacy-policy'
target='_blank'
rel='noopener noreferrer'
className='text-blue-600 hover:text-blue-800 mx-1'
>
{t('隐私政策')}
</a>
</>
)}
</Text>
</Checkbox>
</div>
)}
{!status.self_use_mode_enabled && ( {!status.self_use_mode_enabled && (
<div className='mt-6 text-center text-sm'> <div className='mt-6 text-center text-sm'>
<Text> <Text>
@@ -554,6 +627,44 @@ const LoginForm = () => {
prefix={<IconLock />} prefix={<IconLock />}
/> />
{(hasUserAgreement || hasPrivacyPolicy) && (
<div className='pt-4'>
<Checkbox
checked={agreedToTerms}
onChange={(e) => setAgreedToTerms(e.target.checked)}
>
<Text size='small' className='text-gray-600'>
{t('我已阅读并同意')}
{hasUserAgreement && (
<>
<a
href='/user-agreement'
target='_blank'
rel='noopener noreferrer'
className='text-blue-600 hover:text-blue-800 mx-1'
>
{t('用户协议')}
</a>
</>
)}
{hasUserAgreement && hasPrivacyPolicy && t('和')}
{hasPrivacyPolicy && (
<>
<a
href='/privacy-policy'
target='_blank'
rel='noopener noreferrer'
className='text-blue-600 hover:text-blue-800 mx-1'
>
{t('隐私政策')}
</a>
</>
)}
</Text>
</Checkbox>
</div>
)}
<div className='space-y-2 pt-2'> <div className='space-y-2 pt-2'>
<Button <Button
theme='solid' theme='solid'
@@ -562,6 +673,7 @@ const LoginForm = () => {
htmlType='submit' htmlType='submit'
onClick={handleSubmit} onClick={handleSubmit}
loading={loginLoading} loading={loginLoading}
disabled={(hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms}
> >
{t('继续')} {t('继续')}
</Button> </Button>
+4 -4
View File
@@ -30,7 +30,7 @@ import {
setUserData, setUserData,
} from '../../helpers'; } from '../../helpers';
import Turnstile from 'react-turnstile'; import Turnstile from 'react-turnstile';
import { Button, Card, Divider, Form, Icon, Modal } from '@douyinfe/semi-ui'; import { Button, Card, Checkbox, Divider, Form, Icon, Modal } from '@douyinfe/semi-ui';
import Title from '@douyinfe/semi-ui/lib/es/typography/title'; import Title from '@douyinfe/semi-ui/lib/es/typography/title';
import Text from '@douyinfe/semi-ui/lib/es/typography/text'; import Text from '@douyinfe/semi-ui/lib/es/typography/text';
import { import {
@@ -514,9 +514,9 @@ const RegisterForm = () => {
{(hasUserAgreement || hasPrivacyPolicy) && ( {(hasUserAgreement || hasPrivacyPolicy) && (
<div className='pt-4'> <div className='pt-4'>
<Form.Checkbox <Checkbox
checked={agreedToTerms} checked={agreedToTerms}
onChange={(checked) => setAgreedToTerms(checked)} onChange={(e) => setAgreedToTerms(e.target.checked)}
> >
<Text size='small' className='text-gray-600'> <Text size='small' className='text-gray-600'>
{t('我已阅读并同意')} {t('我已阅读并同意')}
@@ -546,7 +546,7 @@ const RegisterForm = () => {
</> </>
)} )}
</Text> </Text>
</Form.Checkbox> </Checkbox>
</div> </div>
)} )}