Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
196 lines
7.4 KiB
TypeScript
196 lines
7.4 KiB
TypeScript
'use client'
|
|
|
|
import { zodResolver } from '@hookform/resolvers/zod'
|
|
import { useEffect } from 'react'
|
|
import { FormProvider, useForm } from 'react-hook-form'
|
|
|
|
import { texts } from '@/texts'
|
|
import type { EventWizardFormData } from '@/components/events/create/types'
|
|
import AudienceRestrictionSelector from '@/components/events/create/AudienceRestrictionSelector'
|
|
import CancellationPolicyEditor from '@/components/events/create/CancellationPolicyEditor'
|
|
import TicketTypeSelector from '@/components/events/create/TicketTypeSelector'
|
|
import Input from '@/components/formElements/Input'
|
|
import { WizardFormFullWidth, WizardFormGrid, WizardSectionTitle, WizardStepFooter } from '@/components/events/create/WizardFormLayout'
|
|
import { EventWizardStep3Validation, type EventWizardStep3Values } from '@/validation/eventWizard'
|
|
import { showFormValidationToast } from '@/lib/formValidationToast'
|
|
|
|
interface Step3PricingCapacityProps {
|
|
data: EventWizardFormData
|
|
onBack: () => void
|
|
onChange: (patch: Partial<EventWizardFormData>) => void
|
|
onNext: (patch: Partial<EventWizardFormData>) => void
|
|
}
|
|
|
|
export default function Step3PricingCapacity({ data, onBack, onChange, onNext }: Step3PricingCapacityProps) {
|
|
const form = useForm<EventWizardStep3Values>({
|
|
resolver: zodResolver(EventWizardStep3Validation),
|
|
defaultValues: {
|
|
isFree: data.isFree,
|
|
price: data.price,
|
|
capacity: data.capacity,
|
|
reservedCapacity: data.reservedCapacity,
|
|
genderRestriction: data.genderRestriction,
|
|
ageRestriction: data.ageRestriction,
|
|
cancellationFeePercent: data.cancellationFeePercent,
|
|
isDiscoverable: data.isDiscoverable,
|
|
autoCreateGroup: data.autoCreateGroup,
|
|
waitlistAutoOffer: data.waitlistAutoOffer,
|
|
sendReviewRequestSms: data.sendReviewRequestSms,
|
|
},
|
|
})
|
|
|
|
const isFree = form.watch('isFree')
|
|
|
|
useEffect(() => {
|
|
const subscription = form.watch((values) => {
|
|
onChange({
|
|
isFree: values.isFree ?? false,
|
|
price: Number(values.price ?? 0),
|
|
capacity: Number(values.capacity ?? 0),
|
|
reservedCapacity: Number(values.reservedCapacity ?? 0),
|
|
genderRestriction: values.genderRestriction ?? 'open',
|
|
ageRestriction: values.ageRestriction ?? 'open',
|
|
cancellationFeePercent: Number(values.cancellationFeePercent ?? 0),
|
|
isDiscoverable: values.isDiscoverable ?? false,
|
|
autoCreateGroup: values.autoCreateGroup ?? true,
|
|
waitlistAutoOffer: values.waitlistAutoOffer ?? true,
|
|
sendReviewRequestSms: values.sendReviewRequestSms ?? false,
|
|
})
|
|
})
|
|
|
|
return () => {
|
|
subscription.unsubscribe()
|
|
}
|
|
}, [form, onChange])
|
|
|
|
const handleSubmit = form.handleSubmit((values) => {
|
|
onNext({
|
|
isFree: values.isFree,
|
|
price: values.isFree ? 0 : values.price,
|
|
capacity: values.capacity,
|
|
reservedCapacity: values.reservedCapacity,
|
|
genderRestriction: values.genderRestriction,
|
|
ageRestriction: values.ageRestriction,
|
|
cancellationFeePercent: values.cancellationFeePercent,
|
|
isDiscoverable: values.isDiscoverable,
|
|
autoCreateGroup: values.autoCreateGroup,
|
|
waitlistAutoOffer: values.waitlistAutoOffer,
|
|
sendReviewRequestSms: values.sendReviewRequestSms,
|
|
})
|
|
}, showFormValidationToast)
|
|
|
|
return (
|
|
<FormProvider {...form}>
|
|
<form onSubmit={handleSubmit}>
|
|
<WizardFormGrid>
|
|
<WizardSectionTitle description={texts.events.pricingSectionDescription}>
|
|
{texts.events.ticketTypeSectionTitle}
|
|
</WizardSectionTitle>
|
|
|
|
<WizardFormFullWidth>
|
|
<TicketTypeSelector
|
|
isFree={isFree}
|
|
onChange={(nextIsFree) => {
|
|
form.setValue('isFree', nextIsFree, { shouldDirty: true, shouldValidate: true })
|
|
}}
|
|
/>
|
|
</WizardFormFullWidth>
|
|
|
|
{!isFree ? (
|
|
<Input
|
|
required
|
|
formatOptions={{ style: 'decimal' }}
|
|
generalType="numberInput"
|
|
label={texts.events.pricePerTicketLabel}
|
|
minValue={0}
|
|
name="price"
|
|
/>
|
|
) : null}
|
|
|
|
<Input
|
|
required
|
|
generalType="numberInput"
|
|
label={texts.events.capacity}
|
|
minValue={1}
|
|
name="capacity"
|
|
/>
|
|
<WizardFormFullWidth>
|
|
<Input
|
|
generalType="numberInput"
|
|
label={texts.events.reservedCapacityLabel}
|
|
minValue={0}
|
|
name="reservedCapacity"
|
|
/>
|
|
<p className="mt-2 text-xs text-secondary-20">{texts.events.reservedCapacityWizardHint}</p>
|
|
</WizardFormFullWidth>
|
|
|
|
<WizardSectionTitle description={texts.events.audienceSectionDescription}>{texts.events.audienceSectionTitle}</WizardSectionTitle>
|
|
|
|
<WizardFormFullWidth>
|
|
<AudienceRestrictionSelector
|
|
ageRestriction={form.watch('ageRestriction')}
|
|
genderRestriction={form.watch('genderRestriction')}
|
|
onAgeRestrictionChange={(ageRestriction) => {
|
|
form.setValue('ageRestriction', ageRestriction, { shouldDirty: true, shouldValidate: true })
|
|
}}
|
|
onGenderRestrictionChange={(genderRestriction) => {
|
|
form.setValue('genderRestriction', genderRestriction, { shouldDirty: true, shouldValidate: true })
|
|
}}
|
|
/>
|
|
</WizardFormFullWidth>
|
|
|
|
<WizardSectionTitle description={texts.events.cancellationSectionDescription}>
|
|
{texts.events.cancellationSectionTitle}
|
|
</WizardSectionTitle>
|
|
|
|
<WizardFormFullWidth>
|
|
<CancellationPolicyEditor
|
|
value={form.watch('cancellationFeePercent')}
|
|
onChange={(cancellationFeePercent) => {
|
|
form.setValue('cancellationFeePercent', cancellationFeePercent, { shouldDirty: true, shouldValidate: true })
|
|
}}
|
|
/>
|
|
</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.moreSettings}
|
|
<span className="me-2 text-xs font-normal text-secondary-20">{texts.events.advancedSettingsHint}</span>
|
|
</summary>
|
|
<div className="mt-4 grid gap-5 border-t border-consumer-border pt-4 md:grid-cols-2">
|
|
<Input
|
|
generalType="switch"
|
|
label={texts.events.discoverableLabel}
|
|
name="isDiscoverable"
|
|
/>
|
|
<Input
|
|
generalType="switch"
|
|
label={texts.events.autoCreateGroupLabel}
|
|
name="autoCreateGroup"
|
|
/>
|
|
<Input
|
|
generalType="switch"
|
|
label={texts.events.waitlistAutoOfferLabel}
|
|
name="waitlistAutoOffer"
|
|
/>
|
|
<Input
|
|
generalType="switch"
|
|
label={texts.events.sendReviewSmsLabel}
|
|
name="sendReviewRequestSms"
|
|
/>
|
|
</div>
|
|
</details>
|
|
</WizardFormFullWidth>
|
|
|
|
<WizardStepFooter
|
|
primaryLabel={texts.events.nextStep}
|
|
onBack={onBack}
|
|
onPrimary={handleSubmit}
|
|
/>
|
|
</WizardFormGrid>
|
|
</form>
|
|
</FormProvider>
|
|
)
|
|
}
|