admin/components/events/create/steps/Step1BasicInfo.tsx
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

185 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
)
}