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:
@@ -32,12 +32,15 @@ type resolveResponse struct {
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// profileResponse is the authenticated account's own profile.
|
||||
// profileResponse is the authenticated account's own profile. AwayStart and AwayEnd
|
||||
// are the daily away window's "HH:MM" local-time bounds (in TimeZone).
|
||||
type profileResponse struct {
|
||||
UserID string `json:"user_id"`
|
||||
DisplayName string `json:"display_name"`
|
||||
PreferredLanguage string `json:"preferred_language"`
|
||||
TimeZone string `json:"time_zone"`
|
||||
AwayStart string `json:"away_start"`
|
||||
AwayEnd string `json:"away_end"`
|
||||
HintBalance int `json:"hint_balance"`
|
||||
BlockChat bool `json:"block_chat"`
|
||||
BlockFriendRequests bool `json:"block_friend_requests"`
|
||||
@@ -149,6 +152,8 @@ func profileResponseFor(acc account.Account) profileResponse {
|
||||
DisplayName: acc.DisplayName,
|
||||
PreferredLanguage: acc.PreferredLanguage,
|
||||
TimeZone: acc.TimeZone,
|
||||
AwayStart: acc.AwayStart.Format(awayTimeLayout),
|
||||
AwayEnd: acc.AwayEnd.Format(awayTimeLayout),
|
||||
HintBalance: acc.HintBalance,
|
||||
BlockChat: acc.BlockChat,
|
||||
BlockFriendRequests: acc.BlockFriendRequests,
|
||||
@@ -156,6 +161,9 @@ func profileResponseFor(acc account.Account) profileResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// awayTimeLayout is the "HH:MM" wire form of the daily away-window bounds.
|
||||
const awayTimeLayout = "15:04"
|
||||
|
||||
// gameDTOFromGame projects a game.Game into its DTO.
|
||||
func gameDTOFromGame(g game.Game) gameDTO {
|
||||
seats := make([]seatDTO, 0, len(g.Seats))
|
||||
|
||||
Reference in New Issue
Block a user