fix(auth): enable dev email login testing

This commit is contained in:
2026-06-22 11:27:13 +08:00
parent 61fd4ca916
commit d1228324ca
6 changed files with 67 additions and 37 deletions
@@ -23,7 +23,13 @@ import { AuthOtherOptionsDialog } from "./auth-other-options-dialog";
import { AuthProviderIcon } from "./auth-provider-icon"; import { AuthProviderIcon } from "./auth-provider-icon";
import styles from "./auth-facebook-panel.module.css"; import styles from "./auth-facebook-panel.module.css";
export function AuthFacebookPanel() { export interface AuthFacebookPanelProps {
onSwitchToEmail: () => void;
}
export function AuthFacebookPanel({
onSwitchToEmail,
}: AuthFacebookPanelProps) {
const [showOptions, setShowOptions] = useState(false); const [showOptions, setShowOptions] = useState(false);
const { isLoading, errorMessage } = useAuthState(); const { isLoading, errorMessage } = useAuthState();
const authDispatch = useAuthDispatch(); const authDispatch = useAuthDispatch();
@@ -93,6 +99,7 @@ export function AuthFacebookPanel() {
mode="facebook" mode="facebook"
onFacebook={() => handleFacebook()} onFacebook={() => handleFacebook()}
onGoogle={() => handleGoogle()} onGoogle={() => handleGoogle()}
onEmail={onSwitchToEmail}
/> />
</div> </div>
); );
@@ -1,21 +1,9 @@
"use client"; "use client";
/**
* "其他登录方式" 底部弹层
*
* 原始 Dart: lib/ui/auth/widgets/auth_other_options_dialog.dart
* (showModalBottomSheet:白卡 + 顶部圆角 radius28 + drag handle)
*
* 视觉规格(与 Dart 对齐):
* - 固定底部、宽 100%(≤500px)、顶部圆角 28px
* - drag handle 40×4 灰胶囊
* - 标题 "Other Sign-in Options" 24px bold 深色
* - email 模式下渲染 Facebook 按钮(40px 白胶囊 + 边框 + 图标)
* - 始终渲染 Google 按钮
* - Cancel 文本按钮
*/
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { MdEmail } from "react-icons/md";
import { BottomSheet } from "@/app/_components/core/bottom-sheet"; import { BottomSheet } from "@/app/_components/core/bottom-sheet";
import { AppEnvUtil } from "@/utils";
import { AuthProviderIcon } from "./auth-provider-icon"; import { AuthProviderIcon } from "./auth-provider-icon";
import { AuthSocialButton } from "./auth-social-button"; import { AuthSocialButton } from "./auth-social-button";
@@ -26,6 +14,7 @@ export interface AuthOtherOptionsDialogProps {
onClose: () => void; onClose: () => void;
onFacebook: () => void; onFacebook: () => void;
onGoogle: () => void; onGoogle: () => void;
onEmail?: () => void;
/** 当前面板模式:facebook 模式下显示 Email + Googleemail 模式下显示 Facebook + Google。 */ /** 当前面板模式:facebook 模式下显示 Email + Googleemail 模式下显示 Facebook + Google。 */
mode: "facebook" | "email"; mode: "facebook" | "email";
/** 自定义子节点(如嵌入 GIS 按钮) */ /** 自定义子节点(如嵌入 GIS 按钮) */
@@ -37,9 +26,13 @@ export function AuthOtherOptionsDialog({
onClose, onClose,
onFacebook, onFacebook,
onGoogle, onGoogle,
onEmail,
mode, mode,
googleSlot, googleSlot,
}: AuthOtherOptionsDialogProps) { }: AuthOtherOptionsDialogProps) {
const showDevEmailOption =
AppEnvUtil.isDevelopment() && mode === "facebook" && onEmail;
return ( return (
<BottomSheet <BottomSheet
open={open} open={open}
@@ -62,6 +55,18 @@ export function AuthOtherOptionsDialog({
</AuthSocialButton> </AuthSocialButton>
) : null} ) : null}
{showDevEmailOption ? (
<AuthSocialButton
icon={<MdEmail size={20} color="var(--color-auth-text-primary)" />}
onClick={() => {
onClose();
onEmail();
}}
>
Email Sign In
</AuthSocialButton>
) : null}
{googleSlot ? ( {googleSlot ? (
<div className={styles.googleSlot}>{googleSlot}</div> <div className={styles.googleSlot}>{googleSlot}</div>
) : ( ) : (
+3 -1
View File
@@ -18,6 +18,8 @@ export function AuthPanel() {
const switchToFacebook = () => const switchToFacebook = () =>
dispatch({ type: "AuthPanelModeChanged", mode: "facebook" }); dispatch({ type: "AuthPanelModeChanged", mode: "facebook" });
const switchToEmail = () =>
dispatch({ type: "AuthPanelModeChanged", mode: "email" });
const handleBack = () => { const handleBack = () => {
if (state.authPanelMode === "email") { if (state.authPanelMode === "email") {
@@ -32,7 +34,7 @@ export function AuthPanel() {
<AuthBackButton onClick={handleBack} /> <AuthBackButton onClick={handleBack} />
{state.authPanelMode === "facebook" ? ( {state.authPanelMode === "facebook" ? (
<AuthFacebookPanel /> <AuthFacebookPanel onSwitchToEmail={switchToEmail} />
) : ( ) : (
<AuthEmailPanel <AuthEmailPanel
onSwitchToFacebook={() => { onSwitchToFacebook={() => {
+9 -3
View File
@@ -14,9 +14,11 @@ import { AuthTextField } from "./auth-text-field";
import { AuthPrimaryButton } from "./auth-primary-button"; import { AuthPrimaryButton } from "./auth-primary-button";
import { AuthErrorMessage } from "./auth-error-message"; import { AuthErrorMessage } from "./auth-error-message";
import styles from "./email-form.module.css"; import styles from "./email-form.module.css";
import { Logger } from "@/utils"; import { AppEnvUtil, Logger } from "@/utils";
const log = new Logger("AppAuthComponentsEmailLoginForm"); const log = new Logger("AppAuthComponentsEmailLoginForm");
const DEV_DEFAULT_EMAIL = "chanwillian0@gmail.com";
const DEV_DEFAULT_PASSWORD = "12345678";
export interface EmailLoginFormProps { export interface EmailLoginFormProps {
globalError?: string | null; globalError?: string | null;
@@ -28,8 +30,12 @@ export function EmailLoginForm({
isLoading, isLoading,
}: EmailLoginFormProps) { }: EmailLoginFormProps) {
const dispatch = useAuthDispatch(); const dispatch = useAuthDispatch();
const [email, setEmail] = useState(""); const [email, setEmail] = useState(() =>
const [password, setPassword] = useState(""); AppEnvUtil.isDevelopment() ? DEV_DEFAULT_EMAIL : "",
);
const [password, setPassword] = useState(() =>
AppEnvUtil.isDevelopment() ? DEV_DEFAULT_PASSWORD : "",
);
// 本地校验错误(client-side)—— 校验失败时展示第一个非 null 的错误 // 本地校验错误(client-side)—— 校验失败时展示第一个非 null 的错误
// 优先级:local validationError > server globalError // 优先级:local validationError > server globalError
const [validationError, setValidationError] = useState<string | null>(null); const [validationError, setValidationError] = useState<string | null>(null);
+9 -18
View File
@@ -1,21 +1,12 @@
import { SubscriptionScreen } from "@/app/subscription/subscription-screen"; import { Suspense } from "react";
import type { SubscriptionType } from "./subscription-screen"; import { SubscriptionPageClient } from "./subscription-page-client";
import { SubscriptionScreen } from "./subscription-screen";
interface SubscriptionPageProps { export default function SubscriptionPage() {
searchParams: Promise<{ return (
type?: string | string[]; <Suspense fallback={<SubscriptionScreen subscriptionType="vip" />}>
}>; <SubscriptionPageClient />
} </Suspense>
);
function toSubscriptionType(value: string | string[] | undefined): SubscriptionType {
const type = Array.isArray(value) ? value[0] : value;
return type === "voice" ? "voice" : "vip";
}
export default async function SubscriptionPage({
searchParams,
}: SubscriptionPageProps) {
const params = await searchParams;
return <SubscriptionScreen subscriptionType={toSubscriptionType(params.type)} />;
} }
@@ -0,0 +1,19 @@
"use client";
import { useSearchParams } from "next/navigation";
import {
SubscriptionScreen,
type SubscriptionType,
} from "./subscription-screen";
function toSubscriptionType(value: string | null): SubscriptionType {
return value === "voice" ? "voice" : "vip";
}
export function SubscriptionPageClient() {
const searchParams = useSearchParams();
const subscriptionType = toSubscriptionType(searchParams.get("type"));
return <SubscriptionScreen subscriptionType={subscriptionType} />;
}