"use client"; import { useState, useCallback } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useRouter } from "next/navigation"; import { useAuthGuard } from "@/hooks/useAuthGuard"; import { useDocumentTitle } from "@/hooks/useDocumentTitle"; import { useResource } from "@/hooks/useResource"; import { useReturnTo } from "@/hooks/useReturnTo"; import { withReturnTo } from "@/lib/return-to"; import { divesAPI, Dive } from "@/lib/api/dives"; import { buildDiveUpdate, diveToFormValues, diveUpdateSchema, DiveUpdateInput, emptyDiveFormValues, } from "@/lib/validations/dive"; import { useMixtureFieldArray } from "@/components/dives/mixture-fields"; import { usePendingDiveFiles } from "@/components/dives/use-pending-dive-files"; import { useDiveFormVisibility } from "@/hooks/useDiveFormVisibility"; import { useDiveConditionsFill } from "@/hooks/useDiveConditionsFill"; import { entryFixAfterSave } from "@/lib/dive-recordings"; import { DiveForm } from "@/components/dives/dive-form"; import { fetchedMarks } from "@/lib/dive-conditions"; import { DiveFormFieldsMenu } from "@/components/dives/dive-form-fields-menu"; import { DiveIcon } from "@/components/logo"; import { FORM_BODY, FORM_COLUMN, PAGE_COLUMN, PlainHero, } from "@/components/ui/map-hero"; import { PageSpinner } from "@/components/ui/page-spinner"; import { FormPageSkeleton } from "@/components/ui/page-skeleton"; import { NotFoundState } from "@/components/ui/not-found-state"; import { useToast } from "@/components/ui/use-toast"; import { getApiErrorMessage } from "@/lib/api/error"; export function EditDivePageContent() { const { user, isAuthenticated, isLoading: isAuthLoading } = useAuthGuard(); const router = useRouter(); const { toast } = useToast(); const [isSubmitting, setIsSubmitting] = useState(false); const form = useForm({ resolver: zodResolver(diveUpdateSchema), defaultValues: { ...emptyDiveFormValues(), start_time: "" }, }); const mixtureFieldArray = useMixtureFieldArray(form.control); // `fillsDefaults: false` - the edit form has no defaults to carry. What appears // when a field is shown is the stored value, and hiding it never changes form // state, which is react-hook-form's `shouldUnregister: false` doing all of the // work by itself. const visibility = useDiveFormVisibility({ form, replaceMixtures: mixtureFieldArray.replace, fillsDefaults: false, }); const { reveal, revealNonEmpty } = visibility; // Seeds the form from the loaded dive, and is one of the moments a value // arrives from outside the diver's typing: a dive that records notes shows // its notes even under a preset that hides them, because an edit form quietly // holding data the diver cannot see is the one thing this feature must not do. const resetFromDive = useCallback( (diveData: Dive) => { const values = diveToFormValues(diveData); form.reset(values); revealNonEmpty(values); // The import's section holds the dive's files, which can be struck off there. if ((diveData.recordings ?? []).length > 0) reveal(["file_import"]); }, [form, reveal, revealNonEmpty], ); const { id: diveId, resource: dive, isLoading: isLoadingDive, } = useResource(divesAPI.getDive, { enabled: !!user, errorMessage: "Failed to load dive details. Please try again.", redirectTo: "/dives", onLoaded: resetFromDive, }); const files = usePendingDiveFiles(dive?.recordings); // At the entry fix the save leaves, with the files struck off and picked here. const savedPosition = entryFixAfterSave({ recordings: dive?.recordings, removed: files.fileList.removedStored, pending: files.fileList.pending, stored: dive?.entry_latitude != null && dive.entry_longitude != null ? { latitude: dive.entry_latitude, longitude: dive.entry_longitude } : null, }); const conditions = useDiveConditionsFill({ form, visibility, enabled: !!dive, stored: dive ? fetchedMarks(dive) : undefined, fetchedOnOpen: dive?.conditions != null, position: savedPosition, }); useDocumentTitle( dive ? `Edit Dive #${dive.dive_number}` : undefined, "Dives", ); // Back/Cancel return to wherever the edit was started from - the dive list, a // trip, an explicit `?from=` - falling back to the dive itself. const returnTo = useReturnTo({ href: `/dives/${diveId}`, label: "Back to dive", }); const onSubmit = async (data: DiveUpdateInput) => { if (!user || !diveId) return; try { setIsSubmitting(true); const updateData = buildDiveUpdate(conditions.withhold(data)); await divesAPI.updateDive(diveId, updateData); await files.save(diveId, { saved: "Dive updated, but " }); toast({ title: "Success", description: "Dive updated successfully!", }); // The dive, with its back link going where the form's would have. router.push(withReturnTo(`/dives/${diveId}`, returnTo.href)); } catch (error) { console.error("Failed to update dive:", error); const errorMessage = getApiErrorMessage( error, "Failed to update dive. Please try again.", ); toast({ title: "Error", description: errorMessage, variant: "destructive", }); } finally { setIsSubmitting(false); } }; if (isAuthLoading) { return ; } if (!isAuthenticated) { return null; // Will redirect to signin } if (isLoadingDive) { return ( ); } if (!dive) { return (
); } return (
} />
); }