Match Figma profile, blocks, auth, and archive backend

هذا الالتزام موجود في:
boutmoun123
2026-06-28 18:46:01 +03:00
الأصل 3298338e70
التزام 36ac36e333
44 ملفات معدلة مع 1245 إضافات و39 حذوفات

عرض الملف

@@ -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)} />