feat(avatars): show the uploaded profile photo everywhere
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 2m35s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m17s
CI/CD / Deploy - local stack (db + server + web) (push) Successful in 1m12s

Previously the uploaded profile photo only appeared in a few places (profile,
top bar, leaderboard, public profile); chat, friends, game table, match intro,
post-match roster and private rooms showed the emoji avatar only.

- carry avatarImage end-to-end:
  - server DTOs: FriendDto, SeatPlayerDto, RoomPlayerDto, MatchmakeRequest +
    Player/SeatSlot/PSeat; ResolveProfile now returns avatarImage; FriendDtoFor
    fills it from the profile.
  - client types: Friend, RoomSeat.player, MatchmakingState.players,
    ServerSeatPlayer, SeatPlayer (adds avatarId + avatarImage).
  - signalr-service: send my avatarImage on StartMatchmaking/CreatePrivateRoom;
    carry it through mapRoom.
  - game-store: applyServerState + newOnlineMatch + offline match now populate
    avatarId/avatarImage (seat 0 uses your own profile photo).
- render every avatar through the shared <Avatar> component (image → emoji
  fallback): ChatScreen, FriendsScreen (requests/friends/chats), GameTable
  seats, MatchIntroOverlay, MatchPlayersList, RoomScreen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-17 08:17:27 +03:30
parent e5b48ecb26
commit 4739018488
16 changed files with 85 additions and 47 deletions
+4 -3
View File
@@ -9,7 +9,8 @@ import { useOnlineStore } from "@/lib/online-store";
import { useUIStore } from "@/lib/ui-store";
import { useI18n } from "@/lib/i18n";
import { getService } from "@/lib/online/service";
import { Friend, PresenceStatus, RoomSeat, avatarEmoji } from "@/lib/online/types";
import { Friend, PresenceStatus, RoomSeat } from "@/lib/online/types";
import { Avatar } from "@/components/online/Avatar";
import { cn } from "@/lib/cn";
const STATUS_COLOR: Record<PresenceStatus, string> = {
@@ -210,7 +211,7 @@ export function RoomScreen() {
className="w-full glass rounded-xl p-2.5 flex items-center gap-3 hover:bg-navy-800/80 transition text-start"
>
<span className="relative shrink-0">
<span className="text-2xl">{avatarEmoji(f.avatar)}</span>
<Avatar id={f.avatar} image={f.avatarImage} size={34} />
<span className={cn("absolute -bottom-0.5 ltr:-right-0.5 rtl:-left-0.5 size-3 rounded-full border-2 border-navy-900", STATUS_COLOR[f.status])} />
</span>
<span className="flex-1 min-w-0">
@@ -263,7 +264,7 @@ function SeatCard({
<span className="text-[10px] text-cream/50">{label}</span>
{filled ? (
<>
<span className="text-3xl">{avatarEmoji(seat.player?.avatar ?? "a-fox")}</span>
<Avatar id={seat.player?.avatar ?? "a-fox"} image={seat.player?.avatarImage} size={48} />
<span className="text-sm font-bold text-cream text-center max-w-full truncate">
{seat.player?.displayName}
{seat.kind === "bot" && <span className="text-cream/40"> 🤖</span>}