feat(ui): migrate Flutter UI library to Next.js 16 (App Router)
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>
This commit is contained in:
Vendored
+115
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 全局 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user