Match Figma profile, blocks, auth, and archive backend
هذا الالتزام موجود في:
@@ -73,6 +73,13 @@ function buildEditPayload(user: ApiUser): Partial<ApiUser> {
|
||||
stageName: user.stageName ?? "",
|
||||
bio: user.bio ?? "",
|
||||
location: user.location ?? "",
|
||||
avatar: user.avatar ?? "",
|
||||
coverImage: user.coverImage ?? "",
|
||||
musicRoles: user.musicRoles ?? [],
|
||||
favoriteMaqamat: user.favoriteMaqamat ?? [],
|
||||
preferredMood: user.preferredMood ?? "",
|
||||
experienceLevel: user.experienceLevel ?? "",
|
||||
favoriteInstruments: user.favoriteInstruments ?? [],
|
||||
isPrivate: user.isPrivate ?? false,
|
||||
isVerified: user.isVerified ?? false,
|
||||
};
|
||||
@@ -82,6 +89,20 @@ function normalizeComparableString(value: string | null | undefined) {
|
||||
return (value ?? "").trim();
|
||||
}
|
||||
|
||||
function normalizeStringList(value: unknown): string[] {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => String(item).trim()).filter(Boolean);
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function areStringListsEqual(left: string[], right: string[]) {
|
||||
return left.length === right.length && left.every((item, index) => item === right[index]);
|
||||
}
|
||||
|
||||
function buildUserUpdatePayload(
|
||||
selectedUser: ApiUser,
|
||||
editPayload: Partial<ApiUser>,
|
||||
@@ -145,6 +166,58 @@ function buildUserUpdatePayload(
|
||||
payload.location = nextLocation;
|
||||
}
|
||||
|
||||
const nextAvatar = normalizeComparableString(
|
||||
typeof editPayload.avatar === "string" ? editPayload.avatar : selectedUser.avatar,
|
||||
);
|
||||
const currentAvatar = normalizeComparableString(selectedUser.avatar);
|
||||
if (nextAvatar !== currentAvatar) {
|
||||
payload.avatar = nextAvatar;
|
||||
}
|
||||
|
||||
const nextCoverImage = normalizeComparableString(
|
||||
typeof editPayload.coverImage === "string" ? editPayload.coverImage : selectedUser.coverImage,
|
||||
);
|
||||
const currentCoverImage = normalizeComparableString(selectedUser.coverImage);
|
||||
if (nextCoverImage !== currentCoverImage) {
|
||||
payload.coverImage = nextCoverImage;
|
||||
}
|
||||
|
||||
const nextMusicRoles = normalizeStringList(editPayload.musicRoles ?? selectedUser.musicRoles);
|
||||
const currentMusicRoles = normalizeStringList(selectedUser.musicRoles);
|
||||
if (!areStringListsEqual(nextMusicRoles, currentMusicRoles)) {
|
||||
payload.musicRoles = nextMusicRoles;
|
||||
}
|
||||
|
||||
const nextFavoriteMaqamat = normalizeStringList(editPayload.favoriteMaqamat ?? selectedUser.favoriteMaqamat);
|
||||
const currentFavoriteMaqamat = normalizeStringList(selectedUser.favoriteMaqamat);
|
||||
if (!areStringListsEqual(nextFavoriteMaqamat, currentFavoriteMaqamat)) {
|
||||
payload.favoriteMaqamat = nextFavoriteMaqamat;
|
||||
}
|
||||
|
||||
const nextFavoriteInstruments = normalizeStringList(
|
||||
editPayload.favoriteInstruments ?? selectedUser.favoriteInstruments,
|
||||
);
|
||||
const currentFavoriteInstruments = normalizeStringList(selectedUser.favoriteInstruments);
|
||||
if (!areStringListsEqual(nextFavoriteInstruments, currentFavoriteInstruments)) {
|
||||
payload.favoriteInstruments = nextFavoriteInstruments;
|
||||
}
|
||||
|
||||
const nextPreferredMood = normalizeComparableString(
|
||||
typeof editPayload.preferredMood === "string" ? editPayload.preferredMood : selectedUser.preferredMood,
|
||||
);
|
||||
const currentPreferredMood = normalizeComparableString(selectedUser.preferredMood);
|
||||
if (nextPreferredMood !== currentPreferredMood) {
|
||||
payload.preferredMood = nextPreferredMood;
|
||||
}
|
||||
|
||||
const nextExperienceLevel = normalizeComparableString(
|
||||
typeof editPayload.experienceLevel === "string" ? editPayload.experienceLevel : selectedUser.experienceLevel,
|
||||
);
|
||||
const currentExperienceLevel = normalizeComparableString(selectedUser.experienceLevel);
|
||||
if (nextExperienceLevel !== currentExperienceLevel) {
|
||||
payload.experienceLevel = nextExperienceLevel;
|
||||
}
|
||||
|
||||
const nextIsPrivate = Boolean(editPayload.isPrivate ?? selectedUser.isPrivate ?? false);
|
||||
const currentIsPrivate = Boolean(selectedUser.isPrivate ?? false);
|
||||
if (nextIsPrivate !== currentIsPrivate) {
|
||||
@@ -167,13 +240,17 @@ function FieldRow({
|
||||
valueClassName,
|
||||
}: {
|
||||
label: string;
|
||||
value: string | number | boolean | null | undefined;
|
||||
value: string | string[] | number | boolean | null | undefined;
|
||||
valueDir?: "auto" | "rtl" | "ltr";
|
||||
valueClassName?: string;
|
||||
}) {
|
||||
const normalized =
|
||||
value === null || value === undefined || value === ""
|
||||
? "-"
|
||||
: Array.isArray(value)
|
||||
? value.length
|
||||
? value.join(", ")
|
||||
: "-"
|
||||
: typeof value === "boolean"
|
||||
? value
|
||||
? "نعم"
|
||||
@@ -786,6 +863,67 @@ export default function UsersPage() {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<Input
|
||||
placeholder="Avatar URL"
|
||||
value={String(editPayload.avatar ?? "")}
|
||||
onChange={(event) =>
|
||||
setEditPayload((prev) => ({ ...prev, avatar: event.target.value }))
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Cover image URL"
|
||||
value={String(editPayload.coverImage ?? "")}
|
||||
onChange={(event) =>
|
||||
setEditPayload((prev) => ({ ...prev, coverImage: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<Input
|
||||
placeholder="Music roles, comma separated"
|
||||
value={normalizeStringList(editPayload.musicRoles).join(", ")}
|
||||
onChange={(event) =>
|
||||
setEditPayload((prev) => ({ ...prev, musicRoles: normalizeStringList(event.target.value) }))
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Experience level"
|
||||
value={String(editPayload.experienceLevel ?? "")}
|
||||
onChange={(event) =>
|
||||
setEditPayload((prev) => ({ ...prev, experienceLevel: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<Input
|
||||
placeholder="Favorite maqamat, comma separated"
|
||||
value={normalizeStringList(editPayload.favoriteMaqamat).join(", ")}
|
||||
onChange={(event) =>
|
||||
setEditPayload((prev) => ({
|
||||
...prev,
|
||||
favoriteMaqamat: normalizeStringList(event.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Favorite instruments, comma separated"
|
||||
value={normalizeStringList(editPayload.favoriteInstruments).join(", ")}
|
||||
onChange={(event) =>
|
||||
setEditPayload((prev) => ({
|
||||
...prev,
|
||||
favoriteInstruments: normalizeStringList(event.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
placeholder="Preferred mood"
|
||||
value={String(editPayload.preferredMood ?? "")}
|
||||
onChange={(event) =>
|
||||
setEditPayload((prev) => ({ ...prev, preferredMood: event.target.value }))
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
disabled={!canManageUsers}
|
||||
value={String(editPayload.role ?? selectedUser.role ?? "user")}
|
||||
@@ -859,6 +997,13 @@ export default function UsersPage() {
|
||||
<FieldRow label="Role" value={selectedUser.role} />
|
||||
<FieldRow label="Email" value={selectedUser.email} valueDir="ltr" />
|
||||
<FieldRow label="Location" value={selectedUser.location} />
|
||||
<FieldRow label="Avatar" value={selectedUser.avatar} valueDir="ltr" />
|
||||
<FieldRow label="Cover" value={selectedUser.coverImage} valueDir="ltr" />
|
||||
<FieldRow label="Music roles" value={selectedUser.musicRoles} />
|
||||
<FieldRow label="Favorite maqamat" value={selectedUser.favoriteMaqamat} />
|
||||
<FieldRow label="Preferred mood" value={selectedUser.preferredMood} />
|
||||
<FieldRow label="Experience" value={selectedUser.experienceLevel} />
|
||||
<FieldRow label="Instruments" value={selectedUser.favoriteInstruments} />
|
||||
<FieldRow label="Verified" value={selectedUser.isVerified} />
|
||||
<FieldRow label="Disabled" value={selectedUser.isDisabled} />
|
||||
<FieldRow label="Created at" value={formatDateTime(selectedUser.createdAt)} />
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم