feat: add admin user edit page with avatar, banner, and photo gallery

- Backend: adminGetUser query returns user + photos; adminUpdateUser now
  accepts avatarId/bannerId; new adminAddUserPhoto and adminRemoveUserPhoto
  mutations; AdminUserDetailType added to GraphQL schema
- Frontend: /admin/users/[id] page for editing name, avatar, banner, and
  managing the model photo gallery (upload multiple, delete individually)
- Admin users list: edit button per row linking to the detail page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 13:18:43 +01:00
parent e06a1915f2
commit d021acaf0b
6 changed files with 366 additions and 11 deletions

View File

@@ -1055,6 +1055,8 @@ const ADMIN_UPDATE_USER_MUTATION = gql`
$firstName: String
$lastName: String
$artistName: String
$avatarId: String
$bannerId: String
) {
adminUpdateUser(
userId: $userId
@@ -1062,6 +1064,8 @@ const ADMIN_UPDATE_USER_MUTATION = gql`
firstName: $firstName
lastName: $lastName
artistName: $artistName
avatarId: $avatarId
bannerId: $bannerId
) {
id
email
@@ -1070,6 +1074,7 @@ const ADMIN_UPDATE_USER_MUTATION = gql`
artist_name
role
avatar
banner
date_created
}
}
@@ -1081,6 +1086,8 @@ export async function adminUpdateUser(input: {
firstName?: string;
lastName?: string;
artistName?: string;
avatarId?: string;
bannerId?: string;
}) {
return loggedApiCall(
"adminUpdateUser",
@@ -1111,6 +1118,66 @@ export async function adminDeleteUser(userId: string) {
);
}
const ADMIN_GET_USER_QUERY = gql`
query AdminGetUser($userId: String!) {
adminGetUser(userId: $userId) {
id
email
first_name
last_name
artist_name
slug
role
avatar
banner
description
tags
email_verified
date_created
photos {
id
filename
}
}
}
`;
export async function adminGetUser(userId: string, token?: string) {
return loggedApiCall(
"adminGetUser",
async () => {
const client = token ? getAuthClient(token) : getGraphQLClient();
const data = await client.request<{ adminGetUser: any }>(ADMIN_GET_USER_QUERY, { userId });
return data.adminGetUser;
},
{ userId },
);
}
const ADMIN_ADD_USER_PHOTO_MUTATION = gql`
mutation AdminAddUserPhoto($userId: String!, $fileId: String!) {
adminAddUserPhoto(userId: $userId, fileId: $fileId)
}
`;
export async function adminAddUserPhoto(userId: string, fileId: string) {
return loggedApiCall("adminAddUserPhoto", async () => {
await getGraphQLClient().request(ADMIN_ADD_USER_PHOTO_MUTATION, { userId, fileId });
});
}
const ADMIN_REMOVE_USER_PHOTO_MUTATION = gql`
mutation AdminRemoveUserPhoto($userId: String!, $fileId: String!) {
adminRemoveUserPhoto(userId: $userId, fileId: $fileId)
}
`;
export async function adminRemoveUserPhoto(userId: string, fileId: string) {
return loggedApiCall("adminRemoveUserPhoto", async () => {
await getGraphQLClient().request(ADMIN_REMOVE_USER_PHOTO_MUTATION, { userId, fileId });
});
}
// ─── Admin: Videos ────────────────────────────────────────────────────────────
const ADMIN_LIST_VIDEOS_QUERY = gql`