Stage 8: UI social/account/history surfaces
Wire the deferred Stage 7 surfaces end-to-end (UI -> gateway transcode -> backend REST -> existing domain services): friends (incl. one-time friend codes), per-user blocks, friend-game invitations, profile editing + email binding, the statistics screen, and the in-game history + GCG export. Friends gain two add paths (interview decision, a deliberate plan change): one-time 6-digit codes (friend_codes table, 12h TTL, single-use, rate-limited redeem); and play-gated requests (shared game required) where an explicit decline is permanent, an ignored request lapses after 30 days, and a code bypasses a decline. Migration 00006 widens friendships_status_chk and adds friend_codes. Lobby notification badge is poll + push: a new generic `notify` event drives it live; the client polls on open/focus. Language stays a single Settings control that writes through to the durable account's preferred_language. GCG export is finished-only (game.ErrGameActive) and shares/downloads the .gcg file. Tests: backend unit + inttest (friend gate/decline/code, ListInvitations, GetStats, GCG gate), gateway transcode round-trips + notify constructor, UI vitest (codecs, win-rate, share choice) + Playwright social specs. Docs: PLAN (Stage 8 done + refinements + TODO-5), ARCHITECTURE, FUNCTIONAL(+ru), UI_DESIGN, TESTING, module READMEs.
This commit is contained in:
@@ -100,12 +100,84 @@ export interface Profile {
|
||||
displayName: string;
|
||||
preferredLanguage: string;
|
||||
timeZone: string;
|
||||
/** "HH:MM" daily away-window bounds, in timeZone. */
|
||||
awayStart: string;
|
||||
awayEnd: string;
|
||||
hintBalance: number;
|
||||
blockChat: boolean;
|
||||
blockFriendRequests: boolean;
|
||||
isGuest: boolean;
|
||||
}
|
||||
|
||||
/** The full editable profile sent to profileUpdate (overwrites every field). */
|
||||
export interface ProfileUpdate {
|
||||
displayName: string;
|
||||
preferredLanguage: string;
|
||||
timeZone: string;
|
||||
awayStart: string;
|
||||
awayEnd: string;
|
||||
blockChat: boolean;
|
||||
blockFriendRequests: boolean;
|
||||
}
|
||||
|
||||
/** A referenced account with its display name (friend, blocked user, invitee). */
|
||||
export interface AccountRef {
|
||||
accountId: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
/** A freshly issued one-time friend code (the plaintext is returned once). */
|
||||
export interface FriendCode {
|
||||
code: string;
|
||||
expiresAtUnix: number;
|
||||
}
|
||||
|
||||
/** A durable account's lifetime statistics. */
|
||||
export interface Stats {
|
||||
wins: number;
|
||||
losses: number;
|
||||
draws: number;
|
||||
maxGamePoints: number;
|
||||
maxWordPoints: number;
|
||||
}
|
||||
|
||||
/** Settings the inviter chooses for a friend game. */
|
||||
export interface InvitationSettings {
|
||||
variant: Variant;
|
||||
turnTimeoutSecs: number;
|
||||
hintsAllowed: boolean;
|
||||
hintsPerPlayer: number;
|
||||
dropoutTiles: 'remove' | 'return';
|
||||
}
|
||||
|
||||
export interface InvitationInvitee {
|
||||
accountId: string;
|
||||
displayName: string;
|
||||
seat: number;
|
||||
response: 'pending' | 'accepted' | 'declined' | string;
|
||||
}
|
||||
|
||||
export interface Invitation {
|
||||
id: string;
|
||||
inviter: AccountRef;
|
||||
invitees: InvitationInvitee[];
|
||||
variant: Variant;
|
||||
turnTimeoutSecs: number;
|
||||
hintsAllowed: boolean;
|
||||
hintsPerPlayer: number;
|
||||
dropoutTiles: string;
|
||||
status: string;
|
||||
gameId: string;
|
||||
expiresAtUnix: number;
|
||||
}
|
||||
|
||||
/** A finished game's GCG transcript for download/share. */
|
||||
export interface GcgExport {
|
||||
gameId: string;
|
||||
filename: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
token: string;
|
||||
userId: string;
|
||||
@@ -134,4 +206,5 @@ export type PushEvent =
|
||||
| { kind: 'chat_message'; message: ChatMessage }
|
||||
| { kind: 'nudge'; gameId: string; fromUserId: string }
|
||||
| { kind: 'match_found'; gameId: string }
|
||||
| { kind: 'notify'; sub: string }
|
||||
| { kind: 'heartbeat' };
|
||||
|
||||
Reference in New Issue
Block a user