8cab34864e
Migrate all 87 Dart files from /Users/chase/Documents/cozsweet/lib/ui/ to TypeScript React components, replacing Flutter-native features with Web equivalents and reimplementing the BLoC pattern with React Context + useReducer. Highlights - 1:1 BLoC -> Context+useReducer+side-effects mapping for auth, chat, user, and sidebar features (4 providers wired in root-providers.tsx) - 500px MobileShell primitive eliminates per-screen layout boilerplate - Headless Dialog primitive (createPortal + ESC + scroll lock) powers quota / pwa-install / username / pronouns / subscription / other-options dialogs - New integrations/: facebook-sdk, google-identity, pwa-install, media-recorder (Web Speech API + MediaRecorder), browser-detect, chat-websocket (singleton), message-queue (throttled send) - CSS Modules + existing design tokens (no new styling system); extended with breakpoints.css and dimensions.css - Custom hooks: useBreakpoint (useSyncExternalStore), useFullVisibility (IntersectionObserver), usePwaInstall, usePullToRefresh - Vitest + jsdom; 15 unit tests (chat-reducer, auth-validators) - React 19.2.4, Next.js 16.2.7, Zod 4, lottie-react, classnames Workarounds - next.config.ts: experimental.staticGenerationRetryCount = 0 to bypass Next.js 16.2.7 /_global-error workStore prerender bug - src/app/global-error.tsx: required Client Component - src/types/globals.d.ts: centralized window.google / FB / SpeechRecognition declarations Routes (/, /splash, /auth, /chat, /chat/deviceid/[deviceId], /sidebar) all build and lint clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
116 lines
3.1 KiB
TypeScript
116 lines
3.1 KiB
TypeScript
/**
|
|
* 全局 Window 增强
|
|
*
|
|
* 集中声明第三方 SDK 在 window 上挂载的全局变量。
|
|
* 单文件能让 TS 编译器在全局范围找到这些属性。
|
|
*/
|
|
export {};
|
|
|
|
declare global {
|
|
interface Window {
|
|
google?: {
|
|
accounts: {
|
|
id: {
|
|
initialize(params: {
|
|
client_id: string;
|
|
callback: (response: { credential?: string; select_by?: string }) => void;
|
|
auto_select?: boolean;
|
|
cancel_on_tap_outside?: boolean;
|
|
}): void;
|
|
prompt(): void;
|
|
renderButton(
|
|
parent: HTMLElement,
|
|
options: {
|
|
type?: "standard" | "icon";
|
|
theme?: "outline" | "filled_blue" | "filled_black";
|
|
size?: "large" | "medium" | "small";
|
|
text?: "signin_with" | "signup_with" | "continue_with" | "signin";
|
|
shape?: "rectangular" | "pill" | "circle" | "square";
|
|
logo_alignment?: "left" | "center";
|
|
width?: number;
|
|
locale?: string;
|
|
},
|
|
): void;
|
|
disableAutoSelect(): void;
|
|
};
|
|
};
|
|
};
|
|
FB?: {
|
|
init(params: {
|
|
appId: string;
|
|
cookie?: boolean;
|
|
xfbml?: boolean;
|
|
version: string;
|
|
}): void;
|
|
login(
|
|
callback: (response: {
|
|
authResponse?: {
|
|
accessToken: string;
|
|
userID: string;
|
|
expiresIn: number;
|
|
};
|
|
status?: "connected" | "not_authorized" | "unknown";
|
|
}) => void,
|
|
options?: { scope: string },
|
|
): void;
|
|
logout(callback?: () => void): void;
|
|
api(
|
|
path: string,
|
|
callback: (response: {
|
|
id?: string;
|
|
name?: string;
|
|
email?: string;
|
|
picture?: { data?: { url?: string } };
|
|
}) => void,
|
|
): void;
|
|
};
|
|
fbAsyncInit?: () => void;
|
|
|
|
// Web Speech API
|
|
SpeechRecognition?: new () => SpeechRecognitionInstance;
|
|
webkitSpeechRecognition?: new () => SpeechRecognitionInstance;
|
|
}
|
|
|
|
interface SpeechRecognitionInstance extends EventTarget {
|
|
lang: string;
|
|
continuous: boolean;
|
|
interimResults: boolean;
|
|
maxAlternatives: number;
|
|
onresult: ((event: SpeechRecognitionEvent) => void) | null;
|
|
onerror: ((event: SpeechRecognitionErrorEvent) => void) | null;
|
|
onend: ((event: Event) => void) | null;
|
|
onstart: ((event: Event) => void) | null;
|
|
start(): void;
|
|
stop(): void;
|
|
abort(): void;
|
|
}
|
|
|
|
interface SpeechRecognitionEvent extends Event {
|
|
resultIndex: number;
|
|
results: SpeechRecognitionResultList;
|
|
}
|
|
|
|
interface SpeechRecognitionErrorEvent extends Event {
|
|
error: string;
|
|
message: string;
|
|
}
|
|
|
|
interface SpeechRecognitionResultList {
|
|
readonly length: number;
|
|
item(index: number): SpeechRecognitionResult;
|
|
[index: number]: SpeechRecognitionResult;
|
|
}
|
|
|
|
interface SpeechRecognitionResult {
|
|
readonly length: number;
|
|
item(index: number): SpeechRecognitionAlternative;
|
|
isFinal: boolean;
|
|
[index: number]: SpeechRecognitionAlternative;
|
|
}
|
|
|
|
interface SpeechRecognitionAlternative {
|
|
transcript: string;
|
|
confidence: number;
|
|
}
|
|
}
|