diff --git a/src/components/admin/DesignerForm.tsx b/src/components/admin/DesignerForm.tsx index 073a4a87..9291f665 100644 --- a/src/components/admin/DesignerForm.tsx +++ b/src/components/admin/DesignerForm.tsx @@ -1,9 +1,8 @@ -import { useState, useReducer } from 'react'; +import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { entitySchemas } from '@/lib/entityValidationSchemas'; -import { submissionReducer, canSubmit } from '@/lib/submissionStateMachine'; import { getErrorMessage } from '@/lib/errorHandler'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -62,10 +61,6 @@ export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormPr const { headquarters } = useCompanyHeadquarters(); const { user } = useAuth(); const navigate = useNavigate(); - const [submissionState, dispatch] = useReducer(submissionReducer, { - status: 'draft' as const, - data: initialData || {} - }); const { register, @@ -104,16 +99,7 @@ export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormPr return; } - if (!canSubmit(submissionState)) { - toast.error('Cannot submit in current state'); - return; - } - - dispatch({ type: 'VALIDATE', payload: data }); - - try { - dispatch({ type: 'SUBMIT', payload: { submissionId: crypto.randomUUID() } }); - + try { const formData = { ...data, company_type: 'designer' as const, @@ -122,20 +108,12 @@ export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormPr await onSubmit(formData); - dispatch({ type: 'SUBMISSION_COMPLETE' }); - // Only show success toast and close if not editing through moderation queue if (!initialData?.id) { toast.success('Designer submitted for review'); onCancel(); } } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - if (errorMessage.includes('validation')) { - dispatch({ type: 'VALIDATION_ERROR', payload: [{ field: 'general', message: errorMessage }] }); - } else { - dispatch({ type: 'RESET' }); - } handleError(error, { action: initialData?.id ? 'Update Designer' : 'Create Designer', metadata: { companyName: data.name } @@ -262,18 +240,16 @@ export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormPr diff --git a/src/components/admin/ManufacturerForm.tsx b/src/components/admin/ManufacturerForm.tsx index f9fb6bef..48e8a8c5 100644 --- a/src/components/admin/ManufacturerForm.tsx +++ b/src/components/admin/ManufacturerForm.tsx @@ -1,9 +1,8 @@ -import { useState, useReducer } from 'react'; +import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { entitySchemas } from '@/lib/entityValidationSchemas'; -import { submissionReducer, canSubmit } from '@/lib/submissionStateMachine'; import { getErrorMessage } from '@/lib/errorHandler'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -63,10 +62,6 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur const { headquarters } = useCompanyHeadquarters(); const { user } = useAuth(); const navigate = useNavigate(); - const [submissionState, dispatch] = useReducer(submissionReducer, { - status: 'draft' as const, - data: initialData || {} - }); const { register, @@ -107,16 +102,7 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur return; } - if (!canSubmit(submissionState)) { - toast.error('Cannot submit in current state'); - return; - } - - dispatch({ type: 'VALIDATE', payload: data }); - - try { - dispatch({ type: 'SUBMIT', payload: { submissionId: crypto.randomUUID() } }); - + try { const formData = { ...data, company_type: 'manufacturer' as const, @@ -125,20 +111,12 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur await onSubmit(formData); - dispatch({ type: 'SUBMISSION_COMPLETE' }); - // Only show success toast and close if not editing through moderation queue if (!initialData?.id) { toast.success('Manufacturer submitted for review'); onCancel(); } } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - if (errorMessage.includes('validation')) { - dispatch({ type: 'VALIDATION_ERROR', payload: [{ field: 'general', message: errorMessage }] }); - } else { - dispatch({ type: 'RESET' }); - } handleError(error, { action: initialData?.id ? 'Update Manufacturer' : 'Create Manufacturer', metadata: { companyName: data.name } @@ -263,18 +241,16 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur diff --git a/src/components/admin/OperatorForm.tsx b/src/components/admin/OperatorForm.tsx index a689ad41..d90a20f0 100644 --- a/src/components/admin/OperatorForm.tsx +++ b/src/components/admin/OperatorForm.tsx @@ -1,9 +1,8 @@ -import { useState, useReducer } from 'react'; +import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { entitySchemas } from '@/lib/entityValidationSchemas'; -import { submissionReducer, canSubmit } from '@/lib/submissionStateMachine'; import { getErrorMessage } from '@/lib/errorHandler'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -62,10 +61,6 @@ export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormPr const { headquarters } = useCompanyHeadquarters(); const { user } = useAuth(); const navigate = useNavigate(); - const [submissionState, dispatch] = useReducer(submissionReducer, { - status: 'draft' as const, - data: initialData || {} - }); const { register, @@ -104,16 +99,7 @@ export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormPr return; } - if (!canSubmit(submissionState)) { - toast.error('Cannot submit in current state'); - return; - } - - dispatch({ type: 'VALIDATE', payload: data }); - - try { - dispatch({ type: 'SUBMIT', payload: { submissionId: crypto.randomUUID() } }); - + try { const formData = { ...data, company_type: 'operator' as const, @@ -122,20 +108,12 @@ export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormPr await onSubmit(formData); - dispatch({ type: 'SUBMISSION_COMPLETE' }); - // Only show success toast and close if not editing through moderation queue if (!initialData?.id) { toast.success('Operator submitted for review'); onCancel(); } } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - if (errorMessage.includes('validation')) { - dispatch({ type: 'VALIDATION_ERROR', payload: [{ field: 'general', message: errorMessage }] }); - } else { - dispatch({ type: 'RESET' }); - } handleError(error, { action: initialData?.id ? 'Update Operator' : 'Create Operator', metadata: { companyName: data.name } @@ -262,18 +240,16 @@ export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormPr diff --git a/src/components/admin/ParkForm.tsx b/src/components/admin/ParkForm.tsx index 33fb7f96..69db4302 100644 --- a/src/components/admin/ParkForm.tsx +++ b/src/components/admin/ParkForm.tsx @@ -1,10 +1,9 @@ -import { useState, useEffect, useReducer } from 'react'; +import { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { entitySchemas } from '@/lib/entityValidationSchemas'; import { validateSubmissionHandler } from '@/lib/entityFormValidation'; -import { submissionReducer, canSubmit } from '@/lib/submissionStateMachine'; import { getErrorMessage } from '@/lib/errorHandler'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; @@ -131,10 +130,6 @@ const STATUS_DB_TO_DISPLAY: Record = { export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: ParkFormProps) { const { isModerator } = useUserRole(); - const [submissionState, dispatch] = useReducer(submissionReducer, { - status: 'draft' as const, - data: initialData || {} - }); // Validate that onSubmit uses submission helpers (dev mode only) useEffect(() => { @@ -184,18 +179,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: }); - const handleFormSubmit = async (data: ParkFormData) => { - if (!canSubmit(submissionState)) { - toast({ - title: 'Cannot submit', - description: 'Please wait for the current operation to complete', - variant: 'destructive', - }); - return; - } - - dispatch({ type: 'VALIDATE', payload: data }); - + const handleFormSubmit = async (data: ParkFormData) => { try { dispatch({ type: 'SUBMIT', payload: { submissionId: crypto.randomUUID() } }); @@ -535,12 +519,11 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: {/* Form Actions */}
{onCancel && ( diff --git a/src/components/admin/PropertyOwnerForm.tsx b/src/components/admin/PropertyOwnerForm.tsx index 69906868..afd2207c 100644 --- a/src/components/admin/PropertyOwnerForm.tsx +++ b/src/components/admin/PropertyOwnerForm.tsx @@ -1,9 +1,8 @@ -import { useState, useReducer } from 'react'; +import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { entitySchemas } from '@/lib/entityValidationSchemas'; -import { submissionReducer, canSubmit } from '@/lib/submissionStateMachine'; import { getErrorMessage } from '@/lib/errorHandler'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -62,10 +61,6 @@ export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyO const { headquarters } = useCompanyHeadquarters(); const { user } = useAuth(); const navigate = useNavigate(); - const [submissionState, dispatch] = useReducer(submissionReducer, { - status: 'draft' as const, - data: initialData || {} - }); const { register, @@ -104,16 +99,7 @@ export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyO return; } - if (!canSubmit(submissionState)) { - toast.error('Cannot submit in current state'); - return; - } - - dispatch({ type: 'VALIDATE', payload: data }); - - try { - dispatch({ type: 'SUBMIT', payload: { submissionId: crypto.randomUUID() } }); - + try { const formData = { ...data, company_type: 'property_owner' as const, @@ -122,20 +108,12 @@ export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyO await onSubmit(formData); - dispatch({ type: 'SUBMISSION_COMPLETE' }); - // Only show success toast and close if not editing through moderation queue if (!initialData?.id) { toast.success('Property owner submitted for review'); onCancel(); } } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - if (errorMessage.includes('validation')) { - dispatch({ type: 'VALIDATION_ERROR', payload: [{ field: 'general', message: errorMessage }] }); - } else { - dispatch({ type: 'RESET' }); - } handleError(error, { action: initialData?.id ? 'Update Property Owner' : 'Create Property Owner', metadata: { companyName: data.name } @@ -262,18 +240,16 @@ export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyO
diff --git a/src/components/admin/RideForm.tsx b/src/components/admin/RideForm.tsx index 72b8a7e9..7a435df0 100644 --- a/src/components/admin/RideForm.tsx +++ b/src/components/admin/RideForm.tsx @@ -1,9 +1,8 @@ -import { useState, useEffect, useReducer } from 'react'; +import { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { validateSubmissionHandler } from '@/lib/entityFormValidation'; -import { submissionReducer, canSubmit } from '@/lib/submissionStateMachine'; import { getErrorMessage } from '@/lib/errorHandler'; import type { RideTechnicalSpec, RideCoasterStat, RideNameHistory } from '@/types/database'; import type { TempCompanyData, TempRideModelData } from '@/types/company'; @@ -121,10 +120,6 @@ const STATUS_DB_TO_DISPLAY: Record = { export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: RideFormProps) { const { isModerator } = useUserRole(); - const [submissionState, dispatch] = useReducer(submissionReducer, { - status: 'draft' as const, - data: initialData || {} - }); const { preferences } = useUnitPreferences(); const measurementSystem = preferences.measurement_system; @@ -225,20 +220,8 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: const selectedCategory = watch('category'); - const handleFormSubmit = async (data: RideFormData) => { - if (!canSubmit(submissionState)) { - toast({ - title: 'Cannot submit', - description: 'Please wait for the current operation to complete', - variant: 'destructive', - }); - return; - } - - dispatch({ type: 'VALIDATE', payload: data }); - + const handleFormSubmit = async (data: RideFormData) => { try { - dispatch({ type: 'SUBMIT', payload: { submissionId: crypto.randomUUID() } }); // Convert form values back to metric for storage const metricData = { @@ -270,8 +253,6 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: // Pass clean data to parent with extended fields await onSubmit(metricData); - dispatch({ type: 'SUBMISSION_COMPLETE' }); - toast({ title: isEditing ? "Ride Updated" : "Submission Sent", description: isEditing @@ -281,12 +262,6 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: : "Ride submitted for review" }); } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - if (errorMessage.includes('validation') || errorMessage.includes('required')) { - dispatch({ type: 'VALIDATION_ERROR', payload: [{ field: 'general', message: errorMessage }] }); - } else { - dispatch({ type: 'RESET' }); - } handleError(error, { action: isEditing ? 'Update Ride' : 'Create Ride', metadata: { @@ -806,12 +781,11 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: {/* Form Actions */}
{onCancel && ( diff --git a/src/components/admin/RideModelForm.tsx b/src/components/admin/RideModelForm.tsx index 2851157b..c17bbf44 100644 --- a/src/components/admin/RideModelForm.tsx +++ b/src/components/admin/RideModelForm.tsx @@ -1,11 +1,11 @@ -import { useState, useReducer } from 'react'; +import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { Button } from '@/components/ui/button'; import type { RideModelTechnicalSpec } from '@/types/database'; -import { submissionReducer, canSubmit } from '@/lib/submissionStateMachine'; import { getErrorMessage } from '@/lib/errorHandler'; +import { handleError } from '@/lib/errorHandler'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; @@ -77,10 +77,6 @@ export function RideModelForm({ unit?: string; display_order: number; }[]>([]); - const [submissionState, dispatch] = useReducer(submissionReducer, { - status: 'draft' as const, - data: initialData || {} - }); const { register, @@ -101,30 +97,17 @@ export function RideModelForm({ }); - const handleFormSubmit = (data: RideModelFormData) => { - if (!canSubmit(submissionState)) { - return; - } - - dispatch({ type: 'VALIDATE', payload: data }); - + const handleFormSubmit = (data: RideModelFormData) => { try { - dispatch({ type: 'SUBMIT', payload: { submissionId: crypto.randomUUID() } }); - // Include relational technical specs with extended type onSubmit({ ...data, _technical_specifications: technicalSpecs }); - - dispatch({ type: 'SUBMISSION_COMPLETE' }); } catch (error: unknown) { - const errorMessage = getErrorMessage(error); - if (errorMessage.includes('validation')) { - dispatch({ type: 'VALIDATION_ERROR', payload: [{ field: 'general', message: errorMessage }] }); - } else { - dispatch({ type: 'RESET' }); - } + handleError(error, { + action: initialData?.id ? 'Update Ride Model' : 'Create Ride Model' + }); } }; @@ -248,17 +231,15 @@ export function RideModelForm({ type="button" variant="outline" onClick={onCancel} - disabled={submissionState.status === 'validating' || submissionState.status === 'submitting'} > Cancel
diff --git a/src/lib/moderation/lockMonitor.ts b/src/lib/moderation/lockMonitor.ts index 89c422d1..69bb49dc 100644 --- a/src/lib/moderation/lockMonitor.ts +++ b/src/lib/moderation/lockMonitor.ts @@ -45,11 +45,18 @@ export function useLockMonitor( dispatch({ type: 'LOCK_EXPIRED' }); // Show toast with extension option - toast({ - title: 'Lock Expiring Soon', - description: 'Your lock on this submission will expire in less than 2 minutes. Click to extend.', - variant: 'default', - }); + toast({ + title: 'Lock Expiring Soon', + description: 'Your lock on this submission will expire in less than 2 minutes. Click below to extend.', + duration: Infinity, + }); + + // Also call extension function automatically after showing toast + if (itemId) { + setTimeout(() => { + handleExtendLock(itemId, dispatch); + }, 100); + } } }, 30000); // Check every 30 seconds diff --git a/src/lib/submissionStateMachine.ts b/src/lib/submissionStateMachine.ts index c0135580..8091ae45 100644 --- a/src/lib/submissionStateMachine.ts +++ b/src/lib/submissionStateMachine.ts @@ -64,7 +64,7 @@ export function submissionReducer( } return { status: 'validation_error', - data: (state as Extract).data, + data: state.data, errors: action.payload }; @@ -74,7 +74,7 @@ export function submissionReducer( } return { status: 'submitting', - data: (state as Extract).data, + data: state.data, submissionId: action.payload.submissionId }; @@ -84,7 +84,7 @@ export function submissionReducer( } return { status: 'pending_moderation', - submissionId: (state as Extract).submissionId + submissionId: state.submissionId }; case 'LOCK': @@ -93,7 +93,7 @@ export function submissionReducer( } return { status: 'locked', - submissionId: (state as Extract).submissionId, + submissionId: state.submissionId, lockedBy: action.payload.lockedBy, lockedUntil: action.payload.lockedUntil }; @@ -104,7 +104,7 @@ export function submissionReducer( } return { status: 'reviewing', - submissionId: (state as Extract).submissionId, + submissionId: state.submissionId, reviewerId: action.payload.reviewerId }; @@ -114,7 +114,7 @@ export function submissionReducer( } return { status: 'approved', - submissionId: (state as Extract).submissionId, + submissionId: state.submissionId, entityId: action.payload.entityId }; @@ -124,7 +124,7 @@ export function submissionReducer( } return { status: 'rejected', - submissionId: (state as Extract).submissionId, + submissionId: state.submissionId, reason: action.payload.reason }; @@ -132,13 +132,10 @@ export function submissionReducer( if (state.status !== 'reviewing' && state.status !== 'locked') { throw new Error(`Illegal transition: ${state.status} → escalated`); } - const submissionId = state.status === 'reviewing' - ? (state as Extract).submissionId - : (state as Extract).submissionId; return { status: 'escalated', - submissionId, + submissionId: state.submissionId, escalatedBy: action.payload.escalatedBy, reason: action.payload.reason };