继续收口认证入口弹窗壳层
新增 PlatformAuthModalShell 统一认证白底弹窗壳层 登录入口和邀请码弹窗复用共享认证壳层 补充认证壳层和 AuthGate 接入测试 同步 PlatformUiKit 文档和 Hermes 决策记录
This commit is contained in:
@@ -592,6 +592,8 @@ test('auth gate hides register entry and opens invite modal for new sms account'
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '进入作品' }));
|
||||
const dialog = await screen.findByRole('dialog', { name: '账号入口' });
|
||||
expect(dialog.className).toContain('platform-auth-card');
|
||||
expect(dialog.className).toContain('platform-modal-shell');
|
||||
expect(within(dialog).queryByRole('tab', { name: '注册' })).toBeNull();
|
||||
expect(within(dialog).queryByLabelText('邀请码')).toBeNull();
|
||||
await user.type(within(dialog).getByLabelText('手机号'), '13800000000');
|
||||
@@ -609,6 +611,8 @@ test('auth gate hides register entry and opens invite modal for new sms account'
|
||||
const inviteDialog = await screen.findByRole('dialog', {
|
||||
name: '请填写邀请码',
|
||||
});
|
||||
expect(inviteDialog.className).toContain('platform-auth-card');
|
||||
expect(inviteDialog.className).toContain('platform-modal-shell');
|
||||
const inviteCodeInput = within(inviteDialog).getByLabelText(
|
||||
'邀请码',
|
||||
) as HTMLInputElement;
|
||||
@@ -799,6 +803,8 @@ test('login modal resets draft state every time it is reopened', async () => {
|
||||
await user.click(await screen.findByRole('button', { name: '进入作品' }));
|
||||
|
||||
const firstDialog = screen.getByRole('dialog', { name: '账号入口' });
|
||||
expect(firstDialog.className).toContain('platform-auth-card');
|
||||
expect(firstDialog.className).toContain('platform-modal-shell');
|
||||
await user.type(within(firstDialog).getByLabelText('手机号'), '13800000000');
|
||||
await user.click(
|
||||
within(firstDialog).getByRole('button', { name: '获取验证码' }),
|
||||
|
||||
@@ -18,11 +18,11 @@ import {
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformEmptyState } from '../common/PlatformEmptyState';
|
||||
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
|
||||
import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton';
|
||||
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
import { PlatformTextField } from '../common/PlatformTextField';
|
||||
import { CaptchaChallengeField } from './CaptchaChallengeField';
|
||||
import { PlatformAuthModalShell } from './PlatformAuthModalShell';
|
||||
|
||||
type SmsScene = 'login' | 'reset_password';
|
||||
type LoginTab = 'phone' | 'password';
|
||||
@@ -191,201 +191,181 @@ export function LoginScreen({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`platform-theme platform-theme--${platformTheme} platform-overlay fixed inset-0 z-[120] flex items-end justify-center px-3 py-4 text-[var(--platform-text-strong)] backdrop-blur-sm sm:items-center sm:p-4`}
|
||||
onClick={onClose}
|
||||
<PlatformAuthModalShell
|
||||
title={isResetPanelOpen ? '重置密码' : '账号入口'}
|
||||
platformTheme={platformTheme}
|
||||
onClose={onClose}
|
||||
closeLabel="关闭登录弹窗"
|
||||
panelClassName="!max-w-md"
|
||||
>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="auth-login-dialog-title"
|
||||
className="platform-auth-card w-full max-w-md overflow-hidden rounded-[2rem]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3 border-b border-[var(--platform-subpanel-border)] px-5 py-4">
|
||||
<div
|
||||
id="auth-login-dialog-title"
|
||||
className="text-lg font-semibold text-[var(--platform-text-strong)]"
|
||||
>
|
||||
{isResetPanelOpen ? '重置密码' : '账号入口'}
|
||||
</div>
|
||||
<PlatformModalCloseButton
|
||||
onClick={onClose}
|
||||
label="关闭登录弹窗"
|
||||
variant="platformIcon"
|
||||
className="p-2"
|
||||
/>
|
||||
</div>
|
||||
{isResetPanelOpen ? (
|
||||
<PasswordResetPanel
|
||||
phone={resetPhone}
|
||||
code={resetCode}
|
||||
password={resetPasswordValue}
|
||||
sendingCode={sendingCode}
|
||||
loggingIn={loggingIn}
|
||||
cooldownSeconds={resetCooldownSeconds}
|
||||
error={error}
|
||||
onPhoneChange={setResetPhone}
|
||||
onCodeChange={setResetCode}
|
||||
onPasswordChange={setResetPasswordValue}
|
||||
onBack={() => setIsResetPanelOpen(false)}
|
||||
onSendCode={async () => {
|
||||
const result = await onSendCode(resetPhone, 'reset_password');
|
||||
setResetCooldownSeconds(result.cooldownSeconds);
|
||||
}}
|
||||
onSubmit={() =>
|
||||
onResetPassword(resetPhone, resetCode, resetPasswordValue)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col gap-5 px-5 py-5">
|
||||
{phoneLoginEnabled ? (
|
||||
<PlatformSegmentedTabs
|
||||
items={
|
||||
passwordLoginEnabled
|
||||
? LOGIN_TAB_ITEMS
|
||||
: LOGIN_TAB_ITEMS.slice(0, 1)
|
||||
}
|
||||
activeId={activeLoginTab}
|
||||
onChange={setActiveLoginTab}
|
||||
columns={passwordLoginEnabled ? 'two' : 'one'}
|
||||
frame="bare"
|
||||
surface="transparent"
|
||||
tone="underline"
|
||||
size="tab"
|
||||
semantics="tabs"
|
||||
ariaLabel="登录方式"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{isResetPanelOpen ? (
|
||||
<PasswordResetPanel
|
||||
phone={resetPhone}
|
||||
code={resetCode}
|
||||
password={resetPasswordValue}
|
||||
sendingCode={sendingCode}
|
||||
loggingIn={loggingIn}
|
||||
cooldownSeconds={resetCooldownSeconds}
|
||||
error={error}
|
||||
onPhoneChange={setResetPhone}
|
||||
onCodeChange={setResetCode}
|
||||
onPasswordChange={setResetPasswordValue}
|
||||
onBack={() => setIsResetPanelOpen(false)}
|
||||
onSendCode={async () => {
|
||||
const result = await onSendCode(resetPhone, 'reset_password');
|
||||
setResetCooldownSeconds(result.cooldownSeconds);
|
||||
}}
|
||||
onSubmit={() =>
|
||||
onResetPassword(resetPhone, resetCode, resetPasswordValue)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col gap-5 px-5 py-5">
|
||||
{phoneLoginEnabled ? (
|
||||
<PlatformSegmentedTabs
|
||||
items={
|
||||
passwordLoginEnabled
|
||||
? LOGIN_TAB_ITEMS
|
||||
: LOGIN_TAB_ITEMS.slice(0, 1)
|
||||
{passwordLoginEnabled && activeLoginTab === 'password' ? (
|
||||
<form
|
||||
className="flex flex-col gap-4"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
if (
|
||||
submitDisabled ||
|
||||
!phone.trim() ||
|
||||
!password.trim() ||
|
||||
!legalConsentChecked
|
||||
) {
|
||||
return;
|
||||
}
|
||||
activeId={activeLoginTab}
|
||||
onChange={setActiveLoginTab}
|
||||
columns={passwordLoginEnabled ? 'two' : 'one'}
|
||||
frame="bare"
|
||||
surface="transparent"
|
||||
tone="underline"
|
||||
size="tab"
|
||||
semantics="tabs"
|
||||
ariaLabel="登录方式"
|
||||
/>
|
||||
) : null}
|
||||
void onPasswordSubmit(phone, password);
|
||||
}}
|
||||
>
|
||||
<label className="grid gap-2">
|
||||
<PlatformFieldLabel variant="form" className="mb-0">
|
||||
手机号
|
||||
</PlatformFieldLabel>
|
||||
<PlatformTextField
|
||||
autoComplete="tel"
|
||||
inputMode="numeric"
|
||||
value={phone}
|
||||
onChange={(event) => setPhone(event.target.value)}
|
||||
placeholder="13800000000"
|
||||
/>
|
||||
</label>
|
||||
<label className="grid gap-2">
|
||||
<PlatformFieldLabel variant="form" className="mb-0">
|
||||
密码
|
||||
</PlatformFieldLabel>
|
||||
<PlatformTextField
|
||||
autoComplete="current-password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
placeholder="输入密码"
|
||||
/>
|
||||
</label>
|
||||
|
||||
{passwordLoginEnabled && activeLoginTab === 'password' ? (
|
||||
<form
|
||||
className="flex flex-col gap-4"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
if (
|
||||
{error ? <ErrorBanner message={error} /> : null}
|
||||
{legalConsentRow}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<PlatformActionButton
|
||||
type="submit"
|
||||
disabled={
|
||||
submitDisabled ||
|
||||
!phone.trim() ||
|
||||
!password.trim() ||
|
||||
!legalConsentChecked
|
||||
) {
|
||||
return;
|
||||
}
|
||||
void onPasswordSubmit(phone, password);
|
||||
}}
|
||||
>
|
||||
<label className="grid gap-2">
|
||||
<PlatformFieldLabel variant="form" className="mb-0">
|
||||
手机号
|
||||
</PlatformFieldLabel>
|
||||
<PlatformTextField
|
||||
autoComplete="tel"
|
||||
inputMode="numeric"
|
||||
value={phone}
|
||||
onChange={(event) => setPhone(event.target.value)}
|
||||
placeholder="13800000000"
|
||||
/>
|
||||
</label>
|
||||
<label className="grid gap-2">
|
||||
<PlatformFieldLabel variant="form" className="mb-0">
|
||||
密码
|
||||
</PlatformFieldLabel>
|
||||
<PlatformTextField
|
||||
autoComplete="current-password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
placeholder="输入密码"
|
||||
/>
|
||||
</label>
|
||||
size="lg"
|
||||
>
|
||||
{loggingIn ? '登录中' : '登录'}
|
||||
</PlatformActionButton>
|
||||
<button
|
||||
type="button"
|
||||
className="self-end text-sm text-[var(--platform-accent)]"
|
||||
onClick={() => setIsResetPanelOpen(true)}
|
||||
>
|
||||
忘记密码
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error ? <ErrorBanner message={error} /> : null}
|
||||
{legalConsentRow}
|
||||
{wechatLoginEnabled && !miniProgramRuntime ? (
|
||||
<WechatButton
|
||||
loading={wechatLoading}
|
||||
disabled={submitDisabled}
|
||||
onClick={onStartWechatLogin}
|
||||
/>
|
||||
) : null}
|
||||
</form>
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<PlatformActionButton
|
||||
type="submit"
|
||||
disabled={
|
||||
submitDisabled ||
|
||||
!phone.trim() ||
|
||||
!password.trim() ||
|
||||
!legalConsentChecked
|
||||
}
|
||||
size="lg"
|
||||
>
|
||||
{loggingIn ? '登录中' : '登录'}
|
||||
</PlatformActionButton>
|
||||
<button
|
||||
type="button"
|
||||
className="self-end text-sm text-[var(--platform-accent)]"
|
||||
onClick={() => setIsResetPanelOpen(true)}
|
||||
>
|
||||
忘记密码
|
||||
</button>
|
||||
</div>
|
||||
{phoneLoginEnabled && activeLoginTab === 'phone' ? (
|
||||
<PhoneCodeForm
|
||||
phone={phone}
|
||||
code={code}
|
||||
captchaAnswer={captchaAnswer}
|
||||
captchaChallenge={captchaChallenge}
|
||||
cooldownSeconds={cooldownSeconds}
|
||||
sendingCode={sendingCode}
|
||||
loggingIn={loggingIn}
|
||||
error={error}
|
||||
hint={hint}
|
||||
submitLabel="登录"
|
||||
enabled={phoneLoginEnabled}
|
||||
legalConsentChecked={legalConsentChecked}
|
||||
legalConsentNode={legalConsentRow}
|
||||
showPhoneField
|
||||
onPhoneChange={setPhone}
|
||||
onCodeChange={setCode}
|
||||
onCaptchaAnswerChange={setCaptchaAnswer}
|
||||
onSendCode={async () => {
|
||||
setHint('');
|
||||
const result = await onSendCode(phone, 'login', {
|
||||
challengeId: captchaChallenge?.challengeId,
|
||||
answer: captchaAnswer,
|
||||
});
|
||||
setCooldownSeconds(result.cooldownSeconds);
|
||||
setHint(
|
||||
`短信请求已提交,验证码有效期约 ${Math.max(1, Math.round(result.expiresInSeconds / 60))} 分钟。`,
|
||||
);
|
||||
setCaptchaAnswer('');
|
||||
}}
|
||||
onSubmit={() => onPhoneSubmit(phone, code)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{wechatLoginEnabled && !miniProgramRuntime ? (
|
||||
<WechatButton
|
||||
loading={wechatLoading}
|
||||
disabled={submitDisabled}
|
||||
onClick={onStartWechatLogin}
|
||||
/>
|
||||
) : null}
|
||||
</form>
|
||||
) : null}
|
||||
|
||||
{phoneLoginEnabled && activeLoginTab === 'phone' ? (
|
||||
<PhoneCodeForm
|
||||
phone={phone}
|
||||
code={code}
|
||||
captchaAnswer={captchaAnswer}
|
||||
captchaChallenge={captchaChallenge}
|
||||
cooldownSeconds={cooldownSeconds}
|
||||
sendingCode={sendingCode}
|
||||
loggingIn={loggingIn}
|
||||
error={error}
|
||||
hint={hint}
|
||||
submitLabel="登录"
|
||||
enabled={phoneLoginEnabled}
|
||||
legalConsentChecked={legalConsentChecked}
|
||||
legalConsentNode={legalConsentRow}
|
||||
showPhoneField
|
||||
onPhoneChange={setPhone}
|
||||
onCodeChange={setCode}
|
||||
onCaptchaAnswerChange={setCaptchaAnswer}
|
||||
onSendCode={async () => {
|
||||
setHint('');
|
||||
const result = await onSendCode(phone, 'login', {
|
||||
challengeId: captchaChallenge?.challengeId,
|
||||
answer: captchaAnswer,
|
||||
});
|
||||
setCooldownSeconds(result.cooldownSeconds);
|
||||
setHint(
|
||||
`短信请求已提交,验证码有效期约 ${Math.max(1, Math.round(result.expiresInSeconds / 60))} 分钟。`,
|
||||
);
|
||||
setCaptchaAnswer('');
|
||||
}}
|
||||
onSubmit={() => onPhoneSubmit(phone, code)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!passwordLoginEnabled &&
|
||||
!phoneLoginEnabled &&
|
||||
!wechatLoginEnabled &&
|
||||
!miniProgramRuntime ? (
|
||||
<PlatformEmptyState
|
||||
surface="subpanel"
|
||||
size="compact"
|
||||
className="px-4 py-4"
|
||||
>
|
||||
当前登录入口暂不可用。
|
||||
</PlatformEmptyState>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!passwordLoginEnabled &&
|
||||
!phoneLoginEnabled &&
|
||||
!wechatLoginEnabled &&
|
||||
!miniProgramRuntime ? (
|
||||
<PlatformEmptyState
|
||||
surface="subpanel"
|
||||
size="compact"
|
||||
className="px-4 py-4"
|
||||
>
|
||||
当前登录入口暂不可用。
|
||||
</PlatformEmptyState>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</PlatformAuthModalShell>
|
||||
<LegalDocumentModal
|
||||
document={activeLegalDocument}
|
||||
open={Boolean(activeLegalDocument)}
|
||||
|
||||
54
src/components/auth/PlatformAuthModalShell.test.tsx
Normal file
54
src/components/auth/PlatformAuthModalShell.test.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
|
||||
import { PlatformAuthModalShell } from './PlatformAuthModalShell';
|
||||
|
||||
test('renders auth modal shell with platform theme and auth card chrome', () => {
|
||||
const onClose = vi.fn();
|
||||
|
||||
render(
|
||||
<PlatformAuthModalShell
|
||||
title="账号入口"
|
||||
platformTheme="light"
|
||||
onClose={onClose}
|
||||
closeLabel="关闭登录弹窗"
|
||||
panelClassName="!max-w-md"
|
||||
>
|
||||
<div>登录表单</div>
|
||||
</PlatformAuthModalShell>,
|
||||
);
|
||||
|
||||
const dialog = screen.getByRole('dialog', { name: '账号入口' });
|
||||
|
||||
expect(dialog.parentElement?.className).toContain('platform-theme--light');
|
||||
expect(dialog.className).toContain('platform-modal-shell');
|
||||
expect(dialog.className).toContain('platform-auth-card');
|
||||
expect(dialog.className).toContain('!max-w-md');
|
||||
expect(within(dialog).getByText('登录表单')).toBeTruthy();
|
||||
|
||||
fireEvent.click(dialog.parentElement as HTMLElement);
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('keeps escape disabled for auth flows', () => {
|
||||
const onClose = vi.fn();
|
||||
|
||||
render(
|
||||
<PlatformAuthModalShell
|
||||
title="请填写邀请码"
|
||||
platformTheme="dark"
|
||||
onClose={onClose}
|
||||
closeLabel="取消填写邀请码"
|
||||
zIndexClassName="z-[130]"
|
||||
>
|
||||
<div>邀请码表单</div>
|
||||
</PlatformAuthModalShell>,
|
||||
);
|
||||
|
||||
fireEvent.keyDown(window, { key: 'Escape' });
|
||||
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
expect(screen.getByRole('button', { name: '取消填写邀请码' })).toBeTruthy();
|
||||
});
|
||||
59
src/components/auth/PlatformAuthModalShell.tsx
Normal file
59
src/components/auth/PlatformAuthModalShell.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import type { PlatformTheme } from '../../../packages/shared/src/contracts/runtime';
|
||||
import { UnifiedModal } from '../common/UnifiedModal';
|
||||
|
||||
type PlatformAuthModalShellProps = {
|
||||
title: string;
|
||||
platformTheme: PlatformTheme;
|
||||
onClose: () => void;
|
||||
children: ReactNode;
|
||||
closeLabel: string;
|
||||
zIndexClassName?: string;
|
||||
panelClassName?: string;
|
||||
bodyClassName?: string;
|
||||
};
|
||||
|
||||
function joinClassNames(...classNames: Array<string | false | null | undefined>) {
|
||||
return classNames.filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证入口弹窗共享壳层。
|
||||
* 这里只统一主题遮罩、auth card、标题栏和关闭按钮,登录 / 邀请码表单状态仍留在各自业务组件。
|
||||
*/
|
||||
export function PlatformAuthModalShell({
|
||||
title,
|
||||
platformTheme,
|
||||
onClose,
|
||||
children,
|
||||
closeLabel,
|
||||
zIndexClassName = 'z-[120]',
|
||||
panelClassName,
|
||||
bodyClassName = '!p-0',
|
||||
}: PlatformAuthModalShellProps) {
|
||||
return (
|
||||
<UnifiedModal
|
||||
open
|
||||
title={title}
|
||||
onClose={onClose}
|
||||
closeLabel={closeLabel}
|
||||
closeVariant="platformIcon"
|
||||
closeOnBackdrop
|
||||
closeOnEscape={false}
|
||||
portal={false}
|
||||
size="sm"
|
||||
zIndexClassName={zIndexClassName}
|
||||
overlayClassName={`platform-theme platform-theme--${platformTheme} !px-3 !py-4 text-[var(--platform-text-strong)] sm:!p-4`}
|
||||
panelClassName={joinClassNames(
|
||||
'platform-auth-card !rounded-[2rem] sm:!rounded-[2rem]',
|
||||
panelClassName,
|
||||
)}
|
||||
headerClassName="!items-center !px-5 !py-4"
|
||||
titleClassName="text-lg font-semibold text-[var(--platform-text-strong)]"
|
||||
bodyClassName={bodyClassName}
|
||||
>
|
||||
{children}
|
||||
</UnifiedModal>
|
||||
);
|
||||
}
|
||||
@@ -3,9 +3,9 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
import type { PlatformTheme } from '../../../packages/shared/src/contracts/runtime';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
|
||||
import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton';
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
import { PlatformTextField } from '../common/PlatformTextField';
|
||||
import { PlatformAuthModalShell } from './PlatformAuthModalShell';
|
||||
|
||||
type RegistrationInviteModalProps = {
|
||||
isOpen: boolean;
|
||||
@@ -49,70 +49,48 @@ export function RegistrationInviteModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`platform-theme platform-theme--${platformTheme} platform-overlay fixed inset-0 z-[130] flex items-end justify-center px-3 py-4 text-[var(--platform-text-strong)] backdrop-blur-sm sm:items-center sm:p-4`}
|
||||
onClick={onClose}
|
||||
<PlatformAuthModalShell
|
||||
title="请填写邀请码"
|
||||
platformTheme={platformTheme}
|
||||
onClose={onClose}
|
||||
closeLabel="取消填写邀请码"
|
||||
zIndexClassName="z-[130]"
|
||||
panelClassName="!max-w-sm"
|
||||
>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="registration-invite-dialog-title"
|
||||
className="platform-auth-card w-full max-w-sm overflow-hidden rounded-[2rem]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
<form
|
||||
className="flex flex-col gap-4 px-5 py-5"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
if (!normalizedInviteCode) {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
void onSubmit(normalizedInviteCode);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3 border-b border-[var(--platform-subpanel-border)] px-5 py-4">
|
||||
<div
|
||||
id="registration-invite-dialog-title"
|
||||
className="text-lg font-semibold text-[var(--platform-text-strong)]"
|
||||
>
|
||||
请填写邀请码
|
||||
</div>
|
||||
<PlatformModalCloseButton
|
||||
onClick={onClose}
|
||||
label="取消填写邀请码"
|
||||
variant="platformIcon"
|
||||
className="p-2"
|
||||
<label className="grid gap-2">
|
||||
<PlatformFieldLabel variant="form" className="mb-0">
|
||||
邀请码
|
||||
</PlatformFieldLabel>
|
||||
<PlatformTextField
|
||||
autoComplete="off"
|
||||
value={inviteCode}
|
||||
onChange={(event) => setInviteCode(event.target.value)}
|
||||
placeholder="邀请码"
|
||||
/>
|
||||
</div>
|
||||
<form
|
||||
className="flex flex-col gap-4 px-5 py-5"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
if (!normalizedInviteCode) {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
</label>
|
||||
|
||||
void onSubmit(normalizedInviteCode);
|
||||
}}
|
||||
>
|
||||
<label className="grid gap-2">
|
||||
<PlatformFieldLabel variant="form" className="mb-0">
|
||||
邀请码
|
||||
</PlatformFieldLabel>
|
||||
<PlatformTextField
|
||||
autoComplete="off"
|
||||
value={inviteCode}
|
||||
onChange={(event) => setInviteCode(event.target.value)}
|
||||
placeholder="邀请码"
|
||||
/>
|
||||
</label>
|
||||
{error ? (
|
||||
<PlatformStatusMessage tone="error" surface="profile">
|
||||
{error}
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
|
||||
{error ? (
|
||||
<PlatformStatusMessage tone="error" surface="profile">
|
||||
{error}
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
|
||||
<PlatformActionButton
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
size="lg"
|
||||
>
|
||||
{submitting ? '提交中' : normalizedInviteCode ? '提交' : '跳过'}
|
||||
</PlatformActionButton>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<PlatformActionButton type="submit" disabled={submitting} size="lg">
|
||||
{submitting ? '提交中' : normalizedInviteCode ? '提交' : '跳过'}
|
||||
</PlatformActionButton>
|
||||
</form>
|
||||
</PlatformAuthModalShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user