Merge branch 'dev' into test

This commit is contained in:
2026-06-22 11:28:34 +08:00
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 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 { isLoading, errorMessage } = useAuthState();
const authDispatch = useAuthDispatch();
@@ -93,6 +99,7 @@ export function AuthFacebookPanel() {
mode="facebook"
onFacebook={() => handleFacebook()}
onGoogle={() => handleGoogle()}
onEmail={onSwitchToEmail}
/>
</div>
);
@@ -1,21 +1,9 @@
"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 { MdEmail } from "react-icons/md";
import { BottomSheet } from "@/app/_components/core/bottom-sheet";
import { AppEnvUtil } from "@/utils";
import { AuthProviderIcon } from "./auth-provider-icon";
import { AuthSocialButton } from "./auth-social-button";
@@ -26,6 +14,7 @@ export interface AuthOtherOptionsDialogProps {
onClose: () => void;
onFacebook: () => void;
onGoogle: () => void;
onEmail?: () => void;
/** 当前面板模式:facebook 模式下显示 Email + Googleemail 模式下显示 Facebook + Google。 */
mode: "facebook" | "email";
/** 自定义子节点(如嵌入 GIS 按钮) */
@@ -37,9 +26,13 @@ export function AuthOtherOptionsDialog({
onClose,
onFacebook,
onGoogle,
onEmail,
mode,
googleSlot,
}: AuthOtherOptionsDialogProps) {
const showDevEmailOption =
AppEnvUtil.isDevelopment() && mode === "facebook" && onEmail;
return (
<BottomSheet
open={open}
@@ -62,6 +55,18 @@ export function AuthOtherOptionsDialog({
</AuthSocialButton>
) : null}
{showDevEmailOption ? (
<AuthSocialButton
icon={<MdEmail size={20} color="var(--color-auth-text-primary)" />}
onClick={() => {
onClose();
onEmail();
}}
>
Email Sign In
</AuthSocialButton>
) : null}
{googleSlot ? (
<div className={styles.googleSlot}>{googleSlot}</div>
) : (
+3 -1
View File
@@ -18,6 +18,8 @@ export function AuthPanel() {
const switchToFacebook = () =>
dispatch({ type: "AuthPanelModeChanged", mode: "facebook" });
const switchToEmail = () =>
dispatch({ type: "AuthPanelModeChanged", mode: "email" });
const handleBack = () => {
if (state.authPanelMode === "email") {
@@ -32,7 +34,7 @@ export function AuthPanel() {
<AuthBackButton onClick={handleBack} />
{state.authPanelMode === "facebook" ? (
<AuthFacebookPanel />
<AuthFacebookPanel onSwitchToEmail={switchToEmail} />
) : (
<AuthEmailPanel
onSwitchToFacebook={() => {
+9 -3
View File
@@ -14,9 +14,11 @@ import { AuthTextField } from "./auth-text-field";
import { AuthPrimaryButton } from "./auth-primary-button";
import { AuthErrorMessage } from "./auth-error-message";
import styles from "./email-form.module.css";
import { Logger } from "@/utils";
import { AppEnvUtil, Logger } from "@/utils";
const log = new Logger("AppAuthComponentsEmailLoginForm");
const DEV_DEFAULT_EMAIL = "chanwillian0@gmail.com";
const DEV_DEFAULT_PASSWORD = "12345678";
export interface EmailLoginFormProps {
globalError?: string | null;
@@ -28,8 +30,12 @@ export function EmailLoginForm({
isLoading,
}: EmailLoginFormProps) {
const dispatch = useAuthDispatch();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [email, setEmail] = useState(() =>
AppEnvUtil.isDevelopment() ? DEV_DEFAULT_EMAIL : "",
);
const [password, setPassword] = useState(() =>
AppEnvUtil.isDevelopment() ? DEV_DEFAULT_PASSWORD : "",
);
// 本地校验错误(client-side)—— 校验失败时展示第一个非 null 的错误
// 优先级:local validationError > server globalError
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 {
searchParams: Promise<{
type?: string | string[];
}>;
}
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)} />;
export default function SubscriptionPage() {
return (
<Suspense fallback={<SubscriptionScreen subscriptionType="vip" />}>
<SubscriptionPageClient />
</Suspense>
);
}
@@ -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} />;
}