Merge branch 'dev' into test
This commit is contained in:
@@ -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 + Google;email 模式下显示 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>
|
||||
) : (
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
Reference in New Issue
Block a user