mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 07:51:13 -05:00
Fix: Re-evaluate initial submission validation
This commit is contained in:
@@ -8,7 +8,71 @@ The submission flow has structured logging at each critical stage to enable debu
|
|||||||
|
|
||||||
## Logging Stages
|
## Logging Stages
|
||||||
|
|
||||||
### 1. Edit Stage
|
### 1. Location Selection Stage
|
||||||
|
**Location**: `src/components/admin/ParkForm.tsx` → `LocationSearch.onLocationSelect()`
|
||||||
|
|
||||||
|
**Log Points**:
|
||||||
|
- Location selected from search (when user picks from dropdown)
|
||||||
|
- Location set in form state (confirmation of setValue)
|
||||||
|
|
||||||
|
**Log Format**:
|
||||||
|
```typescript
|
||||||
|
console.info('[ParkForm] Location selected:', {
|
||||||
|
name: string,
|
||||||
|
city: string | undefined,
|
||||||
|
state_province: string | undefined,
|
||||||
|
country: string,
|
||||||
|
latitude: number,
|
||||||
|
longitude: number,
|
||||||
|
display_name: string
|
||||||
|
});
|
||||||
|
|
||||||
|
console.info('[ParkForm] Location set in form:', locationObject);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Form Submission Stage
|
||||||
|
**Location**: `src/components/admin/ParkForm.tsx` → `handleFormSubmit()`
|
||||||
|
|
||||||
|
**Log Points**:
|
||||||
|
- Form data being submitted (what's being passed to submission helper)
|
||||||
|
|
||||||
|
**Log Format**:
|
||||||
|
```typescript
|
||||||
|
console.info('[ParkForm] Submitting park data:', {
|
||||||
|
hasLocation: boolean,
|
||||||
|
hasLocationId: boolean,
|
||||||
|
locationData: object | undefined,
|
||||||
|
parkName: string,
|
||||||
|
isEditing: boolean
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Submission Helper Reception Stage
|
||||||
|
**Location**: `src/lib/entitySubmissionHelpers.ts` → `submitParkCreation()`
|
||||||
|
|
||||||
|
**Log Points**:
|
||||||
|
- Data received by submission helper (what arrived from form)
|
||||||
|
- Data being saved to database (temp_location_data structure)
|
||||||
|
|
||||||
|
**Log Format**:
|
||||||
|
```typescript
|
||||||
|
console.info('[submitParkCreation] Received data:', {
|
||||||
|
hasLocation: boolean,
|
||||||
|
hasLocationId: boolean,
|
||||||
|
locationData: object | undefined,
|
||||||
|
parkName: string,
|
||||||
|
hasComposite: boolean
|
||||||
|
});
|
||||||
|
|
||||||
|
console.info('[submitParkCreation] Saving to park_submissions:', {
|
||||||
|
name: string,
|
||||||
|
hasLocation: boolean,
|
||||||
|
hasLocationId: boolean,
|
||||||
|
temp_location_data: object | null
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Edit Stage
|
||||||
**Location**: `src/lib/submissionItemsService.ts` → `updateSubmissionItem()`
|
**Location**: `src/lib/submissionItemsService.ts` → `updateSubmissionItem()`
|
||||||
|
|
||||||
**Log Points**:
|
**Log Points**:
|
||||||
@@ -35,7 +99,7 @@ console.info('[Submission Flow] Saving park data', {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Validation Stage
|
### 5. Validation Stage
|
||||||
**Location**: `src/hooks/moderation/useModerationActions.ts` → `handleApproveSubmission()`
|
**Location**: `src/hooks/moderation/useModerationActions.ts` → `handleApproveSubmission()`
|
||||||
|
|
||||||
**Log Points**:
|
**Log Points**:
|
||||||
@@ -66,7 +130,7 @@ console.warn('[Submission Flow] Validation found blocking errors', {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Approval Stage
|
### 6. Approval Stage
|
||||||
**Location**: `src/lib/submissionItemsService.ts` → `approveSubmissionItems()`
|
**Location**: `src/lib/submissionItemsService.ts` → `approveSubmissionItems()`
|
||||||
|
|
||||||
**Log Points**:
|
**Log Points**:
|
||||||
@@ -99,36 +163,65 @@ console.info('[Submission Flow] Processing item for approval', {
|
|||||||
### Park Location Data
|
### Park Location Data
|
||||||
The most critical transformation logged is the park location data flow:
|
The most critical transformation logged is the park location data flow:
|
||||||
|
|
||||||
1. **Database Storage**: `temp_location_data` (JSONB in park_submissions)
|
1. **User Selection** (LocationSearch): OpenStreetMap result → `location` object
|
||||||
2. **Display/Edit**: `location` (transformed for form compatibility)
|
2. **Form State** (ParkForm): `setValue('location', location)`
|
||||||
3. **Validation**: `location` (transformed from temp_location_data)
|
3. **Form Submission** (ParkForm → submitParkCreation): `data.location` passed in submission
|
||||||
4. **Save**: `temp_location_data` (transformed back for storage)
|
4. **Database Storage** (submitParkCreation): `data.location` → `temp_location_data` (JSONB in park_submissions)
|
||||||
5. **Approval**: `location` (transformed from temp_location_data)
|
5. **Display/Edit**: `temp_location_data` → `location` (transformed for form compatibility)
|
||||||
|
6. **Validation**: `temp_location_data` → `location` (transformed for schema validation)
|
||||||
|
7. **Approval**: `location` used to create actual location record
|
||||||
|
|
||||||
**Why this matters**: Location validation errors typically indicate a break in this transformation chain.
|
**Why this matters**:
|
||||||
|
- If location is NULL in database but user selected one → Check stages 1-4
|
||||||
|
- If validation fails with "Location is required" → Check stages 5-6
|
||||||
|
- Location validation errors typically indicate a break in this transformation chain.
|
||||||
|
|
||||||
## Debugging Workflow
|
## Debugging Workflow
|
||||||
|
|
||||||
### To debug location validation errors:
|
### To debug "Location is required" validation errors:
|
||||||
|
|
||||||
1. **Check browser console** for `[Submission Flow]` logs
|
1. **Check browser console** for `[ParkForm]` and `[Submission Flow]` logs
|
||||||
2. **Verify data at each stage**:
|
2. **Verify data at each stage**:
|
||||||
```javascript
|
```javascript
|
||||||
// Edit stage - should show temp_location_data being saved
|
// Stage 1: Location selection
|
||||||
|
[ParkForm] Location selected: { name: "Farmington, Utah", latitude: 40.98, ... }
|
||||||
|
[ParkForm] Location set in form: { name: "Farmington, Utah", ... }
|
||||||
|
|
||||||
|
// Stage 2: Form submission
|
||||||
|
[ParkForm] Submitting park data { hasLocation: true, locationData: {...} }
|
||||||
|
|
||||||
|
// Stage 3: Submission helper receives data
|
||||||
|
[submitParkCreation] Received data { hasLocation: true, locationData: {...} }
|
||||||
|
[submitParkCreation] Saving to park_submissions { temp_location_data: {...} }
|
||||||
|
|
||||||
|
// Stage 4: Edit stage (if moderator edits later)
|
||||||
[Submission Flow] Saving park data { hasLocation: true, locationData: {...} }
|
[Submission Flow] Saving park data { hasLocation: true, locationData: {...} }
|
||||||
|
|
||||||
// Validation stage - should show location after transformation
|
// Stage 5: Validation stage
|
||||||
[Submission Flow] Transformed park data { hasLocation: true, transformedHasLocation: true }
|
[Submission Flow] Transformed park data { hasLocation: true, transformedHasLocation: true }
|
||||||
|
|
||||||
// Approval stage - should show location present
|
// Stage 6: Approval stage
|
||||||
[Submission Flow] Processing item { hasLocation: true, locationData: {...} }
|
[Submission Flow] Processing item { hasLocation: true, locationData: {...} }
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Look for missing data**:
|
3. **Look for missing data**:
|
||||||
- If `hasLocation: false` in "Saving park data" → Edit form didn't capture location
|
- If `[ParkForm] Location selected` missing → User didn't select location from dropdown
|
||||||
|
- If `hasLocation: false` in form submission → Location not set in form state (possible React Hook Form issue)
|
||||||
|
- If `hasLocation: true` in submission but NULL in database → Database write failed (check errors)
|
||||||
- If `hasLocation: true` but `transformedHasLocation: false` → Transformation failed
|
- If `hasLocation: true` but `transformedHasLocation: false` → Transformation failed
|
||||||
- If validation logs missing → Check database query/fetch
|
- If validation logs missing → Check database query/fetch
|
||||||
|
|
||||||
|
### To debug NULL location in new submissions:
|
||||||
|
|
||||||
|
1. **Open browser console** before creating submission
|
||||||
|
2. **Select location** and verify `[ParkForm] Location selected` appears
|
||||||
|
3. **Submit form** and verify `[ParkForm] Submitting park data` shows `hasLocation: true`
|
||||||
|
4. **Check** `[submitParkCreation] Saving to park_submissions` shows `temp_location_data` is not null
|
||||||
|
5. **If location was selected but is NULL in database**:
|
||||||
|
- Form state was cleared (page refresh/navigation before submit)
|
||||||
|
- React Hook Form setValue didn't work (check "Location set in form" log)
|
||||||
|
- Database write succeeded but data was lost (check for errors)
|
||||||
|
|
||||||
## Error Logging Integration
|
## Error Logging Integration
|
||||||
|
|
||||||
Structured errors use the `handleError()` utility from `@/lib/errorHandler`:
|
Structured errors use the `handleError()` utility from `@/lib/errorHandler`:
|
||||||
|
|||||||
@@ -271,13 +271,24 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
|||||||
(tempNewPropertyOwner ? undefined : selectedPropertyOwnerId);
|
(tempNewPropertyOwner ? undefined : selectedPropertyOwnerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await onSubmit({
|
// Debug: Log what's being submitted
|
||||||
|
const submissionData = {
|
||||||
...data,
|
...data,
|
||||||
operator_id: finalOperatorId,
|
operator_id: finalOperatorId,
|
||||||
property_owner_id: finalPropertyOwnerId,
|
property_owner_id: finalPropertyOwnerId,
|
||||||
_compositeSubmission: (tempNewOperator || tempNewPropertyOwner) ? submissionContent : undefined
|
_compositeSubmission: (tempNewOperator || tempNewPropertyOwner) ? submissionContent : undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
console.info('[ParkForm] Submitting park data:', {
|
||||||
|
hasLocation: !!submissionData.location,
|
||||||
|
hasLocationId: !!submissionData.location_id,
|
||||||
|
locationData: submissionData.location,
|
||||||
|
parkName: submissionData.name,
|
||||||
|
isEditing
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await onSubmit(submissionData);
|
||||||
|
|
||||||
// Parent component handles success feedback
|
// Parent component handles success feedback
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
@@ -426,7 +437,9 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
|||||||
</Label>
|
</Label>
|
||||||
<LocationSearch
|
<LocationSearch
|
||||||
onLocationSelect={(location) => {
|
onLocationSelect={(location) => {
|
||||||
|
console.info('[ParkForm] Location selected:', location);
|
||||||
setValue('location', location);
|
setValue('location', location);
|
||||||
|
console.info('[ParkForm] Location set in form:', watch('location'));
|
||||||
// Manually trigger validation for the location field
|
// Manually trigger validation for the location field
|
||||||
trigger('location');
|
trigger('location');
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -557,6 +557,14 @@ export async function submitParkCreation(
|
|||||||
data: ParkFormData & { _compositeSubmission?: any },
|
data: ParkFormData & { _compositeSubmission?: any },
|
||||||
userId: string
|
userId: string
|
||||||
): Promise<{ submitted: boolean; submissionId: string }> {
|
): Promise<{ submitted: boolean; submissionId: string }> {
|
||||||
|
console.info('[submitParkCreation] Received data:', {
|
||||||
|
hasLocation: !!data.location,
|
||||||
|
hasLocationId: !!data.location_id,
|
||||||
|
locationData: data.location,
|
||||||
|
parkName: data.name,
|
||||||
|
hasComposite: !!data._compositeSubmission
|
||||||
|
});
|
||||||
|
|
||||||
// Validate required fields client-side
|
// Validate required fields client-side
|
||||||
assertValid(validateParkCreateFields(data));
|
assertValid(validateParkCreateFields(data));
|
||||||
|
|
||||||
@@ -663,6 +671,25 @@ export async function submitParkCreation(
|
|||||||
const cardImage = (cardIndex !== null && cardIndex !== undefined) ? uploadedImages[cardIndex] : null;
|
const cardImage = (cardIndex !== null && cardIndex !== undefined) ? uploadedImages[cardIndex] : null;
|
||||||
|
|
||||||
// Insert into relational park_submissions table
|
// Insert into relational park_submissions table
|
||||||
|
const tempLocationData = data.location ? {
|
||||||
|
name: data.location.name,
|
||||||
|
city: data.location.city || null,
|
||||||
|
state_province: data.location.state_province || null,
|
||||||
|
country: data.location.country,
|
||||||
|
latitude: data.location.latitude,
|
||||||
|
longitude: data.location.longitude,
|
||||||
|
timezone: data.location.timezone || null,
|
||||||
|
postal_code: data.location.postal_code || null,
|
||||||
|
display_name: data.location.display_name
|
||||||
|
} : null;
|
||||||
|
|
||||||
|
console.info('[submitParkCreation] Saving to park_submissions:', {
|
||||||
|
name: data.name,
|
||||||
|
hasLocation: !!data.location,
|
||||||
|
hasLocationId: !!data.location_id,
|
||||||
|
temp_location_data: tempLocationData
|
||||||
|
});
|
||||||
|
|
||||||
const { data: parkSubmission, error: parkSubmissionError } = await supabase
|
const { data: parkSubmission, error: parkSubmissionError } = await supabase
|
||||||
.from('park_submissions' as any)
|
.from('park_submissions' as any)
|
||||||
.insert({
|
.insert({
|
||||||
@@ -680,17 +707,7 @@ export async function submitParkCreation(
|
|||||||
operator_id: data.operator_id || null,
|
operator_id: data.operator_id || null,
|
||||||
property_owner_id: data.property_owner_id || null,
|
property_owner_id: data.property_owner_id || null,
|
||||||
location_id: data.location_id || null,
|
location_id: data.location_id || null,
|
||||||
temp_location_data: data.location ? {
|
temp_location_data: tempLocationData,
|
||||||
name: data.location.name,
|
|
||||||
city: data.location.city || null,
|
|
||||||
state_province: data.location.state_province || null,
|
|
||||||
country: data.location.country,
|
|
||||||
latitude: data.location.latitude,
|
|
||||||
longitude: data.location.longitude,
|
|
||||||
timezone: data.location.timezone || null,
|
|
||||||
postal_code: data.location.postal_code || null,
|
|
||||||
display_name: data.location.display_name
|
|
||||||
} : null,
|
|
||||||
banner_image_url: bannerImage?.url || data.banner_image_url || null,
|
banner_image_url: bannerImage?.url || data.banner_image_url || null,
|
||||||
banner_image_id: bannerImage?.cloudflare_id || data.banner_image_id || null,
|
banner_image_id: bannerImage?.cloudflare_id || data.banner_image_id || null,
|
||||||
card_image_url: cardImage?.url || data.card_image_url || null,
|
card_image_url: cardImage?.url || data.card_image_url || null,
|
||||||
|
|||||||
Reference in New Issue
Block a user