3a823ca7ef
Add email / telegram_linked / vk_linked to the Profile (fbs table + regenerated Go/TS bindings, gateway ProfileResp + encodeProfile, backend DTO, UI model + decode). They are filled outside the pure projection — Server.profileResponse now reads the account's identities (like the banner seam) — and will drive the profile's Add / Unlink / change-email controls.
295 lines
7.5 KiB
TypeScript
295 lines
7.5 KiB
TypeScript
// Seed data for the mock transport. Enough to exercise the playable slice locally
|
||
// (pnpm start) with no backend: a profile, one active mid-game whose board already
|
||
// has tiles, and two finished games. Coordinates are 0-indexed (centre 7,7). Words do
|
||
// not need to be strictly legal here — this is a visual/interaction fixture; real
|
||
// legality and scoring come from the backend.
|
||
|
||
import type {
|
||
AccountRef,
|
||
ChatMessage,
|
||
GameView,
|
||
Invitation,
|
||
MoveRecord,
|
||
Profile,
|
||
Seat,
|
||
Session,
|
||
Stats,
|
||
} from '../model';
|
||
|
||
export const ME = 'me';
|
||
|
||
export const SESSION: Session = {
|
||
token: 'mock-token',
|
||
userId: ME,
|
||
isGuest: true,
|
||
displayName: 'You',
|
||
};
|
||
|
||
export const PROFILE: Profile = {
|
||
userId: ME,
|
||
displayName: 'You',
|
||
preferredLanguage: 'en',
|
||
timeZone: 'UTC',
|
||
awayStart: '00:00',
|
||
awayEnd: '07:00',
|
||
hintBalance: 3,
|
||
blockChat: false,
|
||
blockFriendRequests: false,
|
||
isGuest: false,
|
||
notificationsInAppOnly: true,
|
||
// Every variant enabled, so the mock-driven UI offers the full New Game picker.
|
||
variantPreferences: ['erudit_ru', 'scrabble_ru', 'scrabble_en'],
|
||
email: 'you@example.com',
|
||
telegramLinked: false,
|
||
vkLinked: false,
|
||
};
|
||
|
||
// Seed social/account data for the mock (pnpm start + Playwright). The mock profile
|
||
// is a durable account so the social surfaces (friends, stats, history) are reachable.
|
||
// Ann is the active game's opponent but deliberately not a friend, so the in-game
|
||
// "add to friends" flow is demonstrable; Kaya (a finished-game opponent) is the friend.
|
||
export const MOCK_FRIENDS: AccountRef[] = [{ 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: 134,
|
||
moves: 248, // plays across all games
|
||
hintsUsed: 12, // -> hint share 12/248 = 4.8%
|
||
// Letters are lower-cased as the backend emits them; the tile renderer upper-cases for
|
||
// display. The 'd' in "wonderful" is a blank (value 0) to exercise wildcard rendering.
|
||
// Erudit is absent on purpose, so the screen demonstrates skipping a not-yet-played variant.
|
||
bestMoves: [
|
||
{
|
||
variant: 'scrabble_en',
|
||
score: 134,
|
||
word: [
|
||
{ letter: 'w', value: 4, blank: false },
|
||
{ letter: 'o', value: 1, blank: false },
|
||
{ letter: 'n', value: 1, blank: false },
|
||
{ letter: 'd', value: 0, blank: true },
|
||
{ letter: 'e', value: 1, blank: false },
|
||
{ letter: 'r', value: 1, blank: false },
|
||
{ letter: 'f', value: 4, blank: false },
|
||
{ letter: 'u', value: 1, blank: false },
|
||
{ letter: 'l', value: 1, blank: false },
|
||
],
|
||
},
|
||
{
|
||
variant: 'scrabble_ru',
|
||
score: 88,
|
||
word: [
|
||
{ letter: 'с', value: 1, blank: false },
|
||
{ letter: 'ъ', value: 10, blank: false },
|
||
{ letter: 'ё', value: 4, blank: false },
|
||
{ letter: 'м', value: 2, blank: false },
|
||
{ letter: 'к', value: 2, blank: false },
|
||
{ letter: 'а', value: 1, blank: false },
|
||
],
|
||
},
|
||
],
|
||
};
|
||
|
||
export function mockInvitations(): Invitation[] {
|
||
return [
|
||
{
|
||
id: 'inv1',
|
||
inviter: { accountId: 'kaya', displayName: 'Kaya' },
|
||
invitees: [{ accountId: ME, displayName: 'You', seat: 1, response: 'pending' }],
|
||
variant: 'scrabble_ru',
|
||
turnTimeoutSecs: 86400,
|
||
hintsAllowed: true,
|
||
hintsPerPlayer: 1,
|
||
multipleWordsPerTurn: false,
|
||
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 };
|
||
}
|
||
|
||
function play(
|
||
player: number,
|
||
dir: 'H' | 'V',
|
||
tiles: Array<[number, number, string]>,
|
||
words: string[],
|
||
score: number,
|
||
total: number,
|
||
): MoveRecord {
|
||
const ts = tiles.map(([row, col, letter]) => ({ row, col, letter, blank: false }));
|
||
return {
|
||
player,
|
||
action: 'play',
|
||
dir,
|
||
mainRow: ts[0]?.row ?? 7,
|
||
mainCol: ts[0]?.col ?? 7,
|
||
tiles: ts,
|
||
words,
|
||
count: words.length,
|
||
score,
|
||
total,
|
||
};
|
||
}
|
||
|
||
export interface MockGame {
|
||
view: GameView;
|
||
moves: MoveRecord[];
|
||
rack: string[];
|
||
bagLen: number;
|
||
hintsRemaining: number;
|
||
chat: ChatMessage[];
|
||
}
|
||
|
||
// --- active game G1: scrabble_en, You (seat 0) vs Ann (seat 1), your turn ---
|
||
|
||
const G1_MOVES: MoveRecord[] = [
|
||
play(0, 'H', [
|
||
[7, 5, 'H'],
|
||
[7, 6, 'E'],
|
||
[7, 7, 'L'],
|
||
[7, 8, 'L'],
|
||
[7, 9, 'O'],
|
||
], ['HELLO'], 16, 16),
|
||
play(1, 'V', [
|
||
[6, 9, 'W'],
|
||
[8, 9, 'R'],
|
||
[9, 9, 'L'],
|
||
[10, 9, 'D'],
|
||
], ['WORLD'], 9, 9),
|
||
play(0, 'H', [
|
||
[8, 10, 'A'],
|
||
[8, 11, 'T'],
|
||
], ['RAT'], 3, 19),
|
||
play(1, 'V', [
|
||
[9, 10, 'N'],
|
||
[10, 10, 'D'],
|
||
], ['AND'], 4, 13),
|
||
];
|
||
|
||
function activeGame(): MockGame {
|
||
return {
|
||
view: {
|
||
id: 'g1',
|
||
variant: 'scrabble_en',
|
||
dictVersion: 'v1',
|
||
vsAi: false,
|
||
unreadChat: false,
|
||
unreadMessages: false,
|
||
status: 'active',
|
||
players: 2,
|
||
toMove: 0,
|
||
turnTimeoutSecs: 86400,
|
||
multipleWordsPerTurn: true,
|
||
moveCount: G1_MOVES.length,
|
||
endReason: '',
|
||
lastActivityUnix: Math.floor(Date.now() / 1000) - 7200,
|
||
seats: [seat(0, ME, 'You', 19), seat(1, 'ann', 'Ann', 13)],
|
||
},
|
||
moves: G1_MOVES,
|
||
rack: ['R', 'E', 'T', 'I', 'N', 'A', '?'],
|
||
bagLen: 58,
|
||
hintsRemaining: 1,
|
||
chat: [
|
||
{
|
||
id: 'c1',
|
||
gameId: 'g1',
|
||
senderId: 'ann',
|
||
kind: 'message',
|
||
body: 'good luck!',
|
||
createdAtUnix: Math.floor(Date.now() / 1000) - 3600,
|
||
},
|
||
],
|
||
};
|
||
}
|
||
|
||
// --- finished games ---
|
||
|
||
function finishedG2(): MockGame {
|
||
return {
|
||
view: {
|
||
id: 'g2',
|
||
variant: 'scrabble_en',
|
||
dictVersion: 'v1',
|
||
vsAi: false,
|
||
unreadChat: false,
|
||
unreadMessages: false,
|
||
status: 'finished',
|
||
players: 2,
|
||
toMove: 0,
|
||
turnTimeoutSecs: 86400,
|
||
multipleWordsPerTurn: true,
|
||
moveCount: 2,
|
||
endReason: 'normal',
|
||
lastActivityUnix: Math.floor(Date.now() / 1000) - 86400,
|
||
seats: [seat(0, ME, 'You', 320, true), seat(1, 'kaya', 'Kaya', 281)],
|
||
},
|
||
moves: [
|
||
play(0, 'H', [
|
||
[7, 6, 'Q'],
|
||
[7, 7, 'U'],
|
||
[7, 8, 'I'],
|
||
[7, 9, 'Z'],
|
||
], ['QUIZ'], 48, 48),
|
||
play(1, 'V', [
|
||
[6, 9, 'J'],
|
||
[8, 9, 'A'],
|
||
[9, 9, 'M'],
|
||
], ['JAZM'], 30, 30),
|
||
],
|
||
rack: [],
|
||
bagLen: 0,
|
||
hintsRemaining: 0,
|
||
chat: [],
|
||
};
|
||
}
|
||
|
||
function finishedG3(): MockGame {
|
||
return {
|
||
view: {
|
||
id: 'g3',
|
||
variant: 'scrabble_ru',
|
||
dictVersion: 'v1',
|
||
vsAi: false,
|
||
unreadChat: false,
|
||
unreadMessages: false,
|
||
status: 'finished',
|
||
players: 2,
|
||
toMove: 0,
|
||
turnTimeoutSecs: 86400,
|
||
multipleWordsPerTurn: false,
|
||
moveCount: 1,
|
||
endReason: 'resignation',
|
||
lastActivityUnix: Math.floor(Date.now() / 1000) - 172800,
|
||
seats: [seat(0, ME, 'You', 150), seat(1, 'rick', 'Rick', 212, true)],
|
||
},
|
||
moves: [
|
||
play(0, 'H', [
|
||
[7, 6, 'С'],
|
||
[7, 7, 'Л'],
|
||
[7, 8, 'О'],
|
||
[7, 9, 'В'],
|
||
[7, 10, 'О'],
|
||
], ['СЛОВО'], 12, 12),
|
||
],
|
||
rack: [],
|
||
bagLen: 0,
|
||
hintsRemaining: 0,
|
||
chat: [],
|
||
};
|
||
}
|
||
|
||
export function seedGames(): Map<string, MockGame> {
|
||
const m = new Map<string, MockGame>();
|
||
for (const g of [activeGame(), finishedG2(), finishedG3()]) m.set(g.view.id, g);
|
||
return m;
|
||
}
|