fix(auth): handle null backend values and surface login errors on splash

Two related fixes for OAuth login failures:

- `UserSchema`: backend (Facebook/Apple) returns `null` for fields like
  `email`; Zod's `.default("")` rejects `null`, causing silent login
  failures. Introduce `stringOrEmpty` / `numberOrZero` / `booleanOrFalse`
  helpers (`nullable().transform(v => v ?? x).default(x)`) and apply
  across the schema so input accepts `string | null | undefined` while
  output stays the typed primitive.

- `SplashScreen`: render `state.errorMessage` (set by auth state machine
  `onError`, e.g. Facebook sync / schema mismatch) so users no longer
  get stuck on splash without feedback.
This commit is contained in:
2026-06-16 11:10:21 +08:00
parent 00651861ce
commit 29776272ad
2 changed files with 53 additions and 22 deletions
+24 -4
View File
@@ -24,7 +24,7 @@ export function SplashScreen() {
// ─────────────────────────────────────────────────────────────
// useEffect ① 启动时检查 localStorage 里的 loginToken
// - loginTokenOAuth/email sync 写下的)→ 视为"已登录"跳 /chat
// - **不**查 guestToken —— 游客用户**停留 splash**(按你要求)
// - 查 guestToken —— 游客用户停留 splash(按你要求)
// - 都没有 → 留 splash
// proxy 读不到 localStorageEdge runtime),所以这条 fallback 必须在 Client 侧。
// ─────────────────────────────────────────────────────────────
@@ -43,11 +43,11 @@ export function SplashScreen() {
}, [router]);
// ─────────────────────────────────────────────────────────────
// useEffect ② 按钮**刚**派了事件(pendingRedirect=true +
// useEffect ② 按钮派了事件(pendingRedirect=true +
// loginStatus 是非 notLoggedIn → 跳 /chat
//
// **关键**`pendingRedirect` 是 auth context 里的**持久**状态(不是 ref)——
// re-mount 时**不**重置,区分"刚按了"和"历史登录"准确无误。
// 关键`pendingRedirect` 是 auth context 里的持久状态(不是 ref)——
// re-mount 时重置,区分"刚按了"和"历史登录"准确无误。
// 跳完调 `AuthClearPendingRedirect` 置 false(一次性信号)。
// ─────────────────────────────────────────────────────────────
useEffect(() => {
@@ -88,6 +88,26 @@ export function SplashScreen() {
<div className={styles.buttonArea}>
<SplashButton />
</div>
{/*
* 登录错误提示 —— auth state machine 的 onError 会设 errorMessage
* (如 Facebook 同步后端失败 / 后端返回数据 schema 不匹配等)。
* 之前没显示,导致用户卡在 splash 看不到原因。
*/}
{state.errorMessage ? (
<p
role="alert"
style={{
color: "#c0392b",
fontSize: "0.875rem",
marginTop: "1rem",
textAlign: "center",
maxWidth: "320px",
lineHeight: 1.4,
}}
>
{state.errorMessage}
</p>
) : null}
<p className={styles.bottom}>
Elio Silvestri, Your exclusive AI boyfriend
<br />