Commit Graph

164 Commits

Author SHA1 Message Date
admin d6f2cb7c56 fix(pwa): enhance PWA install overlay logic for daily display limits and environment handling 2026-06-16 19:05:50 +08:00
admin 5be40949a1 feat(pwa): implement PwaUtil class for PWA support and installation checks 2026-06-16 18:59:17 +08:00
admin 8d786f1e04 fix(chat): align user bubble to match AI bubble for consistent layout 2026-06-16 18:58:42 +08:00
admin 0a9abc2502 fix(auth): stop parsing logout response body (avoids ZodError on null data)
Root cause: backend `/auth/logout` returns `{ success: true, data: null }`
(no business payload for a fire-and-forget endpoint). The previous
`AuthApi.logout` ran `unwrap(env)` (which passes null through, since
null is defined) and then `LogoutResponse.fromJson(null)`, which
Zod-parses as `z.object(...)` and throws "Invalid input: expected
object, received null".

The error was caught in `AuthRepository.logout` with
`console.warn(... clearing local anyway)` — logout actually still
worked (local clear ran), but a noisy red ZodError hit the console on
every logout click.

Fix: align `AuthApi.logout` with the existing fire-and-forget pattern
used by `register` and `sendCode` — call `httpClient` and ignore the
response body. Drop the now-unused `LogoutResponse` import.

`AuthRepository.logout` already discards the return value, so the
`Promise<LogoutResponse> → Promise<void>` signature change is safe.

Bundled in this commit (unrelated):
- chat-machine.actors.ts / user-machine.ts: removed stale design-doc
  comment blocks (IDE/linter cleanup)
- user-machine.actors.ts: removed redundant `userStorage.clearUserData()`
  from `userLogoutActor` — `authRepo.logout` already clears local
  user data, so this was a no-op duplicate.
- sidebar-screen.tsx: removed one stale comment line.
2026-06-16 18:34:55 +08:00
admin 39e7d61c8a fix(auth): re-check auth status after logout; clear NextAuth session after sync
1) Sidebar logout flow:
   - After AuthReset, also dispatch AuthStatusCheckSubmitted so the auth
     machine re-verifies storage (not just resets to initialState). This
     makes sure the redirect to /chat reflects the actual storage state,
     not just the forcibly-cleared context.

2) OAuthSessionSync:
   - After dispatching AuthGoogleSyncSubmitted / AuthFacebookSyncSubmitted,
     call signOut({ redirect: false }) to drop the NextAuth session.
     The OAuth idToken / accessToken has already been handed to the
     backend; clearing the browser-side session prevents lingering OAuth
     credential exposure. Fire-and-forget — once status flips to
     "unauthenticated", the useEffect early-returns so no re-entry.

Note: auth-machine.ts lost a 3-line stale comment block about XState v5
type inference for inline assign; bundled with this commit.
2026-06-16 18:24:23 +08:00
admin a44463b3ee refactor(user): split user-machine into helpers and actors
Mirror the existing chat-module structure (chat-machine.helpers.ts +
chat-machine.actors.ts) for consistency across state-machine modules.

Split:
- user-machine.helpers.ts: pure data layer (no XState dep)
  - userStorage singleton
  - InitData interface
  - toView DTO → UserView mapper
  - readInitData (parallel getUser + getAvatarUrl)
- user-machine.actors.ts: async actor implementations (fromPromise)
  - userRepo / authRepo injection
  - userInitActor / userFetchActor / userLogoutActor
- user-machine.ts: only setup().createMachine() — keeps inline assign
  actions referencing context/event where they belong, imports actors
  (not helpers, per the layered convention).

No behaviour change — only file boundaries moved. Public API (exports of
UserState / UserEvent / initialState / userMachine / UserMachine) stays
identical so external imports are untouched.
2026-06-16 18:12:11 +08:00
admin 8b6e38d3cb feat(chat): prepend greeting message when chat history is empty
On first chat open (or when both local and network return empty),
the chat screen would render completely blank, which feels cold and
broken. Prepend a single AI persona greeting as the first message so
the first impression is warm.

Trigger:
- Final returned messages array is empty
- Either network succeeds with empty result, or network fails and local
  is also empty

Design notes:
- Greeting is NOT persisted to local/network — pure UI-layer fallback.
  Once the user sends a real message, network has content and the
  greeting no longer appears.
- If the user closes the app without ever sending a message and reopens,
  the greeting reappears (intentional: "warm first frame on each cold
  start").
- Rendered as an AI bubble (isFromAI: true) so the visual style matches
  the AI persona.
2026-06-16 17:56:03 +08:00
admin c5686d2749 feat(auth): implement Facebook user data fetching from Graph API and persist profile information 2026-06-16 17:43:19 +08:00
admin 82bf917ace feat(auth): enhance AuthStatusChecker with detailed status change handling and prevention of infinite loops 2026-06-16 17:42:37 +08:00
admin 200fb1642f fix(auth): surface validation errors in email login/register forms
Validation failure was previously silent — the form's submit() returned
without dispatching or showing anything on screen, so users could not tell
why the login/register button appeared to do nothing.

Changes:
- Add validationError local state in EmailLoginForm and EmailRegisterForm
- Display first validation error via AuthErrorMessage, prioritised over
  the server's globalError (validation errors feel more immediate)
- Clear validation error on field change (standard "error fades as you
  fix it" UX) — implemented as onChange handlers, not useEffect, to
  avoid React's cascading-render anti-pattern
- Add observability to the entire email flow (was previously silent end
  to end): form submit ENTRY, validator rejection, actor ENTRY, actor
  DONE — mirrors the logging style already used by guestLoginActor
- Add entry: assign({ errorMessage: null }) to loadingEmailLogin and
  loadingEmailRegister so stale errors clear on transition into the
  loading state (matches loadingGuestLogin)
2026-06-16 17:29:58 +08:00
admin 2792d0c9c5 feat(chat): implement WebSocket message sending and integrate with chat state machine 2026-06-16 16:50:57 +08:00
admin 5764b3c433 feat(ui): replace emoji icons with lucide-react icon components 2026-06-16 16:23:36 +08:00
admin 02b1b713a5 style(splash): set Athelas as primary font and italicize button text
- Prioritize Athelas in --font-system font stack with system fonts as fallback
- Apply italic style to splash button heading and label for typography emphasis
2026-06-16 15:30:23 +08:00
admin dcb6312fa3 feat(chat): add quota-exhausted banner for guest users
Introduce ChatQuotaExhaustedBanner component that displays a pink gradient
banner with an "Unlock your membership to continue" CTA when a guest user's
free chat quota has been exhausted. The banner is shown in ChatScreen when
isGuest is true, quota has loaded, and the chat state machine's
quotaExceededTrigger has been incremented by a quota guard. Default click
navigates to /subscription, with an optional onUnlock callback for
overrides/tests. Styles align with the existing splash and sidebar VIP
card visual language.
2026-06-16 15:18:11 +08:00
admin 9ffa30cc03 style: remove markdown bold syntax from code comments
Remove `**...**` emphasis markers from inline comments and JSDoc blocks across
auth and chat components, machine mappers, and quota helpers. These are
plain code comments, not rendered markdown, so the bold syntax was noise.

Files touched:
- src/app/auth/components/auth-screen.tsx
- src/app/chat/components/chat-header.tsx
- src/app/chat/components/chat-screen.tsx
- chat machine mapper / quota helpers

No functional or behavioral changes.
2026-06-16 14:06:31 +08:00
admin c878959dd7 chore: add use client directive and remove outdated dart reference
- Add "use client" directive to device_identifier.ts to enable client-side usage
- Remove outdated Dart source reference comment in pwa-install-overlay.tsx
2026-06-16 13:23:46 +08:00
admin 50ae420871 chore(config): add /ws path suffix to WebSocket URLs
Update WebSocket base URLs across environment examples and default config to include the required `/ws` path suffix that the backend WebSocket endpoint expects. Applies to development, test/local, and production environments.
2026-06-16 12:35:45 +08:00
admin cba52884a3 chore: clean up migration comments and unused params
Remove obsolete Dart migration notes and route handling comments from splash-button.tsx and auth-machine.ts, and drop the unused `self` parameter from `chatWebSocketActor` callback signature.
2026-06-16 12:01:42 +08:00
admin 29776272ad 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.
2026-06-16 11:10:21 +08:00
admin e44cc7e2f3 feat(sidebar): redesign sidebar UI with three user states (light theme)
Replace the dark sidebar theme with a light-themed three-state UI
matching the design references:

- guest  : pink "login" pill in user row, no status pill on VIP card,
           "Activate VIP Membership" button shown
- member : "VIP membership not activated" subtitle, no status pill,
           "Activate VIP Membership" button shown
- vip    : pink "VIP Member" pill with diamond icon, "Activated" pill
           in VIP card header, no Activate button

Decompose into BackBar, UserHeader, VipBenefitsCard, and VoicePackageCard
under src/app/sidebar/components/. Delete obsolete GuestPanel,
UserInfoCard, and VipCta.

Lift VIP_BENEFITS to a shared src/data/constants/vip-benefits.ts so the
sidebar and subscription page render identical benefit copy.

Add five sidebar tokens (--color-card-surface, --color-voice-gradient-*
--border-card, --shadow-card, --color-pill-bg) to src/tokens/colors.css.

Includes barrelsby regeneration for src/app/sidebar/components and
side-effect barrel updates under src/stores/{auth,chat,sidebar,user},
plus package-lock.json from npm install (required for lint/typecheck).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-16 10:30:21 +08:00
admin 0548a08cbb feat: integrate Stripe payment with subscription plans
Add Stripe payment integration across the project:

- Add @stripe/stripe-js dependency
- Configure Stripe environment variables (secret key, publishable key, webhook secret) for dev, local, and production environments
- Add Product/Price IDs for monthly, quarterly, and annual subscription tiers
- Extend subscription plan data with voiceMinutesPerDay quotas (30/45/60 minutes per tier)
- Update .gitignore to exclude implementation_plan.md
2026-06-16 10:21:02 +08:00
admin 7b347a5c30 style(sidebar): retune user area to dark theme with pink accent
Rebase the sidebar's user information area onto the project's
dark sidebar surface (--color-sidebar-background #0d0b14) and
pink accent (--color-accent #f84d96), replacing the previous
white-card / purple-gradient look that did not align with the
design.

- Shell, back link, profile card, guest panel: switch to dark
  surfaces (#1a1625) with white text.
- VIP badge: use --color-accent pink (was purple fallback).
- VIP CTA + Sign up primary link: use --color-button-gradient
  pink gradient (was purple fallback).
- Sign in secondary link: use dialog border on transparent.
- Override nested SettingsSection elements (list/row/button/svg)
  inside the sidebar's settingsArea so the dark theme carries
  through without modifying the shared core component.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-15 18:50:04 +08:00
admin fd5dfe2715 feat(sidebar): show user info with three-state UI (guest/member/vip)
Sidebar screen now primarily displays user-related information
and derives three render states from loginStatus + currentUser.isVip:

- guest (notLoggedIn): welcome panel + Sign in / Sign up entries
- member (logged in, no VIP): user info card + Get VIP CTA + Log out
- vip (logged in, VIP): user info card with VIP badge + Manage VIP CTA
  + Log out

Adds UserView.isVip (default false), patches user-machine.toView()
to populate it, and introduces three presentational components
(GuestPanel, UserInfoCard, VipCta) co-located under src/app/sidebar.

Preserves existing UserInit + logout-redirect behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-15 18:11:39 +08:00
admin 1449acd6c9 chore(barrels): regenerate barrel files via barrelsby
- Fix chat components barrel to properly export all component modules
- Remove interceptor exports from API services barrel
- Regenerate store barrel files (auth, chat, sidebar, user) using barrelsby
2026-06-15 17:53:15 +08:00
Claude e88a99626d feat(subscription): add VIP subscription page
Add a new /subscription route showing three VIP membership plans (Monthly,
Quarterly, Annual) with a pink→peach banner, per-plan gradient price bars,
a benefits card, a mock activate CTA, and an agreement checkbox.

- New reusable <Checkbox /> in src/app/_components/core for the legal
  agreement toggle
- Hardcoded SUBSCRIPTION_PLANS constant in src/data/constants
- New SubscriptionScreen composed of 7 colocated components (back link,
  user row, banner, plan card, benefits card, CTA button) with CSS
  Modules matching the existing design-token system
- Route registered as protected and reached from /sidebar "Membership"
- Mock CTA: window.alert + router.push(/chat) — no payment calls

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-15 17:49:15 +08:00
admin b9315c9f8b refactor(chat): synchronize guest chat quota updates for messages and images 2026-06-15 17:47:35 +08:00
admin 31f2433c4b refactor: auto-init guest chat quota on first read, clean up logs
- Remove unused `quotaWarningThreshold` and `warningThreshold` getter from `GuestChatQuota` DTO
- Apply get-or-init pattern in `ChatStorage.getGuestDailyChatQuota` and `getGuestTotalQuota`: when no existing value, initialize with DTO defaults (`threshold` = max per day, `totalQuotaDefault`) and return the seeded data
- Fall back to original `null` result if initialization write fails
- Consolidate logging strategy: drop per-actor trace logs in `chat-machine.ts`, keep business-decision logs in 3 assign actions, move all actor ENTRY/API/DONE chains to `chat-machine.actors.ts` and helper load logs to `chat-machine.helpers.ts`
2026-06-15 17:39:50 +08:00
admin fa694af723 docs(plan): add chat state machine refactor design
Update implementation_plan.md with a detailed refactor plan that splits
`chatInitActor` into two independent actors (`loadQuotaActor` +
`loadHistoryActor`) and redesigns the history loading flow to follow
local → network → save semantics so the UI sees local data first,
then network data.

Key changes outlined in the plan:
- Add new events: `ChatQuotaLoaded`, `ChatHistoryLocalLoaded`,
  `ChatHistoryNetworkLoaded`, `ChatHistorySyncDone`
- Remove dead-code `ChatInit` event
- Extend `ChatState` with `quotaLoaded` and `historyLoaded` flags
- Use an `always` barrier in `guestSession.initializing` and
  `userSession.initializing` so both tasks must complete before
  transitioning to `ready`
- Guest init runs `loadQuota` + `loadHistory` in parallel; non-guest
  init runs `chatWebSocket` + `loadHistory` as independent tasks

Also adds `implementation_plan` to .gitignore.
2026-06-15 17:05:32 +08:00
admin 9b28404673 refactor(chat): remove QuotaDialog and related state/effects
Drop the QuotaDialog component invocation in chat-screen along with
its useState/useEffect wiring, and delete the component files. Update
JSDoc in chat-screen and routes to reflect the removed consumer.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-15 16:24:50 +08:00
admin 1f3980d461 chore(chat): refactor chat init into separate quota and history actors
Split `chatInitActor` into `loadQuotaActor` (guest quota fetch) and `loadHistoryActor` (local → network → save sync), and add `quotaLoaded` / `historyLoaded` state flags so the UI can display loading status during the new local-first history hydration flow. Also drop the now-unused `ChatInit` event and pipe Next.js stdout/stderr to a persistent log file in the post-receive hook for easier troubleshooting.
2026-06-15 16:21:40 +08:00
admin cfc6de5a8c style(pwa-install-dialog): tune content text color and margins
Set content color to #393939 and apply 36px horizontal margin to align
with the dialog's visual rhythm.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-15 15:57:46 +08:00
admin ca3adb0f5b chore(router): disable router proxy and re-exports by commenting out
Comment out `src/router/index.ts` and `src/router/proxy.ts` to temporarily
disable the Next.js 16 proxy (auth-driven redirects) and router re-exports.
This effectively turns off the centralized route guard logic and stops the
proxy from intercepting requests until further iteration.
2026-06-15 11:50:09 +08:00
admin 5d1aebdfad refactor(auth): extract helpers and actors out of auth-machine
Move the file-local Helpers block (readGuestId) to auth-helpers.ts and the
Actors block (authRepo + 7 fromPromise actors) to auth-actors.ts. Public API
(authMachine, AuthEvent, AuthState, initialState, AuthMachine) is unchanged;
auth-context.tsx and the barrel index.ts need no edits.
2026-06-15 11:33:05 +08:00
admin c7b02ecfb6 refactor(auth): extract NextAuth config to shared lib module
Move the NextAuth v4 configuration from the route handler
(src/app/api/auth/[...nextauth]/route.ts) into a dedicated module
(src/lib/auth/auth.ts) to establish a single source of truth. The
route file now simply re-exports the handler from the shared
module, keeping provider setup, JWT/session callbacks, and
environment variable references in one reusable location.
2026-06-12 17:56:01 +08:00
admin dbc9a0cd13 refactor(auth): inline NextAuth v4 config and simplify AuthPlatform API
- Inline NextAuth v4 handler directly in `[...nextauth]/route.ts`, removing
  the `@/lib/auth/nextauth` abstraction layer
- Add Google/Facebook providers with JWT/session callbacks that expose
  `idToken` and `accessToken` on the session for backend exchange
- Refactor `AuthPlatform` from constructor-based to static methods
  (`googleSignIn()` / `facebookSignIn()`)
- Update `auth-machine.ts` and layout comment reference to match new structure
- Align with original Dart `nextauth-helpers.{googleLogin, facebookLogin}()`
  naming and keep persistence concerns (unstorage, backend, custom cookies)
  out of scope for this iteration
2026-06-12 17:20:56 +08:00
admin ea339bf621 fix:删除错误配置的代码 2026-06-12 16:43:29 +08:00
admin b36bb2c36b fix(user): make lastMessageAt nullable to handle new backend response 2026-06-12 16:28:33 +08:00
admin 7bc21c1864 chore(auth): add debug logging to guest login flow
Add console.log instrumentation across the guest login pipeline (auth-machine, authRepository, authApi) to trace request lifecycle and surface Zod parsing failures with detailed issue info. Useful for diagnosing lastMessageAt schema mismatches.
2026-06-12 16:21:21 +08:00
admin 0660b90809 fix:无法设置token的问题 2026-06-12 15:55:50 +08:00
admin 513b6ac116 fix:返回消息的时间戳错误问题 2026-06-12 15:19:50 +08:00
admin d64ad0f130 refactor(chat): extract ai disclosure into dedicated banner component
- Move AiDisclosure and EmptyState logic into a new AiDisclosureBanner component
- Remove isGuest prop from ChatArea as it's no longer needed there
- Update styling with explicit color values for better visibility
2026-06-12 12:12:13 +08:00
admin 57992bffac chore(chat): clean up legacy state machine and fix bubble alignment
- Remove redundant `loadingMoreHistory` and `ready` states from chat machine, consolidating history loading flow
- Add `align-self: flex-start` to chat row container for proper alignment
- Clean up commented-out margin rules in text bubble styles
- Update deploy script GIT_REMOTE from "server" to "test"
2026-06-12 11:59:34 +08:00
admin a9938b8dae refactor(chat): wire sendMessageHttpActor for HTTP message sending and update response handling 2026-06-12 11:45:46 +08:00
admin 38f060bbd8 refactor(chat): extract XState actors into chat-machine.actors.ts
- Split chat state machine actors (chatInit, sendMessageHttp, loadMoreHistory, chatWebSocket) into a dedicated module
- Add comprehensive console logging across the chat state machine for full trace debugging
- Comment out margin on text bubble CSS for layout adjustment
- Add debug logging to chat input bar send handler
- Clean up redundant comments in auth-screen component
2026-06-12 11:20:19 +08:00
admin 6394c8ba7b refactor(chat): remove legacy screen visibility events and use absolute state refs
- Remove ChatScreenVisible and ChatScreenInvisible events from chat state machine
- Remove legacy ChatInit event handler
- Migrate state transitions to use absolute references (#chat.guestSession, #chat.userSession, #chat.idle)
- Adjust legal text wording for Cozsweet privacy agreement
2026-06-11 19:10:01 +08:00
admin 1b13e4fa19 fix(auth): add inline auto styles to logo Image to silence dimension warning
Next.js Image scans only inline style for 'auto' to determine whether
sizing is fully delegated to CSS. The .logo class already sets
height: var(--auth-logo-height) and width: auto, but the inline auto
hint is required to suppress the width/height-modified warning.

Applies to both auth-facebook-panel and auth-email-panel where the same
login logo is rendered.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-11 18:59:45 +08:00
admin bdc607c6c5 fix(auth): update legal text and center icon+label in auth buttons
- Update auth-legal-text copy to "I agree to the Cozsweet Privacy
  Agreement and Terms of User", keeping the two external links.
- Center the Facebook icon and "Login with Facebook" label as a group
  inside the main Facebook button (change .facebookLabel flex).
- Apply the same centering to the shared AuthSocialButton .label rule
  so the Email and Facebook entries in the "Other Sign-in Options"
  bottom sheet also render icon+text centered.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-11 18:58:12 +08:00
admin d55694a73e refactor(chat): move WebSocket lifecycle into chat machine via fromCallback actor
- chat-screen no longer manages WebSocket directly; it now dispatches three
  auth lifecycle events (ChatGuestLogin, ChatNonGuestLogin, ChatLogout)
  derived from auth.loginStatus
- chat-machine internally owns the WebSocket long-lived actor via
  fromCallback, completing the previously deferred migration scope
- removed ChatWebSocket import, getWsToken helper, and direct WS
  effect from chat-screen; renamed dispatchInitialLoad to
  dispatchAuthLifecycle to reflect the new responsibility
- decouples auth from chat machine via event-driven boundary: machine
  stays unaware of loginStatus, guest semantics live at the dispatch
  layer (guest → skip WS, non-guest → connect with token)
2026-06-11 18:57:11 +08:00
admin 4b356f58f2 refactor(chat): remove unused ChatScreenVisible and ChatScreenInvisible events
The screen-lifecycle events were dead code: the transitions in chat-machine
(idle <-> ready) had no caller behavior tied to them, and the dispatches
in chat-screen.tsx were the only senders. Drop the union members, both
state transitions, and the mount-effect dispatches.

The idle state remains declared as initial but is no longer reachable
at runtime; can be refactored in a follow-up.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-11 18:39:50 +08:00
admin ed4ccddae3 refactor(chat): decouple chat state from auth and gate quota alerts to guests
- Remove ChatAuthStatusChanged dispatch from auth login flow; chat screen now subscribes directly to auth state machine (loginStatus) to derive isGuest and drive WS lifecycle
- Gate quota-exceeded and warning dialogs to guests only (non-guests have no message limit)
- Dispatch ChatLoadMoreHistory for non-guest initial load vs ChatInit for guests
- Fix AppConstants import path from @/core/constants/app_constants to @/core/app_constants
- Add defensive isGuest checks in quota effects to prevent non-guest quota triggers
2026-06-11 18:31:08 +08:00