Updated the alert message in the UserEditModal component to improve code readability by formatting the function call across multiple lines. This change enhances maintainability and aligns with the project's coding standards.
276 lines
9.0 KiB
TypeScript
276 lines
9.0 KiB
TypeScript
'use client'
|
||
|
||
import { zodResolver } from '@hookform/resolvers/zod'
|
||
import { useEffect, useState } from 'react'
|
||
import { FormProvider, useForm } from 'react-hook-form'
|
||
|
||
import { addToast } from '@/lib/toast'
|
||
import type { AdminUserDetail } from '@/services/adminUserDetail'
|
||
import Modal from '@/components/modals/Modal'
|
||
import Input from '@/components/formElements/Input'
|
||
import { AdminFormSection } from '@/components/forms/AdminFormLayout'
|
||
import UnsavedChangesIndicator from '@/components/forms/UnsavedChangesIndicator'
|
||
import { ADMIN_UPDATE_USER, type AdminUpdateUserPayload } from '@/services/adminUsers'
|
||
import { API_ROUTES } from '@/services/config'
|
||
import { AdminUserEditValidation, type AdminUserEditValues } from '@/validation/adminUsers'
|
||
import useAlertModal from '@/hooks/useAlertModal'
|
||
import axiosInstance from '@/config/axios'
|
||
|
||
const STATUS_OPTIONS = [
|
||
{ id: 'active', name: 'فعال' },
|
||
{ id: 'suspended', name: 'معلق' },
|
||
]
|
||
|
||
const HOST_PLAN_OPTIONS = [
|
||
{ id: 'free', name: 'رایگان (حداکثر ۱۰ ایونت)' },
|
||
{ id: 'unlimited', name: 'نامحدود' },
|
||
]
|
||
|
||
const GENDER_OPTIONS = [
|
||
{ id: 'male', name: 'مرد' },
|
||
{ id: 'female', name: 'زن' },
|
||
{ id: 'other', name: 'سایر' },
|
||
]
|
||
|
||
interface CityOption {
|
||
id: number
|
||
name: string
|
||
}
|
||
|
||
const toFormValues = (user: AdminUserDetail): AdminUserEditValues => ({
|
||
firstName: user.firstName ?? '',
|
||
lastName: user.lastName ?? '',
|
||
gender: user.gender ?? '',
|
||
cityId: user.cityId ? String(user.cityId) : '',
|
||
avatarUrl: user.avatarUrl ?? '',
|
||
bio: user.bio ?? '',
|
||
status: user.status === 'suspended' ? 'suspended' : 'active',
|
||
hostPlan: user.hostPlan ?? 'free',
|
||
})
|
||
|
||
interface UserEditModalProps {
|
||
isOpen: boolean
|
||
onOpenChange: (isOpen: boolean) => void
|
||
user: AdminUserDetail
|
||
/** Current admin's own id — status can't be self-edited. */
|
||
currentAdminId?: string
|
||
onSuccess: () => void
|
||
}
|
||
|
||
const UserEditModal = ({ isOpen, onOpenChange, user, currentAdminId, onSuccess }: UserEditModalProps) => {
|
||
const { showAlert } = useAlertModal()
|
||
const [submitting, setSubmitting] = useState(false)
|
||
const [cities, setCities] = useState<CityOption[]>([])
|
||
|
||
const isSelfEdit = Boolean(currentAdminId) && currentAdminId === user.id
|
||
|
||
const form = useForm<AdminUserEditValues>({
|
||
resolver: zodResolver(AdminUserEditValidation),
|
||
defaultValues: toFormValues(user),
|
||
})
|
||
const { reset } = form
|
||
|
||
// Modal stays mounted across opens, so reset explicitly whenever it opens
|
||
// (and the target user's data may have refreshed since the last edit).
|
||
useEffect(() => {
|
||
if (isOpen) {
|
||
reset(toFormValues(user))
|
||
}
|
||
}, [isOpen, reset, user])
|
||
|
||
useEffect(() => {
|
||
if (!isOpen || cities.length > 0) return
|
||
|
||
axiosInstance
|
||
.get(API_ROUTES.GEOGRAPHY.ALL_CITIES)
|
||
.then((res) => {
|
||
const payload: unknown = res.data
|
||
let list: unknown = []
|
||
|
||
if (Array.isArray(payload)) {
|
||
list = payload
|
||
} else if (typeof payload === 'object' && payload !== null) {
|
||
const record = payload as { data?: unknown; body?: unknown }
|
||
|
||
list = record.data ?? record.body ?? []
|
||
}
|
||
|
||
setCities(Array.isArray(list) ? (list as { id: number; name: string }[]) : [])
|
||
})
|
||
.catch(() => {
|
||
setCities([])
|
||
})
|
||
}, [isOpen, cities.length])
|
||
|
||
const buildDiffPayload = (values: AdminUserEditValues): AdminUpdateUserPayload => {
|
||
const initial = toFormValues(user)
|
||
const payload: AdminUpdateUserPayload = {}
|
||
|
||
if (values.firstName && values.firstName !== initial.firstName) payload.firstName = values.firstName
|
||
if (values.lastName && values.lastName !== initial.lastName) payload.lastName = values.lastName
|
||
if (values.gender && values.gender !== initial.gender) payload.gender = values.gender
|
||
if (values.cityId && values.cityId !== initial.cityId) payload.cityId = Number(values.cityId)
|
||
if (values.avatarUrl && values.avatarUrl !== initial.avatarUrl) payload.avatarUrl = values.avatarUrl
|
||
if (values.bio && values.bio !== initial.bio) payload.bio = values.bio
|
||
if (!isSelfEdit && values.status !== initial.status) payload.status = values.status
|
||
if (values.hostPlan !== initial.hostPlan) payload.hostPlan = values.hostPlan
|
||
|
||
return payload
|
||
}
|
||
|
||
const submitUpdate = async (values: AdminUserEditValues) => {
|
||
const payload = buildDiffPayload(values)
|
||
|
||
if (Object.keys(payload).length === 0) {
|
||
onOpenChange(false)
|
||
|
||
return
|
||
}
|
||
|
||
setSubmitting(true)
|
||
const result = await ADMIN_UPDATE_USER(user.id, payload)
|
||
|
||
setSubmitting(false)
|
||
|
||
if (!result.ok) return
|
||
|
||
addToast({ title: 'اطلاعات کاربر ذخیره شد', color: 'success' })
|
||
onOpenChange(false)
|
||
onSuccess()
|
||
}
|
||
|
||
const handleSubmit = (values: AdminUserEditValues) => {
|
||
const initial = toFormValues(user)
|
||
const payload = buildDiffPayload(values)
|
||
|
||
if (Object.keys(payload).length === 0) {
|
||
onOpenChange(false)
|
||
|
||
return
|
||
}
|
||
|
||
if (!isSelfEdit && values.status === 'suspended' && initial.status !== 'suspended') {
|
||
showAlert(
|
||
'این کاربر معلق شود؟ کاربر تا فعالسازی مجدد امکان استفاده از حساب را نخواهد داشت.',
|
||
() => submitUpdate(values),
|
||
undefined,
|
||
{
|
||
dangerAccept: true,
|
||
}
|
||
)
|
||
|
||
return
|
||
}
|
||
|
||
showAlert('تغییرات این کاربر ذخیره شود؟', () => submitUpdate(values))
|
||
}
|
||
|
||
return (
|
||
<Modal
|
||
acceptBtnText="ذخیره تغییرات"
|
||
isLoading={submitting}
|
||
isOpen={isOpen}
|
||
title={`ویرایش «${[user.firstName, user.lastName].filter(Boolean).join(' ') || user.mobile}»`}
|
||
onAccept={form.handleSubmit(handleSubmit)}
|
||
onOpenChange={onOpenChange}
|
||
>
|
||
<FormProvider {...form}>
|
||
<form
|
||
className="flex flex-col gap-4"
|
||
onSubmit={form.handleSubmit(handleSubmit)}
|
||
>
|
||
<UnsavedChangesIndicator isDirty={form.formState.isDirty} />
|
||
<AdminFormSection
|
||
contained={false}
|
||
description="اطلاعات عمومی نمایشدادهشده در پروفایل کاربر"
|
||
title="اطلاعات پروفایل"
|
||
>
|
||
<div className="flex flex-col gap-4">
|
||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||
<Input
|
||
generalType="input"
|
||
label="نام"
|
||
name="firstName"
|
||
placeholder="نام"
|
||
variant="flat"
|
||
/>
|
||
<Input
|
||
generalType="input"
|
||
label="نام خانوادگی"
|
||
name="lastName"
|
||
placeholder="نام خانوادگی"
|
||
variant="flat"
|
||
/>
|
||
</div>
|
||
<Input
|
||
generalType="select"
|
||
label="پلن میزبانی"
|
||
name="hostPlan"
|
||
selectKey="id"
|
||
selectOptions={HOST_PLAN_OPTIONS}
|
||
selectValue="name"
|
||
/>
|
||
<Input
|
||
generalType="select"
|
||
label="جنسیت"
|
||
name="gender"
|
||
selectKey="id"
|
||
selectOptions={GENDER_OPTIONS}
|
||
selectValue="name"
|
||
/>
|
||
<Input
|
||
generalType="select"
|
||
label="شهر"
|
||
name="cityId"
|
||
selectKey="id"
|
||
selectOptions={cities}
|
||
selectValue="name"
|
||
/>
|
||
<Input
|
||
direction="ltr"
|
||
generalType="input"
|
||
label="آدرس تصویر پروفایل"
|
||
name="avatarUrl"
|
||
placeholder="https://..."
|
||
variant="flat"
|
||
/>
|
||
<Input
|
||
generalType="textarea"
|
||
label="بیوگرافی"
|
||
name="bio"
|
||
placeholder="بیوگرافی کوتاه کاربر"
|
||
variant="flat"
|
||
/>
|
||
</div>
|
||
</AdminFormSection>
|
||
|
||
<AdminFormSection
|
||
contained={false}
|
||
description="سطح دسترسی و امکان استفاده از حساب"
|
||
title="تنظیمات مدیریتی"
|
||
>
|
||
<div className="flex flex-col gap-4">
|
||
{isSelfEdit && (
|
||
<div className="rounded-xl border border-fourth-100 bg-fourth-100 px-3 py-2 text-xs leading-5 text-fourth-900">
|
||
وضعیت حساب خودتان از اینجا قابل ویرایش نیست.
|
||
</div>
|
||
)}
|
||
<Input
|
||
disabled={isSelfEdit}
|
||
generalType="select"
|
||
label="وضعیت حساب"
|
||
name="status"
|
||
selectKey="id"
|
||
selectOptions={STATUS_OPTIONS}
|
||
selectValue="name"
|
||
/>
|
||
</div>
|
||
</AdminFormSection>
|
||
</form>
|
||
</FormProvider>
|
||
</Modal>
|
||
)
|
||
}
|
||
|
||
export default UserEditModal
|