Commit Graph

278 Commits

Author SHA1 Message Date
admin 9a65f2f9ad feat(chat): toggle send button icon by input content 2026-06-22 18:58:57 +08:00
admin 031bad376f fix(subscription): increase EZPAY development redirect delay to 10 seconds 2026-06-22 18:53:49 +08:00
admin 41b9a49c74 copy(chat): update input placeholder 2026-06-22 18:48:36 +08:00
admin 9a3c8f0d12 fix(payment): allow renewing active subscriptions 2026-06-22 18:48:21 +08:00
admin 2cfbaba058 fix(payment): resume ezpay orders only after return 2026-06-22 18:23:59 +08:00
admin 0b8bae5e5f fix(payment): enable ezpay debug delay outside production 2026-06-22 18:10:27 +08:00
admin dd95dd8a26 chore(payment): update ezpay dev redirect notice 2026-06-22 17:37:53 +08:00
admin 21b9954351 feat(payment): restore ezpay return orders 2026-06-22 17:31:04 +08:00
admin 890c955712 fix(payment): support ezpay cashier urls 2026-06-22 16:35:06 +08:00
admin 5ed43a37ce feat(subscription): add payment channel selector 2026-06-22 16:14:34 +08:00
admin ec8c559a19 style(sidebar): refine membership cards 2026-06-22 15:46:48 +08:00
admin b105ba3457 style(sidebar): update voice package icon 2026-06-22 14:58:50 +08:00
admin 375091e828 fix(subscription): prefer Stripe payment dialog 2026-06-22 14:06:46 +08:00
admin d1228324ca fix(auth): enable dev email login testing 2026-06-22 11:27:13 +08:00
admin 61fd4ca916 feat(chat): handle daily message paywall 2026-06-22 10:46:17 +08:00
admin df7f673855 feat(subscription): support voice package mode 2026-06-22 09:53:35 +08:00
admin cf435e8fbb style(subscription): polish plan card styles 2026-06-18 19:42:00 +08:00
admin 52aad5b7f3 style(subscription): refine plan card layout 2026-06-18 19:20:35 +08:00
admin 5bf98e9452 feat(subscription): support Stripe payment element 2026-06-18 18:03:36 +08:00
admin 7354afe93d refactor(ui): extract auth and avatar components 2026-06-18 17:14:02 +08:00
admin 1fc1635341 chore: wire release hook and service updates 2026-06-18 16:18:52 +08:00
admin a347b39001 feat(payment): connect payment service flow 2026-06-18 15:40:59 +08:00
admin e2a89ef063 style(chat): align external browser dialog 2026-06-18 14:30:59 +08:00
admin cf54bc742d fix(chat): limit external browser prompt daily 2026-06-18 14:23:31 +08:00
admin d190de4dda refactor(api): use dto barrel imports 2026-06-18 14:16:00 +08:00
admin 812a3e41b9 refactor(logging): centralize console output 2026-06-18 13:34:19 +08:00
admin f600e11d55 refactor(auth): centralize login status sync 2026-06-18 12:52:49 +08:00
admin 0d0dabaace refactor(utils): add barrel exports 2026-06-18 11:58:50 +08:00
admin b09795a7b8 fix(deploy): force stop existing next service 2026-06-18 11:35:26 +08:00
admin 172ef28060 feat(chat): prompt facebook users to open external browser 2026-06-18 10:59:13 +08:00
admin e4b1690b45 fix(chat): redirect unauthenticated users from chat 2026-06-17 19:44:22 +08:00
admin bded7497d2 fix(chat): show browser hint in development 2026-06-17 18:03:25 +08:00
admin 5d66cef85c refactor(dto): move app models into dto layer 2026-06-17 17:34:55 +08:00
admin f0b5275c55 refactor(sidebar): comment out user info section and membership option 2026-06-17 17:30:16 +08:00
admin afba85e809 fix(auth): update routing for subscription access 2026-06-17 17:11:34 +08:00
admin d074f1a813 fix(auth): hide email option in other sign-in dialog 2026-06-17 17:07:53 +08:00
admin b34533b242 refactor(auth): use icon library for provider icons 2026-06-17 16:58:24 +08:00
admin f39e7e0672 fix(pwa): suppress install dialog in in-app browsers 2026-06-17 16:19:21 +08:00
admin 5c26ad9d5b refactor(splash): remove Athelas font references from CSS files 2026-06-17 16:05:20 +08:00
admin f7201fa654 fix(chat): make send button circular 2026-06-17 15:54:05 +08:00
admin 977c0905bd refactor(chat): remove redundant auth state management from ChatScreen documentation
refactor(chat): update guest chat quota logic to use isProduction utility
fix(user): add TODO for user initialization event on login status change
2026-06-17 15:30:36 +08:00
admin bda1605ce7 fix(auth): redirect to splash after logout 2026-06-17 15:07:16 +08:00
admin 0d2a515e51 fix(sidebar): redirect to splash screen after logout 2026-06-17 14:35:28 +08:00
admin 287affa34a refactor(app): move route screens out of components
Exclude test and spec files from generated barrels so page imports do not load test modules at runtime.
2026-06-17 14:13:42 +08:00
admin cda76a1651 fix(splash): keep login button text on one line 2026-06-17 13:06:56 +08:00
admin c3f2f8d954 fix(sidebar): trigger logout from click 2026-06-17 12:59:48 +08:00
admin d41b3c4473 refactor(schemas): extract nullable-default helpers to shared file
The three defensive Zod helpers (stringOrEmpty, numberOrZero,
booleanOrFalse) used by user.ts are general-purpose Zod utilities
that any schema may need — they're not user-specific. The original
inline declaration in user.ts had two downsides:

1. Any new schema (chat, auth, metrics, etc.) that needs the same
   'null | undefined → default scalar' pattern would have to
   duplicate the helper.

2. The long defensive-parse comment block (11 lines) lived inside
   user.ts and didn't explain the helpers' general purpose.

Move them to src/data/schemas/nullable-defaults.ts (top-level
sibling of auth/, chat/, metrics/, user/ subdirs) so any schema
can import them. Keep the original explanation verbatim at the
top of the new file.

Bundled in this commit:
- user.ts imports the helpers from the new file (drops 11 lines
  of comment + 3 const declarations)
- 3 auto-generated barrel index files regenerated by barrelsby
  to pick up the new exports (chat/, subscription/, stores/user/)
2026-06-17 12:05:09 +08:00
admin 10e6c69af8 fix(pwa): prepare beforeinstallprompt listener early to avoid event loss
The previous install flow had a timing race: the
`beforeinstallprompt` event fires at unpredictable times after
page load (often ~30s+ into the session, when Chrome's heuristic
finally decides the user is engaged). If the listener was
attached only inside the dialog's onClick handler, by the time
the user clicked OK the event had already fired into the void
and the deferred prompt was null — so `install()` returned
"unavailable" every time.

The new flow attaches the listener as early as possible:

1. `pwaUtil.prepareInstallPrompt()` — new public method that
   calls the private `attachListener()`. Idempotent (guard via
   `listenersAttached`).

2. `pwaUtil.canPromptInstall()` — companion getter that returns
   whether a deferred event is currently cached. Useful for UI
   to decide whether to even show the install CTA.

3. `pwa-install-overlay.tsx` — calls `prepareInstallPrompt()`
   right after the isSupported / isInstalled guards. The overlay
   mounts when the user enters /chat, so this is the earliest
   practical point in the user flow.

4. `pwa-screen.tsx` — calls `prepareInstallPrompt()` BOTH at
   module load (synchronous, so the listener is attached before
   the splash screen's useEffects run) AND in a useEffect for
   robustness. The splash screen is the very first screen the
   user sees, so attaching here is the earliest possible.

5. `pwa-install-dialog.tsx` — `handleInstall` is now async,
   and `onInstall` accepts `() => void | Promise<void>`. The
   overlay calls `await pwaUtil.install()` so the deferred
   prompt flow can complete before the dialog unmounts.

Together: the listener is now attached from the moment the
splash screen module loads, the deferred prompt is captured
whenever Chrome decides to fire the event, and the dialog's
install button can reliably `prompt()` against the cached
deferred. The full install flow now works end-to-end.
2026-06-17 12:00:24 +08:00
admin c07a10ee80 refactor(splash): handle Skip redirect in splash screen, decouple SplashButton
Previously, the Skip button (guest login entry) relied on the auth
state machine's `pendingRedirect` flag to trigger the navigation
to /chat after guest login completed. That introduced a
non-obvious coupling: the button dispatched an event, the machine
ran an actor, the auth screen (or splash useEffect) saw the flag
and redirected.

This refactor moves the redirect into the splash screen itself:

- `SplashButton` now takes an `onSkip` prop and no longer knows
  about auth dispatch. Pure presentation component.
- `SplashScreen` provides `handleSkip` that dispatches the
  `AuthGuestLoginSubmitted` event AND immediately calls
  `router.replace(/chat)` for snappier perceived navigation.
- `auth-machine.ts`: `loadingGuestLogin.onDone` no longer sets
  `pendingRedirect: true` (splash already navigated). Sets
  it to `false` explicitly so other screens (auth, sidebar) that
  also react to `pendingRedirect` don't double-navigate if the
  user triggers guest login from a non-splash surface in the future.

No behavior change for the happy path: Skip → /chat works the same.
The refactor is purely about responsibility allocation and
component decoupling.
2026-06-17 11:51:35 +08:00
admin 015ec111bd fix(pwa): rename serwist route from [[...slug]] to [path] to match Serwist's hardcoded param
The Serwist createSerwistRoute factory returns a GET handler with
`params: Promise<{ path: string }>` hardcoded. Next.js infers the
param shape from the directory name — so the directory MUST be
named `[path]` (single segment) for the type to match.

`[[...slug]]` (catch-all) was wrong on two counts:
1. The segment name is `slug`, not `path` (Serwist expects
   exactly `path`).
2. The shape is `string[]` (array), not `string` (Serwist
   expects a single string).

Per the Serwist source comment: 'Asset and chunk names must be at
the top, as our path is /serwist/[path], not /serwist/[...path]'
— all SW output files (sw.js, workbox-XXX.js) are flat, single
segment is sufficient.

The fix is a directory rename only — the route.ts content
(createSerwistRoute call) is unchanged.

Verification:
- pnpm build now succeeds (TypeScript check passes)
- The Serwist route appears in the build output as `●  /serwist/[path]`
  prerendered with `sw.js` and `sw.js.map` static pages
- `pnpm start` + Chrome: `/serwist/sw.js` will be served by the
  route handler and registered by <SerwistProvider>
2026-06-17 11:30:57 +08:00