feat(chat): add promotional external entry flow

This commit is contained in:
2026-07-13 12:43:18 +08:00
parent e5ee9940ca
commit 3752b3b729
44 changed files with 1623 additions and 190 deletions
+27 -38
View File
@@ -2,6 +2,7 @@
import { AuthStorage } from "@/data/storage/auth/auth_storage";
import { UserStorage } from "@/data/storage/user/user_storage";
import type { PendingChatPromotionType } from "@/data/storage/navigation";
import { ROUTES } from "@/router/routes";
export type ExternalEntryTarget =
@@ -18,21 +19,34 @@ export interface ExternalEntryPayload {
export interface ExternalEntryTargetInput {
target?: string | null;
redirect?: string | null;
next?: string | null;
}
export interface ExternalEntryPromotionInput {
mode?: string | null;
promotionType?: string | null;
}
export function resolveExternalEntryPromotionType({
mode,
promotionType,
}: ExternalEntryPromotionInput): PendingChatPromotionType | null {
if (mode?.trim().toLowerCase() !== "promotion") return null;
const normalizedType = promotionType?.trim().toLowerCase();
if (
normalizedType === "voice" ||
normalizedType === "image" ||
normalizedType === "private"
) {
return normalizedType;
}
return null;
}
export function resolveExternalEntryTarget({
target,
redirect,
next,
}: ExternalEntryTargetInput): ExternalEntryTarget {
return (
resolveExplicitRoute(redirect) ??
resolveExplicitRoute(next) ??
resolveTargetAlias(target) ??
ROUTES.chat
);
return resolveTarget(target) ?? ROUTES.chat;
}
export async function persistExternalEntryPayload({
@@ -61,44 +75,19 @@ export async function persistExternalEntryPayload({
await Promise.all(tasks);
}
function resolveExplicitRoute(
value: string | null | undefined,
): ExternalEntryTarget | null {
if (!value) return null;
const route = normalizeRoute(value);
if (
route === ROUTES.chat ||
route === ROUTES.tip ||
route === ROUTES.privateRoom
) {
return route;
}
return null;
}
function resolveTargetAlias(
function resolveTarget(
value: string | null | undefined,
): ExternalEntryTarget | null {
if (!value) return null;
const target = value.trim().toLowerCase();
if (target === "chat") return ROUTES.chat;
if (target === "tip" || target === "tips") {
return ROUTES.tip;
}
if (target === "private-room") {
return ROUTES.privateRoom;
}
if (target === "tip") return ROUTES.tip;
if (target === "private-room") return ROUTES.privateRoom;
return null;
}
function normalizeRoute(value: string): string {
const trimmed = value.trim();
if (!trimmed.startsWith("/") || trimmed.startsWith("//")) return "";
return trimmed.split(/[?#]/, 1)[0] ?? "";
}
function hasValue(value: string | null | undefined): value is string {
return typeof value === "string" && value.trim().length > 0;
}