feat: friends panel (#3990)

## Description:

# Add Friends tab to Account modal

## Summary

- Adds a "Friends" tab to the Account modal, alongside Account / Stats /
Games.
- New `<friends-list>` Lit component covering the full friend lifecycle:
send request, accept / deny incoming, withdraw outgoing, remove friend,
paginated list with "Load more".
- New `FriendsApi.ts` wrapping `GET/POST/DELETE /friends*` endpoints
with typed error codes (`not_found` / `conflict` / `bad_request` /
`request_failed`).
- Zod schemas for the friend API responses in `core/ApiSchemas.ts`.
- Translations under a new `friends.*` block in `en.json`.

Friends and pending requests are displayed by public ID via
`copy-button`, matching the existing clan convention.


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evan
This commit is contained in:
Evan
2026-05-23 16:16:16 +01:00
committed by GitHub
parent 545ad313e3
commit fd6cd762e6
5 changed files with 644 additions and 0 deletions
+29
View File
@@ -218,6 +218,35 @@ export type RankedLeaderboardResponse = z.infer<
typeof RankedLeaderboardResponseSchema
>;
export const FriendEntrySchema = z.object({
publicId: z.string(),
createdAt: z.iso.datetime(),
});
export type FriendEntry = z.infer<typeof FriendEntrySchema>;
export const FriendRequestsResponseSchema = z.object({
incoming: FriendEntrySchema.array(),
outgoing: FriendEntrySchema.array(),
});
export type FriendRequestsResponse = z.infer<
typeof FriendRequestsResponseSchema
>;
export const FriendsListResponseSchema = z.object({
results: FriendEntrySchema.array(),
total: z.number(),
page: z.number(),
limit: z.number(),
});
export type FriendsListResponse = z.infer<typeof FriendsListResponseSchema>;
export const SendFriendRequestResponseSchema = z.object({
status: z.enum(["requested", "accepted"]),
});
export type SendFriendRequestResponse = z.infer<
typeof SendFriendRequestResponseSchema
>;
export const NewsItemSchema = z.object({
id: z.string(),
title: z.string(),