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:
+43
-2
@@ -4,7 +4,17 @@
|
||||
// not need to be strictly legal here — this is a visual/interaction fixture; real
|
||||
// legality and scoring come from the backend.
|
||||
|
||||
import type { ChatMessage, GameView, MoveRecord, Profile, Seat, Session } from '../model';
|
||||
import type {
|
||||
AccountRef,
|
||||
ChatMessage,
|
||||
GameView,
|
||||
Invitation,
|
||||
MoveRecord,
|
||||
Profile,
|
||||
Seat,
|
||||
Session,
|
||||
Stats,
|
||||
} from '../model';
|
||||
|
||||
export const ME = 'me';
|
||||
|
||||
@@ -20,12 +30,43 @@ export const PROFILE: Profile = {
|
||||
displayName: 'You',
|
||||
preferredLanguage: 'en',
|
||||
timeZone: 'UTC',
|
||||
awayStart: '00:00',
|
||||
awayEnd: '07:00',
|
||||
hintBalance: 3,
|
||||
blockChat: false,
|
||||
blockFriendRequests: false,
|
||||
isGuest: true,
|
||||
isGuest: false,
|
||||
};
|
||||
|
||||
// Seed social/account data for the mock (pnpm start + Playwright). The mock profile
|
||||
// is a durable account so the Stage 8 surfaces (friends, stats, history) are reachable.
|
||||
export const MOCK_FRIENDS: AccountRef[] = [
|
||||
{ accountId: 'ann', displayName: 'Ann' },
|
||||
{ accountId: 'kaya', displayName: 'Kaya' },
|
||||
];
|
||||
|
||||
export const MOCK_INCOMING: AccountRef[] = [{ accountId: 'rick', displayName: 'Rick' }];
|
||||
|
||||
export const MOCK_STATS: Stats = { wins: 7, losses: 4, draws: 1, maxGamePoints: 421, maxWordPoints: 95 };
|
||||
|
||||
export function mockInvitations(): Invitation[] {
|
||||
return [
|
||||
{
|
||||
id: 'inv1',
|
||||
inviter: { accountId: 'kaya', displayName: 'Kaya' },
|
||||
invitees: [{ accountId: ME, displayName: 'You', seat: 1, response: 'pending' }],
|
||||
variant: 'english',
|
||||
turnTimeoutSecs: 86400,
|
||||
hintsAllowed: true,
|
||||
hintsPerPlayer: 1,
|
||||
dropoutTiles: 'remove',
|
||||
status: 'pending',
|
||||
gameId: '',
|
||||
expiresAtUnix: Math.floor(Date.now() / 1000) + 7 * 86400,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function seat(s: number, accountId: string, displayName: string, score: number, isWinner = false): Seat {
|
||||
return { seat: s, accountId, displayName, score, hintsUsed: 0, isWinner };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user