Enforce task limits and update event form
This commit is contained in:
@@ -2,14 +2,14 @@ import React from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CalendarDays, ChevronDown, MapPin, Save, Check } from 'lucide-react';
|
||||
import { CalendarDays, MapPin, Save, Check } from 'lucide-react';
|
||||
import { isPast, isSameDay, parseISO, startOfDay } from 'date-fns';
|
||||
import { YStack, XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { Switch } from '@tamagui/switch';
|
||||
import { MobileShell } from './components/MobileShell';
|
||||
import { MobileCard, CTAButton, FloatingActionButton } from './components/Primitives';
|
||||
import { MobileDateTimeInput, MobileField, MobileInput, MobileSelect, MobileTextArea } from './components/FormControls';
|
||||
import { MobileDateInput, MobileField, MobileInput, MobileSelect, MobileTextArea } from './components/FormControls';
|
||||
import { LegalConsentSheet } from './components/LegalConsentSheet';
|
||||
import {
|
||||
createEvent,
|
||||
@@ -31,7 +31,6 @@ import { getApiErrorMessage, getApiValidationMessage, isApiError } from '../lib/
|
||||
import toast from 'react-hot-toast';
|
||||
import { useBackNavigation } from './hooks/useBackNavigation';
|
||||
import { useAdminTheme } from './theme';
|
||||
import { withAlpha } from './components/colors';
|
||||
import { useAuth } from '../auth/context';
|
||||
import { useEventContext } from '../context/EventContext';
|
||||
|
||||
@@ -138,7 +137,7 @@ export default function MobileEventFormPage() {
|
||||
const handleDateChange = React.useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (isEventCompleted) return;
|
||||
setForm((prev) => ({ ...prev, date: event.target.value }));
|
||||
setForm((prev) => ({ ...prev, date: normalizeEventDateTime(event.target.value, prev.date) }));
|
||||
},
|
||||
[isEventCompleted],
|
||||
);
|
||||
@@ -258,11 +257,12 @@ export default function MobileEventFormPage() {
|
||||
async function handleSubmit() {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
const normalizedEventDate = normalizeEventDateTime(form.date);
|
||||
try {
|
||||
if (isEdit && slug) {
|
||||
const updated = await updateEvent(slug, {
|
||||
name: form.name,
|
||||
event_date: form.date || undefined,
|
||||
event_date: normalizedEventDate || undefined,
|
||||
event_type_id: form.eventTypeId ?? undefined,
|
||||
status: form.published ? 'published' : 'draft',
|
||||
settings: {
|
||||
@@ -280,7 +280,7 @@ export default function MobileEventFormPage() {
|
||||
name: form.name || t('eventForm.fields.name.fallback', 'Event'),
|
||||
slug: `${Date.now()}`,
|
||||
event_type_id: form.eventTypeId ?? undefined,
|
||||
event_date: form.date || undefined,
|
||||
event_date: normalizedEventDate || undefined,
|
||||
status: form.published ? 'published' : 'draft',
|
||||
package_id: isSuperAdmin ? form.packageId ?? undefined : undefined,
|
||||
service_package_slug: form.servicePackageSlug ?? undefined,
|
||||
@@ -314,7 +314,7 @@ export default function MobileEventFormPage() {
|
||||
name: form.name || t('eventForm.fields.name.fallback', 'Event'),
|
||||
slug: `${Date.now()}`,
|
||||
event_type_id: form.eventTypeId ?? undefined,
|
||||
event_date: form.date || undefined,
|
||||
event_date: normalizedEventDate || undefined,
|
||||
status: form.published ? 'published' : 'draft',
|
||||
package_id: isSuperAdmin ? form.packageId ?? undefined : undefined,
|
||||
service_package_slug: form.servicePackageSlug ?? undefined,
|
||||
@@ -374,6 +374,20 @@ export default function MobileEventFormPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const requiredLabel = React.useCallback(
|
||||
(label: string) => (
|
||||
<XStack alignItems="center" space="$1">
|
||||
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text fontSize="$sm" fontWeight="800" color={danger}>
|
||||
*
|
||||
</Text>
|
||||
</XStack>
|
||||
),
|
||||
[danger, text],
|
||||
);
|
||||
|
||||
return (
|
||||
<MobileShell
|
||||
activeTab="home"
|
||||
@@ -389,7 +403,7 @@ export default function MobileEventFormPage() {
|
||||
) : null}
|
||||
|
||||
<MobileCard space="$3">
|
||||
<MobileField label={t('eventForm.fields.name.label', 'Event name')}>
|
||||
<MobileField label={requiredLabel(t('eventForm.fields.name.label', 'Event name'))}>
|
||||
<MobileInput
|
||||
type="text"
|
||||
value={form.name}
|
||||
@@ -451,10 +465,10 @@ export default function MobileEventFormPage() {
|
||||
</MobileField>
|
||||
) : null}
|
||||
|
||||
<MobileField label={t('eventForm.fields.date.label', 'Date & time')}>
|
||||
<MobileField label={requiredLabel(t('eventForm.fields.date.label', 'Date & time'))}>
|
||||
<XStack alignItems="center" space="$2">
|
||||
<MobileDateTimeInput
|
||||
value={form.date}
|
||||
<MobileDateInput
|
||||
value={extractDateValue(form.date)}
|
||||
onChange={handleDateChange}
|
||||
style={{ flex: 1 }}
|
||||
disabled={isEventCompleted}
|
||||
@@ -463,7 +477,7 @@ export default function MobileEventFormPage() {
|
||||
</XStack>
|
||||
</MobileField>
|
||||
|
||||
<MobileField label={t('eventForm.fields.type.label', 'Event type')}>
|
||||
<MobileField label={requiredLabel(t('eventForm.fields.type.label', 'Event type'))}>
|
||||
{typesLoading ? (
|
||||
<Text fontSize="$sm" color={muted}>{t('eventForm.fields.type.loading', 'Loading event types…')}</Text>
|
||||
) : eventTypes.length === 0 ? (
|
||||
@@ -589,9 +603,9 @@ export default function MobileEventFormPage() {
|
||||
<YStack space="$2" paddingBottom="$10">
|
||||
{!isEdit ? (
|
||||
<CTAButton
|
||||
label={t('eventForm.actions.saveDraft', 'Save as draft')}
|
||||
label={t('eventForm.actions.create', 'Create event')}
|
||||
tone="ghost"
|
||||
onPress={back}
|
||||
onPress={() => handleSubmit()}
|
||||
/>
|
||||
) : null}
|
||||
</YStack>
|
||||
@@ -663,6 +677,30 @@ function isWaiverRequiredError(error: unknown): boolean {
|
||||
return 'accepted_waiver' in metaErrors;
|
||||
}
|
||||
|
||||
const DEFAULT_EVENT_TIME = '12:00';
|
||||
|
||||
function normalizeEventDateTime(value: string, previousValue?: string): string {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const [datePart, timePartRaw] = value.split('T');
|
||||
if (!datePart) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const nextTime = timePartRaw?.slice(0, 5);
|
||||
if (!nextTime || nextTime === '00:00') {
|
||||
const previousTime = previousValue?.split('T')[1]?.slice(0, 5);
|
||||
if (previousTime && previousTime !== '00:00') {
|
||||
return `${datePart}T${previousTime}`;
|
||||
}
|
||||
|
||||
return `${datePart}T${DEFAULT_EVENT_TIME}`;
|
||||
}
|
||||
|
||||
return `${datePart}T${nextTime}`;
|
||||
}
|
||||
|
||||
function toDateTimeLocal(value?: string | null): string {
|
||||
if (!value) return '';
|
||||
@@ -674,6 +712,14 @@ function toDateTimeLocal(value?: string | null): string {
|
||||
return fallback.length >= 16 ? fallback.slice(0, 16) : '';
|
||||
}
|
||||
|
||||
function extractDateValue(value?: string | null): string {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return value.split('T')[0] ?? '';
|
||||
}
|
||||
|
||||
function resolveLocation(event: TenantEvent): string {
|
||||
const settings = (event.settings ?? {}) as Record<string, unknown>;
|
||||
const candidate =
|
||||
|
||||
Reference in New Issue
Block a user