plans
2026-05-04
2026 05 04 Forms to Convex Direct

Forms to Convex Direct Mutation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Remove useFormSession hook and localStorage from forms. Forms submit directly to Convex via a single mutation call with all form data.

Architecture: Create submitFormData mutation that takes formType + all form fields as JSON and inserts directly to formSessions table. Forms call this one mutation on submit. No session ID, no localStorage, no debounced updates.

Tech Stack: Convex mutation, React Hook Form, Zod


Forms Affected

FormFileStatus
WorkshopProposalFormcomponents/ui/workshop-proposal-form.tsxNeeds update
VenueRentalFormcomponents/ui/venue-rental-form.tsxNeeds update
PrivateEventsFormcomponents/ui/private-events-form.tsxNeeds update
ArtistProposalFormcomponents/ui/artist-proposal-form.tsxNeeds update
HostAnEventFormcomponents/ui/host-an-event-form.tsxNeeds update
ContactFormcomponents/ui/contact-form.tsxAlready uses server action — no change

Files to modify:

  • apps/backend/convex/functions/form_sessions.ts — Add submitFormData mutation
  • apps/frontend/components/ui/workshop-proposal-form.tsx
  • apps/frontend/components/ui/venue-rental-form.tsx
  • apps/frontend/components/ui/private-events-form.tsx
  • apps/frontend/components/ui/artist-proposal-form.tsx
  • apps/frontend/components/ui/host-an-event-form.tsx

Files to delete:

  • apps/frontend/lib/hooks/use-form-session.ts

Task 1: Add submitFormData Mutation

File:

  • Modify: apps/backend/convex/functions/form_sessions.ts

  • Step 1: Add submitFormData mutation

Add this mutation to form_sessions.ts:

// Submit form data directly (no localStorage, no session ID)
export const submitFormData = mutation({
  args: {
    formType: v.union(
      v.literal("CONTACT"),
      v.literal("VENUE_RENTAL"),
      v.literal("PRIVATE_EVENTS"),
      v.literal("WORKSHOPS"),
      v.literal("ARTIST_PROPOSAL"),
      v.literal("HOST_AN_EVENT"),
    ),
    data: v.string(), // JSON string of all form fields
  },
  handler: async (ctx, { formType, data }) => {
    const now = Date.now();
    // Generate UUID server-side — no client localStorage needed
    const sessionId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
 
    await ctx.db.insert("formSessions", {
      sessionId,
      formType,
      data,
      submitted: true,
      expiresAt: now + FORM_SESSION_TTL,
      createdAt: now,
      updatedAt: now,
    });
 
    return { success: true };
  },
});
  • Step 2: Commit
git add apps/backend/convex/functions/form_sessions.ts
git commit -m "feat(forms): add submitFormData mutation for direct Convex submission"

Task 2: Update WorkshopProposalForm

File:

  • Modify: apps/frontend/components/ui/workshop-proposal-form.tsx

  • Step 1: Remove useFormSession, add Convex mutation import

Replace:

import { useFormSession } from "@/lib/hooks/use-form-session";

with:

import { useMutation } from "convex/react";
import { api } from "~/convex/_generated/api";
  • Step 2: Remove updateField and submit from useFormSession

Replace:

const { updateField, submit } = useFormSession("WORKSHOPS");

with nothing (useMutation goes in component).

  • Step 3: Add useMutation for submitFormData inside component

In the component function, add:

const submitForm = useMutation(api.formSessions.submitFormData);
  • Step 4: Remove the useEffect that calls updateField

Remove the entire:

useEffect(() => {
  const subscription = form.watch((values) => {
    Object.entries(values).forEach(([key, value]) => {
      if (value !== undefined) {
        updateField(key, value);
      }
    });
  });
  return () => subscription.unsubscribe();
}, [form, updateField]);
  • Step 5: Update onSubmit to call mutation directly

Replace:

const onSubmit = () => {
  submit();
  setIsSubmitted(true);
};

with:

const onSubmit = async (data: WorkshopProposalFormData) => {
  await submitForm({
    formType: "WORKSHOPS",
    data: JSON.stringify(data),
  });
  setIsSubmitted(true);
};

And change form.handleSubmit(onSubmit) to pass the data:

<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
  • Step 6: Commit
git add apps/frontend/components/ui/workshop-proposal-form.tsx
git commit -m "refactor(forms): migrate workshop-proposal to direct Convex mutation"

Task 3: Update VenueRentalForm

File:

  • Modify: apps/frontend/components/ui/venue-rental-form.tsx

Same pattern as Task 2:

  1. Remove useFormSession import
  2. Add useMutation and api imports
  3. Add const submitForm = useMutation(api.formSessions.submitFormData); in component
  4. Remove useEffect with form.watch
  5. Remove { updateField, submit } from useFormSession
  6. Update onSubmit to call submitForm with { formType: "VENUE_RENTAL", data: JSON.stringify(data) }
  • Commit
git add apps/frontend/components/ui/venue-rental-form.tsx
git commit -m "refactor(forms): migrate venue-rental to direct Convex mutation"

Task 4: Update PrivateEventsForm

File:

  • Modify: apps/frontend/components/ui/private-events-form.tsx

Same pattern:

  1. Remove useFormSession import
  2. Add useMutation and api imports
  3. Add const submitForm = useMutation(api.formSessions.submitFormData); in component
  4. Remove useEffect with form.watch
  5. Remove { updateField, submit } from useFormSession
  6. Update onSubmit to call submitForm with { formType: "PRIVATE_EVENTS", data: JSON.stringify(data) }
  • Commit
git add apps/frontend/components/ui/private-events-form.tsx
git commit -m "refactor(forms): migrate private-events to direct Convex mutation"

Task 5: Update ArtistProposalForm

File:

  • Modify: apps/frontend/components/ui/artist-proposal-form.tsx

Same pattern:

  1. Remove useFormSession import
  2. Add useMutation and api imports
  3. Add const submitForm = useMutation(api.formSessions.submitFormData); in component
  4. Remove useEffect with form.watch
  5. Remove { updateField, submit } from useFormSession
  6. Update onSubmit to call submitForm with { formType: "ARTIST_PROPOSAL", data: JSON.stringify(data) }
  • Commit
git add apps/frontend/components/ui/artist-proposal-form.tsx
git commit -m "refactor(forms): migrate artist-proposal to direct Convex mutation"

Task 6: Update HostAnEventForm

File:

  • Modify: apps/frontend/components/ui/host-an-event-form.tsx

Same pattern:

  1. Remove useFormSession import
  2. Add useMutation and api imports
  3. Add const submitForm = useMutation(api.formSessions.submitFormData); in component
  4. Remove useEffect with form.watch
  5. Remove { updateField, submit } from useFormSession
  6. Update onSubmit to call submitForm with { formType: "HOST_AN_EVENT", data: JSON.stringify(data) }
  • Commit
git add apps/frontend/components/ui/host-an-event-form.tsx
git commit -m "refactor(forms): migrate host-an-event to direct Convex mutation"

Task 7: Delete useFormSession Hook

File:

  • Delete: apps/frontend/lib/hooks/use-form-session.ts

  • Delete the file

git rm apps/frontend/lib/hooks/use-form-session.ts
git commit -m "chore: remove useFormSession hook (no longer needed)"

Task 8: Verify No Remaining useFormSession Usage

  • Step 1: Verify no usage remains
grep -r "useFormSession" apps/frontend --include="*.tsx" --include="*.ts"

Expected: No matches (except raw/ directory)

  • Commit
git commit -m "chore: verify no useFormSession usage remains"

Self-Review Checklist

  1. Spec coverage: All 5 forms migrated, hook deleted
  2. No placeholders: All code is complete
  3. Type consistency: formType string values match schema exactly
  4. Contact form: Already uses server action — no change needed