diff --git a/components/events/create/CancellationPolicyEditor.tsx b/components/events/create/CancellationPolicyEditor.tsx
index 8644062..254c0f3 100644
--- a/components/events/create/CancellationPolicyEditor.tsx
+++ b/components/events/create/CancellationPolicyEditor.tsx
@@ -5,44 +5,77 @@ import { texts } from '@/texts'
interface CancellationPolicyEditorProps {
disabled?: boolean
- value: number
- onChange: (value: number) => void
+ cancellationFeePercent: number
+ cancellationFeePercent12To24Hours: number
+ cancellationFeePercentMoreThan24Hours: number
+ onChange: (
+ field: 'cancellationFeePercent' | 'cancellationFeePercent12To24Hours' | 'cancellationFeePercentMoreThan24Hours',
+ value: number
+ ) => void
}
/**
- * Makes the cancellation fee explicit to hosts while keeping the persisted
- * event contract to the existing `cancellationFeePercent` field.
+ * Makes all three time-based cancellation fee tiers explicit to hosts.
*/
-export default function CancellationPolicyEditor({ disabled = false, value, onChange }: CancellationPolicyEditorProps) {
- const normalizedValue = Number.isFinite(value) ? Math.min(100, Math.max(0, value)) : 0
+export default function CancellationPolicyEditor({
+ disabled = false,
+ cancellationFeePercent,
+ cancellationFeePercent12To24Hours,
+ cancellationFeePercentMoreThan24Hours,
+ onChange,
+}: CancellationPolicyEditorProps) {
+ const tiers = [
+ {
+ field: 'cancellationFeePercentMoreThan24Hours' as const,
+ label: texts.events.cancellationFeeMoreThan24HoursLabel,
+ value: cancellationFeePercentMoreThan24Hours,
+ },
+ {
+ field: 'cancellationFeePercent12To24Hours' as const,
+ label: texts.events.cancellationFee12To24HoursLabel,
+ value: cancellationFeePercent12To24Hours,
+ },
+ {
+ field: 'cancellationFeePercent' as const,
+ label: texts.events.cancellationFeeLabel,
+ value: cancellationFeePercent,
+ },
+ ]
return (
-
-
-
-
{texts.events.cancellationFeeLabel}
-
{texts.events.cancellationSectionDescription}
-
-
-
{
- const next = Number(nextValue)
+
+ {tiers.map((tier) => {
+ const normalizedValue = Number.isFinite(tier.value) ? Math.min(100, Math.max(0, tier.value)) : 0
- onChange(Number.isFinite(next) ? Math.min(100, Math.max(0, next)) : 0)
- }}
- />
- {/* This marker belongs to the compact numeric field, not to its value. */}
- ٪
-
-
+ return (
+
+
+
{tier.label}
+
+ {
+ const next = Number(nextValue)
+
+ onChange(tier.field, Number.isFinite(next) ? Math.min(100, Math.max(0, next)) : 0)
+ }}
+ />
+ ٪
+
+
+
+ )
+ })}
)
}
diff --git a/components/events/create/EventCreateWizard.tsx b/components/events/create/EventCreateWizard.tsx
index a5c7290..d1dffac 100644
--- a/components/events/create/EventCreateWizard.tsx
+++ b/components/events/create/EventCreateWizard.tsx
@@ -239,6 +239,8 @@ export default function EventCreateWizard({ finishRoute = APP_ROUTES.MANAGE_EVEN
genderRestriction: data.genderRestriction,
ageRestriction: data.ageRestriction,
cancellationFeePercent: data.cancellationFeePercent,
+ cancellationFeePercent12To24Hours: data.cancellationFeePercent12To24Hours,
+ cancellationFeePercentMoreThan24Hours: data.cancellationFeePercentMoreThan24Hours,
settings: {
isDiscoverable: data.isDiscoverable,
autoCreateGroup: data.autoCreateGroup,
diff --git a/components/events/create/cloneFromEvent.test.ts b/components/events/create/cloneFromEvent.test.ts
index 4220927..a908197 100644
--- a/components/events/create/cloneFromEvent.test.ts
+++ b/components/events/create/cloneFromEvent.test.ts
@@ -30,6 +30,8 @@ const baseEvent = {
reservedCapacity: 3,
bookedCount: 5,
cancellationFeePercent: 10,
+ cancellationFeePercent12To24Hours: 10,
+ cancellationFeePercentMoreThan24Hours: 10,
commissionPercent: null,
effectiveCommissionPercent: 7,
status: 'published',
diff --git a/components/events/create/cloneFromEvent.ts b/components/events/create/cloneFromEvent.ts
index 39dab49..a4548e1 100644
--- a/components/events/create/cloneFromEvent.ts
+++ b/components/events/create/cloneFromEvent.ts
@@ -55,6 +55,8 @@ export function mapEventToWizardCloneData(event: CreatedEvent, media: EventMedia
genderRestriction: event.genderRestriction ?? 'open',
ageRestriction: event.ageRestriction ?? 'open',
cancellationFeePercent: event.cancellationFeePercent,
+ cancellationFeePercent12To24Hours: event.cancellationFeePercent12To24Hours,
+ cancellationFeePercentMoreThan24Hours: event.cancellationFeePercentMoreThan24Hours,
isDiscoverable: event.settings.isDiscoverable,
autoCreateGroup: event.settings.autoCreateGroup,
addressVisibility: event.settings.addressVisibility,
diff --git a/components/events/create/steps/Step3PricingCapacity.tsx b/components/events/create/steps/Step3PricingCapacity.tsx
index 5d642fa..d41ad15 100644
--- a/components/events/create/steps/Step3PricingCapacity.tsx
+++ b/components/events/create/steps/Step3PricingCapacity.tsx
@@ -32,6 +32,8 @@ export default function Step3PricingCapacity({ data, onBack, onChange, onNext }:
genderRestriction: data.genderRestriction,
ageRestriction: data.ageRestriction,
cancellationFeePercent: data.cancellationFeePercent,
+ cancellationFeePercent12To24Hours: data.cancellationFeePercent12To24Hours,
+ cancellationFeePercentMoreThan24Hours: data.cancellationFeePercentMoreThan24Hours,
isDiscoverable: data.isDiscoverable,
autoCreateGroup: data.autoCreateGroup,
waitlistAutoOffer: data.waitlistAutoOffer,
@@ -51,6 +53,8 @@ export default function Step3PricingCapacity({ data, onBack, onChange, onNext }:
genderRestriction: values.genderRestriction ?? 'open',
ageRestriction: values.ageRestriction ?? 'open',
cancellationFeePercent: Number(values.cancellationFeePercent ?? 0),
+ cancellationFeePercent12To24Hours: Number(values.cancellationFeePercent12To24Hours ?? 0),
+ cancellationFeePercentMoreThan24Hours: Number(values.cancellationFeePercentMoreThan24Hours ?? 0),
isDiscoverable: values.isDiscoverable ?? false,
autoCreateGroup: values.autoCreateGroup ?? true,
waitlistAutoOffer: values.waitlistAutoOffer ?? true,
@@ -72,6 +76,8 @@ export default function Step3PricingCapacity({ data, onBack, onChange, onNext }:
genderRestriction: values.genderRestriction,
ageRestriction: values.ageRestriction,
cancellationFeePercent: values.cancellationFeePercent,
+ cancellationFeePercent12To24Hours: values.cancellationFeePercent12To24Hours,
+ cancellationFeePercentMoreThan24Hours: values.cancellationFeePercentMoreThan24Hours,
isDiscoverable: values.isDiscoverable,
autoCreateGroup: values.autoCreateGroup,
waitlistAutoOffer: values.waitlistAutoOffer,
@@ -145,9 +151,11 @@ export default function Step3PricingCapacity({ data, onBack, onChange, onNext }:
{
- form.setValue('cancellationFeePercent', cancellationFeePercent, { shouldDirty: true, shouldValidate: true })
+ cancellationFeePercent={form.watch('cancellationFeePercent')}
+ cancellationFeePercent12To24Hours={form.watch('cancellationFeePercent12To24Hours')}
+ cancellationFeePercentMoreThan24Hours={form.watch('cancellationFeePercentMoreThan24Hours')}
+ onChange={(field, value) => {
+ form.setValue(field, value, { shouldDirty: true, shouldValidate: true })
}}
/>
diff --git a/components/events/create/types.ts b/components/events/create/types.ts
index 062b052..38ca830 100644
--- a/components/events/create/types.ts
+++ b/components/events/create/types.ts
@@ -44,6 +44,8 @@ export interface EventWizardFormData {
genderRestriction: EventGenderRestriction
ageRestriction: EventAgeRestriction
cancellationFeePercent: number
+ cancellationFeePercent12To24Hours: number
+ cancellationFeePercentMoreThan24Hours: number
isDiscoverable: boolean
autoCreateGroup: boolean
addressVisibility: 'public' | 'attendees_only'
@@ -75,7 +77,9 @@ export const INITIAL_WIZARD_DATA: EventWizardFormData = {
reservedCapacity: 0,
genderRestriction: 'open',
ageRestriction: 'open',
- cancellationFeePercent: 0,
+ cancellationFeePercent: 30,
+ cancellationFeePercent12To24Hours: 20,
+ cancellationFeePercentMoreThan24Hours: 10,
// Defaults to true here in the wizard, unlike the backend's own
// default of false -- since this is the only place that currently
// sets it, and an organizer/admin creating an event almost always
diff --git a/e2e/fixtures/hostEvent.ts b/e2e/fixtures/hostEvent.ts
index 2c834ef..5e4af34 100644
--- a/e2e/fixtures/hostEvent.ts
+++ b/e2e/fixtures/hostEvent.ts
@@ -70,6 +70,8 @@ export const createHostEvent =
>(overrides: T)
cityId: 2,
provinceId: 1,
cancellationFeePercent: 0,
+ cancellationFeePercent12To24Hours: 0,
+ cancellationFeePercentMoreThan24Hours: 0,
discoveryPlacements: [],
createdAt: '2029-12-01T10:00:00.000Z',
publishedAt: null,
diff --git a/features/events/detail/admin-event-detail/AdminEventDetailsSection.tsx b/features/events/detail/admin-event-detail/AdminEventDetailsSection.tsx
index e236021..07ee5d4 100644
--- a/features/events/detail/admin-event-detail/AdminEventDetailsSection.tsx
+++ b/features/events/detail/admin-event-detail/AdminEventDetailsSection.tsx
@@ -18,6 +18,7 @@ import { formatNumber, formatPersonName } from '@/helpers'
import { getPublicMapApiKey } from '@/helpers/mapir'
import { formatIranianMobile, formatPersianDate } from '@/lib/formatters'
import { ageRestrictionLabel, genderRestrictionLabel } from '@/lib/eventAudience'
+import { texts } from '@/texts'
import { DetailItem } from './AdminEventDetailUi'
@@ -141,7 +142,11 @@ const AdminEventDetailsSection = ({
-