Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
185 lines
6.2 KiB
TypeScript
185 lines
6.2 KiB
TypeScript
'use client'
|
||
|
||
import { zodResolver } from '@hookform/resolvers/zod'
|
||
import { useEffect, useRef } from 'react'
|
||
import dynamic from 'next/dynamic'
|
||
import { FormProvider, useForm } from 'react-hook-form'
|
||
|
||
import { texts, format } from '@/texts'
|
||
import type { EventWizardFormData } from '@/components/events/create/types'
|
||
import { hasRequiredPosters } from '@/components/events/create/types'
|
||
import { addToast } from '@/lib/toast'
|
||
import Input from '@/components/formElements/Input'
|
||
import EventMediaGalleryUploader from '@/components/events/create/EventMediaGalleryUploader'
|
||
import CategoryTreeSelect from '@/components/events/create/CategoryTreeSelect'
|
||
import { WizardFormFullWidth, WizardFormGrid, WizardSectionTitle, WizardStepFooter } from '@/components/events/create/WizardFormLayout'
|
||
import { EventWizardStep1Validation, type EventWizardStep1Values } from '@/validation/eventWizard'
|
||
import { shouldAutogenerateEventSlug, slugifyEventTitle } from '@/features/events/eventSlug'
|
||
import { usePublicCategoriesQuery } from '@/queries/consumer/usePublicCategoriesQuery'
|
||
import { showFormValidationToast } from '@/lib/formValidationToast'
|
||
|
||
const TextEditor = dynamic(() => import('@/components/formElements/TextEditor'), { ssr: false })
|
||
|
||
interface Step1BasicInfoProps {
|
||
data: EventWizardFormData
|
||
onChange: (patch: Partial<EventWizardFormData>) => void
|
||
onNext: (patch: Partial<EventWizardFormData>) => void
|
||
}
|
||
|
||
export default function Step1BasicInfo({ data, onChange, onNext }: Step1BasicInfoProps) {
|
||
const categoriesQuery = usePublicCategoriesQuery()
|
||
const categories = categoriesQuery.data?.items ?? []
|
||
const form = useForm<EventWizardStep1Values>({
|
||
resolver: zodResolver(EventWizardStep1Validation),
|
||
defaultValues: {
|
||
title: data.title,
|
||
slug: data.slug,
|
||
categoryId: data.categoryId,
|
||
shortDescription: data.shortDescription,
|
||
description: data.description,
|
||
},
|
||
})
|
||
|
||
const followTitleRef = useRef(shouldAutogenerateEventSlug(data.slug, data.title))
|
||
const lastAutoSlugRef = useRef(data.slug || slugifyEventTitle(data.title))
|
||
const title = form.watch('title')
|
||
const slug = form.watch('slug')
|
||
|
||
useEffect(() => {
|
||
if (categoriesQuery.isError) {
|
||
addToast({ title: texts.events.categoriesLoadFailed, color: 'danger' })
|
||
}
|
||
}, [categoriesQuery.isError])
|
||
|
||
useEffect(() => {
|
||
if (!followTitleRef.current) return
|
||
|
||
const next = slugifyEventTitle(title ?? '')
|
||
|
||
lastAutoSlugRef.current = next
|
||
if (form.getValues('slug') !== next) {
|
||
form.setValue('slug', next)
|
||
}
|
||
}, [form, title])
|
||
|
||
useEffect(() => {
|
||
const subscription = form.watch((values, info) => {
|
||
if (info.name === 'slug' && (values.slug ?? '') !== lastAutoSlugRef.current) {
|
||
followTitleRef.current = false
|
||
}
|
||
|
||
onChange({
|
||
title: values.title ?? '',
|
||
slug: values.slug ?? '',
|
||
categoryId: values.categoryId ?? '',
|
||
shortDescription: values.shortDescription ?? '',
|
||
description: values.description ?? '',
|
||
})
|
||
})
|
||
|
||
return () => {
|
||
subscription.unsubscribe()
|
||
}
|
||
}, [form, onChange])
|
||
|
||
const handleSubmit = form.handleSubmit((values) => {
|
||
if (!hasRequiredPosters(data.media)) {
|
||
addToast({
|
||
title: texts.events.postersRequiredTitle,
|
||
description: texts.events.postersRequiredShort,
|
||
color: 'danger',
|
||
})
|
||
|
||
return
|
||
}
|
||
|
||
onNext({
|
||
title: values.title,
|
||
slug: values.slug,
|
||
categoryId: values.categoryId,
|
||
shortDescription: values.shortDescription ?? '',
|
||
description: values.description ?? '',
|
||
media: data.media,
|
||
})
|
||
}, showFormValidationToast)
|
||
|
||
return (
|
||
<FormProvider {...form}>
|
||
<form onSubmit={handleSubmit}>
|
||
<WizardFormGrid>
|
||
<WizardSectionTitle description={texts.events.mediaSectionDescription}>پوستر و تصاویر</WizardSectionTitle>
|
||
|
||
<WizardFormFullWidth>
|
||
<EventMediaGalleryUploader
|
||
items={data.media}
|
||
onChange={(media) => {
|
||
onChange({ media })
|
||
}}
|
||
/>
|
||
</WizardFormFullWidth>
|
||
|
||
<WizardSectionTitle description={texts.events.basicInfoSectionDescription}>معرفی رویداد</WizardSectionTitle>
|
||
|
||
<Input
|
||
required
|
||
generalType="input"
|
||
label={texts.events.eventTitleLabel}
|
||
maxValue={200}
|
||
name="title"
|
||
/>
|
||
<CategoryTreeSelect
|
||
required
|
||
categories={categories}
|
||
label={texts.common.category}
|
||
name="categoryId"
|
||
/>
|
||
|
||
<WizardFormFullWidth>
|
||
<Input
|
||
generalType="textarea"
|
||
label={texts.events.shortDescriptionLabel}
|
||
name="shortDescription"
|
||
textAreaMinRows={3}
|
||
/>
|
||
</WizardFormFullWidth>
|
||
|
||
<WizardFormFullWidth>
|
||
<p className="labelClass mb-2">{texts.events.fullDescriptionLabel}</p>
|
||
<TextEditor
|
||
value={form.watch('description')}
|
||
onChange={(value) => {
|
||
form.setValue('description', value)
|
||
}}
|
||
/>
|
||
</WizardFormFullWidth>
|
||
|
||
<WizardFormFullWidth>
|
||
<details className="group rounded-2xl border border-consumer-border bg-consumer-surface-muted p-4">
|
||
<summary className="cursor-pointer list-none text-sm font-bold text-consumer-text marker:hidden">
|
||
{texts.events.configureEventLink}
|
||
<span className="me-2 text-xs font-normal text-secondary-20">{texts.events.advanced}</span>
|
||
</summary>
|
||
<div className="mt-4 border-t border-consumer-border pt-4">
|
||
<Input
|
||
required
|
||
description={slug ? format(texts.events.slugPublicLink, { slug }) : texts.events.slugHintFromTitle}
|
||
direction="ltr"
|
||
generalType="input"
|
||
label={texts.events.slugLabel}
|
||
maxValue={220}
|
||
name="slug"
|
||
/>
|
||
</div>
|
||
</details>
|
||
</WizardFormFullWidth>
|
||
|
||
<WizardStepFooter
|
||
primaryLabel={texts.events.nextStep}
|
||
onPrimary={handleSubmit}
|
||
/>
|
||
</WizardFormGrid>
|
||
</form>
|
||
</FormProvider>
|
||
)
|
||
}
|