mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 14:31:11 -05:00
Fix: Implement company submission and validation plan
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
@@ -15,6 +16,10 @@ import { useCompanyHeadquarters } from '@/hooks/useAutocompleteData';
|
|||||||
import { useUserRole } from '@/hooks/useUserRole';
|
import { useUserRole } from '@/hooks/useUserRole';
|
||||||
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
||||||
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
|
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
|
||||||
|
import { submitDesignerCreation, submitDesignerUpdate } from '@/lib/entitySubmissionHelpers';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
type DesignerFormData = z.infer<typeof entitySchemas.designer>;
|
type DesignerFormData = z.infer<typeof entitySchemas.designer>;
|
||||||
|
|
||||||
@@ -53,6 +58,9 @@ interface DesignerFormProps {
|
|||||||
export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormProps) {
|
export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormProps) {
|
||||||
const { isModerator } = useUserRole();
|
const { isModerator } = useUserRole();
|
||||||
const { headquarters } = useCompanyHeadquarters();
|
const { headquarters } = useCompanyHeadquarters();
|
||||||
|
const { user } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@@ -84,7 +92,29 @@ export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormPr
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form onSubmit={handleSubmit((data) => onSubmit(data as unknown as DesignerFormData))} className="space-y-6">
|
<form onSubmit={handleSubmit(async (data) => {
|
||||||
|
if (!user) {
|
||||||
|
toast.error('You must be logged in to submit');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
try {
|
||||||
|
if (initialData?.id) {
|
||||||
|
await submitDesignerUpdate(initialData.id, data as unknown as DesignerFormData, user.id);
|
||||||
|
toast.success('Designer update submitted for review');
|
||||||
|
} else {
|
||||||
|
await submitDesignerCreation(data as unknown as DesignerFormData, user.id);
|
||||||
|
toast.success('Designer submitted for review');
|
||||||
|
}
|
||||||
|
onCancel();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Submission error:', error);
|
||||||
|
toast.error(error instanceof Error ? error.message : 'Failed to submit designer');
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
})} className="space-y-6">
|
||||||
{/* Basic Information */}
|
{/* Basic Information */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -202,13 +232,13 @@ export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormPr
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex gap-3 justify-end">
|
<div className="flex gap-3 justify-end">
|
||||||
<Button type="button" variant="outline" onClick={onCancel}>
|
<Button type="button" variant="outline" onClick={onCancel} disabled={isSubmitting}>
|
||||||
<X className="w-4 h-4 mr-2" />
|
<X className="w-4 h-4 mr-2" />
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit">
|
<Button type="submit" disabled={isSubmitting}>
|
||||||
<Save className="w-4 h-4 mr-2" />
|
<Save className="w-4 h-4 mr-2" />
|
||||||
Save Designer
|
{isSubmitting ? 'Submitting...' : 'Save Designer'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
@@ -15,6 +16,10 @@ import { useCompanyHeadquarters } from '@/hooks/useAutocompleteData';
|
|||||||
import { useUserRole } from '@/hooks/useUserRole';
|
import { useUserRole } from '@/hooks/useUserRole';
|
||||||
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
||||||
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
|
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
|
||||||
|
import { submitManufacturerCreation, submitManufacturerUpdate } from '@/lib/entitySubmissionHelpers';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
type ManufacturerFormData = z.infer<typeof entitySchemas.manufacturer>;
|
type ManufacturerFormData = z.infer<typeof entitySchemas.manufacturer>;
|
||||||
|
|
||||||
@@ -55,6 +60,9 @@ interface ManufacturerFormProps {
|
|||||||
export function ManufacturerForm({ onSubmit, onCancel, initialData }: ManufacturerFormProps) {
|
export function ManufacturerForm({ onSubmit, onCancel, initialData }: ManufacturerFormProps) {
|
||||||
const { isModerator } = useUserRole();
|
const { isModerator } = useUserRole();
|
||||||
const { headquarters } = useCompanyHeadquarters();
|
const { headquarters } = useCompanyHeadquarters();
|
||||||
|
const { user } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@@ -88,7 +96,29 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form onSubmit={handleSubmit((data) => onSubmit(data as unknown as ManufacturerFormData))} className="space-y-6">
|
<form onSubmit={handleSubmit(async (data) => {
|
||||||
|
if (!user) {
|
||||||
|
toast.error('You must be logged in to submit');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
try {
|
||||||
|
if (initialData?.id) {
|
||||||
|
await submitManufacturerUpdate(initialData.id, data as unknown as ManufacturerFormData, user.id);
|
||||||
|
toast.success('Manufacturer update submitted for review');
|
||||||
|
} else {
|
||||||
|
await submitManufacturerCreation(data as unknown as ManufacturerFormData, user.id);
|
||||||
|
toast.success('Manufacturer submitted for review');
|
||||||
|
}
|
||||||
|
onCancel();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Submission error:', error);
|
||||||
|
toast.error(error instanceof Error ? error.message : 'Failed to submit manufacturer');
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
})} className="space-y-6">
|
||||||
{/* Basic Information */}
|
{/* Basic Information */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -204,13 +234,13 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex gap-3 justify-end">
|
<div className="flex gap-3 justify-end">
|
||||||
<Button type="button" variant="outline" onClick={onCancel}>
|
<Button type="button" variant="outline" onClick={onCancel} disabled={isSubmitting}>
|
||||||
<X className="w-4 h-4 mr-2" />
|
<X className="w-4 h-4 mr-2" />
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit">
|
<Button type="submit" disabled={isSubmitting}>
|
||||||
<Save className="w-4 h-4 mr-2" />
|
<Save className="w-4 h-4 mr-2" />
|
||||||
Save Manufacturer
|
{isSubmitting ? 'Submitting...' : 'Save Manufacturer'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
@@ -15,6 +16,10 @@ import { useCompanyHeadquarters } from '@/hooks/useAutocompleteData';
|
|||||||
import { useUserRole } from '@/hooks/useUserRole';
|
import { useUserRole } from '@/hooks/useUserRole';
|
||||||
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
||||||
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
|
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
|
||||||
|
import { submitOperatorCreation, submitOperatorUpdate } from '@/lib/entitySubmissionHelpers';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
type OperatorFormData = z.infer<typeof entitySchemas.operator>;
|
type OperatorFormData = z.infer<typeof entitySchemas.operator>;
|
||||||
|
|
||||||
@@ -55,6 +60,9 @@ interface OperatorFormProps {
|
|||||||
export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormProps) {
|
export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormProps) {
|
||||||
const { isModerator } = useUserRole();
|
const { isModerator } = useUserRole();
|
||||||
const { headquarters } = useCompanyHeadquarters();
|
const { headquarters } = useCompanyHeadquarters();
|
||||||
|
const { user } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@@ -86,7 +94,29 @@ export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormPr
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form onSubmit={handleSubmit((data) => onSubmit(data as unknown as OperatorFormData))} className="space-y-6">
|
<form onSubmit={handleSubmit(async (data) => {
|
||||||
|
if (!user) {
|
||||||
|
toast.error('You must be logged in to submit');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
try {
|
||||||
|
if (initialData?.id) {
|
||||||
|
await submitOperatorUpdate(initialData.id, data as unknown as OperatorFormData, user.id);
|
||||||
|
toast.success('Operator update submitted for review');
|
||||||
|
} else {
|
||||||
|
await submitOperatorCreation(data as unknown as OperatorFormData, user.id);
|
||||||
|
toast.success('Operator submitted for review');
|
||||||
|
}
|
||||||
|
onCancel();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Submission error:', error);
|
||||||
|
toast.error(error instanceof Error ? error.message : 'Failed to submit operator');
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
})} className="space-y-6">
|
||||||
{/* Basic Information */}
|
{/* Basic Information */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -204,13 +234,13 @@ export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormPr
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex gap-3 justify-end">
|
<div className="flex gap-3 justify-end">
|
||||||
<Button type="button" variant="outline" onClick={onCancel}>
|
<Button type="button" variant="outline" onClick={onCancel} disabled={isSubmitting}>
|
||||||
<X className="w-4 h-4 mr-2" />
|
<X className="w-4 h-4 mr-2" />
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit">
|
<Button type="submit" disabled={isSubmitting}>
|
||||||
<Save className="w-4 h-4 mr-2" />
|
<Save className="w-4 h-4 mr-2" />
|
||||||
Save Operator
|
{isSubmitting ? 'Submitting...' : 'Save Operator'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
@@ -14,6 +15,10 @@ import { useCompanyHeadquarters } from '@/hooks/useAutocompleteData';
|
|||||||
import { useUserRole } from '@/hooks/useUserRole';
|
import { useUserRole } from '@/hooks/useUserRole';
|
||||||
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
||||||
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
|
import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input';
|
||||||
|
import { submitPropertyOwnerCreation, submitPropertyOwnerUpdate } from '@/lib/entitySubmissionHelpers';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const propertyOwnerSchema = z.object({
|
const propertyOwnerSchema = z.object({
|
||||||
name: z.string().min(1, 'Name is required'),
|
name: z.string().min(1, 'Name is required'),
|
||||||
@@ -82,6 +87,9 @@ interface PropertyOwnerFormProps {
|
|||||||
export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyOwnerFormProps) {
|
export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyOwnerFormProps) {
|
||||||
const { isModerator } = useUserRole();
|
const { isModerator } = useUserRole();
|
||||||
const { headquarters } = useCompanyHeadquarters();
|
const { headquarters } = useCompanyHeadquarters();
|
||||||
|
const { user } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@@ -113,7 +121,29 @@ export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyO
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form onSubmit={handleSubmit((data) => onSubmit(data as unknown as PropertyOwnerFormData))} className="space-y-6">
|
<form onSubmit={handleSubmit(async (data) => {
|
||||||
|
if (!user) {
|
||||||
|
toast.error('You must be logged in to submit');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
try {
|
||||||
|
if (initialData?.id) {
|
||||||
|
await submitPropertyOwnerUpdate(initialData.id, data as unknown as PropertyOwnerFormData, user.id);
|
||||||
|
toast.success('Property owner update submitted for review');
|
||||||
|
} else {
|
||||||
|
await submitPropertyOwnerCreation(data as unknown as PropertyOwnerFormData, user.id);
|
||||||
|
toast.success('Property owner submitted for review');
|
||||||
|
}
|
||||||
|
onCancel();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Submission error:', error);
|
||||||
|
toast.error(error instanceof Error ? error.message : 'Failed to submit property owner');
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
})} className="space-y-6">
|
||||||
{/* Basic Information */}
|
{/* Basic Information */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -231,13 +261,13 @@ export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyO
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex gap-3 justify-end">
|
<div className="flex gap-3 justify-end">
|
||||||
<Button type="button" variant="outline" onClick={onCancel}>
|
<Button type="button" variant="outline" onClick={onCancel} disabled={isSubmitting}>
|
||||||
<X className="w-4 h-4 mr-2" />
|
<X className="w-4 h-4 mr-2" />
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit">
|
<Button type="submit" disabled={isSubmitting}>
|
||||||
<Save className="w-4 h-4 mr-2" />
|
<Save className="w-4 h-4 mr-2" />
|
||||||
Save Property Owner
|
{isSubmitting ? 'Submitting...' : 'Save Property Owner'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -117,6 +117,23 @@ export interface RideFormData {
|
|||||||
card_image_id?: string;
|
card_image_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CompanyFormData {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
description?: string;
|
||||||
|
person_type: 'company' | 'individual' | 'firm' | 'organization';
|
||||||
|
founded_year?: number;
|
||||||
|
founded_date?: string;
|
||||||
|
founded_date_precision?: string;
|
||||||
|
headquarters_location?: string;
|
||||||
|
website_url?: string;
|
||||||
|
images?: ImageAssignments;
|
||||||
|
banner_image_url?: string;
|
||||||
|
banner_image_id?: string;
|
||||||
|
card_image_url?: string;
|
||||||
|
card_image_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ⚠️ CRITICAL SECURITY PATTERN ⚠️
|
* ⚠️ CRITICAL SECURITY PATTERN ⚠️
|
||||||
*
|
*
|
||||||
@@ -438,3 +455,432 @@ export async function submitRideUpdate(
|
|||||||
|
|
||||||
return { submitted: true, submissionId: submissionData.id };
|
return { submitted: true, submissionId: submissionData.id };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ⚠️ CRITICAL SECURITY PATTERN ⚠️
|
||||||
|
*
|
||||||
|
* Submits a new manufacturer for creation through the moderation queue.
|
||||||
|
*/
|
||||||
|
export async function submitManufacturerCreation(
|
||||||
|
data: CompanyFormData,
|
||||||
|
userId: string
|
||||||
|
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||||
|
let processedImages = data.images;
|
||||||
|
if (data.images?.uploaded && data.images.uploaded.length > 0) {
|
||||||
|
try {
|
||||||
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to upload images:', error);
|
||||||
|
throw new Error('Failed to upload images. Please check your connection and try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
|
.from('content_submissions')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
submission_type: 'manufacturer',
|
||||||
|
content: { action: 'create' },
|
||||||
|
status: 'pending'
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (submissionError) throw submissionError;
|
||||||
|
|
||||||
|
const { error: itemError } = await supabase
|
||||||
|
.from('submission_items')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
item_type: 'manufacturer',
|
||||||
|
item_data: {
|
||||||
|
...data,
|
||||||
|
company_type: 'manufacturer',
|
||||||
|
images: processedImages as unknown as Json
|
||||||
|
},
|
||||||
|
status: 'pending',
|
||||||
|
order_index: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemError) throw itemError;
|
||||||
|
|
||||||
|
return { submitted: true, submissionId: submissionData.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitManufacturerUpdate(
|
||||||
|
companyId: string,
|
||||||
|
data: CompanyFormData,
|
||||||
|
userId: string
|
||||||
|
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||||
|
const { data: existingCompany, error: fetchError } = await supabase
|
||||||
|
.from('companies')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', companyId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (fetchError) throw new Error(`Failed to fetch manufacturer: ${fetchError.message}`);
|
||||||
|
if (!existingCompany) throw new Error('Manufacturer not found');
|
||||||
|
|
||||||
|
let processedImages = data.images;
|
||||||
|
if (data.images?.uploaded && data.images.uploaded.length > 0) {
|
||||||
|
try {
|
||||||
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to upload images:', error);
|
||||||
|
throw new Error('Failed to upload images. Please check your connection and try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
|
.from('content_submissions')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
submission_type: 'manufacturer',
|
||||||
|
content: { action: 'edit', company_id: companyId },
|
||||||
|
status: 'pending'
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (submissionError) throw submissionError;
|
||||||
|
|
||||||
|
const { error: itemError } = await supabase
|
||||||
|
.from('submission_items')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
item_type: 'manufacturer',
|
||||||
|
item_data: {
|
||||||
|
...data,
|
||||||
|
company_id: companyId,
|
||||||
|
company_type: 'manufacturer',
|
||||||
|
images: processedImages as any
|
||||||
|
},
|
||||||
|
original_data: JSON.parse(JSON.stringify(existingCompany)),
|
||||||
|
status: 'pending',
|
||||||
|
order_index: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemError) throw itemError;
|
||||||
|
|
||||||
|
return { submitted: true, submissionId: submissionData.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitDesignerCreation(
|
||||||
|
data: CompanyFormData,
|
||||||
|
userId: string
|
||||||
|
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||||
|
let processedImages = data.images;
|
||||||
|
if (data.images?.uploaded && data.images.uploaded.length > 0) {
|
||||||
|
try {
|
||||||
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to upload images:', error);
|
||||||
|
throw new Error('Failed to upload images. Please check your connection and try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
|
.from('content_submissions')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
submission_type: 'designer',
|
||||||
|
content: { action: 'create' },
|
||||||
|
status: 'pending'
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (submissionError) throw submissionError;
|
||||||
|
|
||||||
|
const { error: itemError } = await supabase
|
||||||
|
.from('submission_items')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
item_type: 'designer',
|
||||||
|
item_data: {
|
||||||
|
...data,
|
||||||
|
company_type: 'designer',
|
||||||
|
images: processedImages as unknown as Json
|
||||||
|
},
|
||||||
|
status: 'pending',
|
||||||
|
order_index: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemError) throw itemError;
|
||||||
|
|
||||||
|
return { submitted: true, submissionId: submissionData.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitDesignerUpdate(
|
||||||
|
companyId: string,
|
||||||
|
data: CompanyFormData,
|
||||||
|
userId: string
|
||||||
|
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||||
|
const { data: existingCompany, error: fetchError } = await supabase
|
||||||
|
.from('companies')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', companyId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (fetchError) throw new Error(`Failed to fetch designer: ${fetchError.message}`);
|
||||||
|
if (!existingCompany) throw new Error('Designer not found');
|
||||||
|
|
||||||
|
let processedImages = data.images;
|
||||||
|
if (data.images?.uploaded && data.images.uploaded.length > 0) {
|
||||||
|
try {
|
||||||
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to upload images:', error);
|
||||||
|
throw new Error('Failed to upload images. Please check your connection and try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
|
.from('content_submissions')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
submission_type: 'designer',
|
||||||
|
content: { action: 'edit', company_id: companyId },
|
||||||
|
status: 'pending'
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (submissionError) throw submissionError;
|
||||||
|
|
||||||
|
const { error: itemError } = await supabase
|
||||||
|
.from('submission_items')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
item_type: 'designer',
|
||||||
|
item_data: {
|
||||||
|
...data,
|
||||||
|
company_id: companyId,
|
||||||
|
company_type: 'designer',
|
||||||
|
images: processedImages as any
|
||||||
|
},
|
||||||
|
original_data: JSON.parse(JSON.stringify(existingCompany)),
|
||||||
|
status: 'pending',
|
||||||
|
order_index: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemError) throw itemError;
|
||||||
|
|
||||||
|
return { submitted: true, submissionId: submissionData.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitOperatorCreation(
|
||||||
|
data: CompanyFormData,
|
||||||
|
userId: string
|
||||||
|
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||||
|
let processedImages = data.images;
|
||||||
|
if (data.images?.uploaded && data.images.uploaded.length > 0) {
|
||||||
|
try {
|
||||||
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to upload images:', error);
|
||||||
|
throw new Error('Failed to upload images. Please check your connection and try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
|
.from('content_submissions')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
submission_type: 'operator',
|
||||||
|
content: { action: 'create' },
|
||||||
|
status: 'pending'
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (submissionError) throw submissionError;
|
||||||
|
|
||||||
|
const { error: itemError } = await supabase
|
||||||
|
.from('submission_items')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
item_type: 'operator',
|
||||||
|
item_data: {
|
||||||
|
...data,
|
||||||
|
company_type: 'operator',
|
||||||
|
images: processedImages as unknown as Json
|
||||||
|
},
|
||||||
|
status: 'pending',
|
||||||
|
order_index: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemError) throw itemError;
|
||||||
|
|
||||||
|
return { submitted: true, submissionId: submissionData.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitOperatorUpdate(
|
||||||
|
companyId: string,
|
||||||
|
data: CompanyFormData,
|
||||||
|
userId: string
|
||||||
|
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||||
|
const { data: existingCompany, error: fetchError } = await supabase
|
||||||
|
.from('companies')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', companyId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (fetchError) throw new Error(`Failed to fetch operator: ${fetchError.message}`);
|
||||||
|
if (!existingCompany) throw new Error('Operator not found');
|
||||||
|
|
||||||
|
let processedImages = data.images;
|
||||||
|
if (data.images?.uploaded && data.images.uploaded.length > 0) {
|
||||||
|
try {
|
||||||
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to upload images:', error);
|
||||||
|
throw new Error('Failed to upload images. Please check your connection and try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
|
.from('content_submissions')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
submission_type: 'operator',
|
||||||
|
content: { action: 'edit', company_id: companyId },
|
||||||
|
status: 'pending'
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (submissionError) throw submissionError;
|
||||||
|
|
||||||
|
const { error: itemError } = await supabase
|
||||||
|
.from('submission_items')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
item_type: 'operator',
|
||||||
|
item_data: {
|
||||||
|
...data,
|
||||||
|
company_id: companyId,
|
||||||
|
company_type: 'operator',
|
||||||
|
images: processedImages as any
|
||||||
|
},
|
||||||
|
original_data: JSON.parse(JSON.stringify(existingCompany)),
|
||||||
|
status: 'pending',
|
||||||
|
order_index: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemError) throw itemError;
|
||||||
|
|
||||||
|
return { submitted: true, submissionId: submissionData.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitPropertyOwnerCreation(
|
||||||
|
data: CompanyFormData,
|
||||||
|
userId: string
|
||||||
|
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||||
|
let processedImages = data.images;
|
||||||
|
if (data.images?.uploaded && data.images.uploaded.length > 0) {
|
||||||
|
try {
|
||||||
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to upload images:', error);
|
||||||
|
throw new Error('Failed to upload images. Please check your connection and try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
|
.from('content_submissions')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
submission_type: 'property_owner',
|
||||||
|
content: { action: 'create' },
|
||||||
|
status: 'pending'
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (submissionError) throw submissionError;
|
||||||
|
|
||||||
|
const { error: itemError } = await supabase
|
||||||
|
.from('submission_items')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
item_type: 'property_owner',
|
||||||
|
item_data: {
|
||||||
|
...data,
|
||||||
|
company_type: 'property_owner',
|
||||||
|
images: processedImages as unknown as Json
|
||||||
|
},
|
||||||
|
status: 'pending',
|
||||||
|
order_index: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemError) throw itemError;
|
||||||
|
|
||||||
|
return { submitted: true, submissionId: submissionData.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitPropertyOwnerUpdate(
|
||||||
|
companyId: string,
|
||||||
|
data: CompanyFormData,
|
||||||
|
userId: string
|
||||||
|
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||||
|
const { data: existingCompany, error: fetchError } = await supabase
|
||||||
|
.from('companies')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', companyId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (fetchError) throw new Error(`Failed to fetch property owner: ${fetchError.message}`);
|
||||||
|
if (!existingCompany) throw new Error('Property owner not found');
|
||||||
|
|
||||||
|
let processedImages = data.images;
|
||||||
|
if (data.images?.uploaded && data.images.uploaded.length > 0) {
|
||||||
|
try {
|
||||||
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to upload images:', error);
|
||||||
|
throw new Error('Failed to upload images. Please check your connection and try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
|
.from('content_submissions')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
submission_type: 'property_owner',
|
||||||
|
content: { action: 'edit', company_id: companyId },
|
||||||
|
status: 'pending'
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (submissionError) throw submissionError;
|
||||||
|
|
||||||
|
const { error: itemError } = await supabase
|
||||||
|
.from('submission_items')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
item_type: 'property_owner',
|
||||||
|
item_data: {
|
||||||
|
...data,
|
||||||
|
company_id: companyId,
|
||||||
|
company_type: 'property_owner',
|
||||||
|
images: processedImages as any
|
||||||
|
},
|
||||||
|
original_data: JSON.parse(JSON.stringify(existingCompany)),
|
||||||
|
status: 'pending',
|
||||||
|
order_index: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemError) throw itemError;
|
||||||
|
|
||||||
|
return { submitted: true, submissionId: submissionData.id };
|
||||||
|
}
|
||||||
|
|||||||
@@ -96,11 +96,11 @@ export const rideValidationSchema = z.object({
|
|||||||
|
|
||||||
// Company Schema (Manufacturer, Designer, Operator, Property Owner)
|
// Company Schema (Manufacturer, Designer, Operator, Property Owner)
|
||||||
export const companyValidationSchema = z.object({
|
export const companyValidationSchema = z.object({
|
||||||
name: z.string().min(1, 'Company name is required').max(200, 'Name must be less than 200 characters'),
|
name: z.string().trim().min(1, 'Company name is required').max(200, 'Name must be less than 200 characters'),
|
||||||
slug: z.string().min(1, 'Slug is required').regex(/^[a-z0-9-]+$/, 'Slug must contain only lowercase letters, numbers, and hyphens'),
|
slug: z.string().trim().min(1, 'Slug is required').regex(/^[a-z0-9-]+$/, 'Slug must contain only lowercase letters, numbers, and hyphens'),
|
||||||
|
company_type: z.enum(['manufacturer', 'designer', 'operator', 'property_owner']).optional(),
|
||||||
description: z.string().max(2000, 'Description must be less than 2000 characters').optional(),
|
description: z.string().max(2000, 'Description must be less than 2000 characters').optional(),
|
||||||
company_type: z.string().min(1, 'Company type is required'),
|
person_type: z.enum(['company', 'individual', 'firm', 'organization']),
|
||||||
person_type: z.enum(['company', 'individual', 'firm', 'organization']).optional(),
|
|
||||||
founded_year: z.number().min(1800, 'Founded year must be after 1800').max(currentYear, `Founded year cannot be in the future`).optional(),
|
founded_year: z.number().min(1800, 'Founded year must be after 1800').max(currentYear, `Founded year cannot be in the future`).optional(),
|
||||||
founded_date: z.string().optional(),
|
founded_date: z.string().optional(),
|
||||||
founded_date_precision: z.enum(['day', 'month', 'year']).optional(),
|
founded_date_precision: z.enum(['day', 'month', 'year']).optional(),
|
||||||
|
|||||||
@@ -72,7 +72,11 @@ export function validateEntityData(entityType: string, data: any): ValidationRes
|
|||||||
case 'designer':
|
case 'designer':
|
||||||
case 'operator':
|
case 'operator':
|
||||||
case 'property_owner':
|
case 'property_owner':
|
||||||
if (!data.company_type) errors.push('Company type is required');
|
if (!data.company_type) {
|
||||||
|
errors.push(`Company type is required (expected: ${entityType})`);
|
||||||
|
} else if (data.company_type !== entityType) {
|
||||||
|
errors.push(`Company type mismatch: expected '${entityType}' but got '${data.company_type}'`);
|
||||||
|
}
|
||||||
if (data.founded_year) {
|
if (data.founded_year) {
|
||||||
const year = parseInt(data.founded_year);
|
const year = parseInt(data.founded_year);
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
|
|||||||
Reference in New Issue
Block a user