feat: add food interest tracking to registration flow
Add "I would like to include food for the week" checkbox to the payment
step with co-producing meals messaging. Track food interest in Google
Sheets as column Q ("Want Food").
- Add wantFood field to RegistrationData interface
- Add interactive food checkbox replacing static note in payment step
- Pass wantFood through register API to Google Sheets
- Expand sheet ranges from A:P to A:Q, add "Want Food" header
- Preserve food column on payment status updates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c64c4fb554
commit
b8567b0f54
|
|
@ -5,7 +5,7 @@ export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
|
|
||||||
const { name, email, contact, contributions, expectations, howHeard, dietary, crewConsent } = body
|
const { name, email, contact, contributions, expectations, howHeard, dietary, crewConsent, wantFood } = body
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (!name || !email || !contact || !contributions || !expectations || !crewConsent) {
|
if (!name || !email || !contact || !contributions || !expectations || !crewConsent) {
|
||||||
|
|
@ -28,6 +28,7 @@ export async function POST(request: NextRequest) {
|
||||||
howHeard: howHeard || "",
|
howHeard: howHeard || "",
|
||||||
dietary: Array.isArray(dietary) ? dietary.join(", ") : dietary || "",
|
dietary: Array.isArray(dietary) ? dietary.join(", ") : dietary || "",
|
||||||
crewConsent,
|
crewConsent,
|
||||||
|
wantFood: wantFood || false,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(`[Register API] Registration added for ${name} at row ${rowNumber}`)
|
console.log(`[Register API] Registration added for ${name} at row ${rowNumber}`)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ export default function RegisterPage() {
|
||||||
const [step, setStep] = useState<"form" | "payment">("form")
|
const [step, setStep] = useState<"form" | "payment">("form")
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
const [includeAccommodation, setIncludeAccommodation] = useState(true)
|
const [includeAccommodation, setIncludeAccommodation] = useState(true)
|
||||||
|
const [wantFood, setWantFood] = useState(false)
|
||||||
const [accommodationVenue, setAccommodationVenue] = useState<"commons-hub" | "herrnhof">("commons-hub")
|
const [accommodationVenue, setAccommodationVenue] = useState<"commons-hub" | "herrnhof">("commons-hub")
|
||||||
const [accommodationType, setAccommodationType] = useState("ch-multi")
|
const [accommodationType, setAccommodationType] = useState("ch-multi")
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
|
|
@ -85,6 +86,7 @@ export default function RegisterPage() {
|
||||||
howHeard: formData.howHeard,
|
howHeard: formData.howHeard,
|
||||||
dietary: dietaryString,
|
dietary: dietaryString,
|
||||||
crewConsent: formData.crewConsent,
|
crewConsent: formData.crewConsent,
|
||||||
|
wantFood,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -283,13 +285,26 @@ export default function RegisterPage() {
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Food note */}
|
{/* Food */}
|
||||||
<div className="py-4 border-b border-border">
|
<div className="py-4 border-b border-border">
|
||||||
<p className="text-sm text-muted-foreground">
|
<div className="flex items-start gap-3">
|
||||||
<span className="font-medium text-foreground">Food:</span>{" "}
|
<Checkbox
|
||||||
We'll follow up via email with food options and pricing closer to the event.
|
id="include-food"
|
||||||
Your dietary preferences from step 1 have been noted.
|
checked={wantFood}
|
||||||
</p>
|
onCheckedChange={(checked) => setWantFood(checked as boolean)}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Label htmlFor="include-food" className="font-medium cursor-pointer">
|
||||||
|
I would like to include food for the week
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
We are exploring co-producing our own meals as a community. More details and costs
|
||||||
|
will be shared soon — checking this box registers your interest so we can plan accordingly.
|
||||||
|
Your dietary preferences from step 1 have been noted.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Processing fee */}
|
{/* Processing fee */}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ export interface RegistrationData {
|
||||||
howHeard: string
|
howHeard: string
|
||||||
dietary: string
|
dietary: string
|
||||||
crewConsent: string
|
crewConsent: string
|
||||||
|
wantFood: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymentUpdateData {
|
export interface PaymentUpdateData {
|
||||||
|
|
@ -65,12 +66,13 @@ export async function addRegistration(data: RegistrationData): Promise<number> {
|
||||||
"", // N: Payment Date
|
"", // N: Payment Date
|
||||||
"", // O: Accommodation Venue
|
"", // O: Accommodation Venue
|
||||||
"", // P: Accommodation Type
|
"", // P: Accommodation Type
|
||||||
|
data.wantFood ? "Yes" : "No", // Q: Want Food
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
const response = await sheets.spreadsheets.values.append({
|
const response = await sheets.spreadsheets.values.append({
|
||||||
spreadsheetId: SPREADSHEET_ID,
|
spreadsheetId: SPREADSHEET_ID,
|
||||||
range: `${SHEET_NAME}!A:P`,
|
range: `${SHEET_NAME}!A:Q`,
|
||||||
valueInputOption: "USER_ENTERED",
|
valueInputOption: "USER_ENTERED",
|
||||||
insertDataOption: "INSERT_ROWS",
|
insertDataOption: "INSERT_ROWS",
|
||||||
requestBody: { values },
|
requestBody: { values },
|
||||||
|
|
@ -97,7 +99,7 @@ export async function updatePaymentStatus(data: PaymentUpdateData): Promise<bool
|
||||||
// First, get all rows to find the matching registration
|
// First, get all rows to find the matching registration
|
||||||
const response = await sheets.spreadsheets.values.get({
|
const response = await sheets.spreadsheets.values.get({
|
||||||
spreadsheetId: SPREADSHEET_ID,
|
spreadsheetId: SPREADSHEET_ID,
|
||||||
range: `${SHEET_NAME}!A:P`,
|
range: `${SHEET_NAME}!A:Q`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const rows = response.data.values || []
|
const rows = response.data.values || []
|
||||||
|
|
@ -124,7 +126,7 @@ export async function updatePaymentStatus(data: PaymentUpdateData): Promise<bool
|
||||||
|
|
||||||
// Update the row with payment information
|
// Update the row with payment information
|
||||||
const existingRow = rows[targetRowIndex - 1]
|
const existingRow = rows[targetRowIndex - 1]
|
||||||
const updateRange = `${SHEET_NAME}!C${targetRowIndex}:P${targetRowIndex}`
|
const updateRange = `${SHEET_NAME}!C${targetRowIndex}:Q${targetRowIndex}`
|
||||||
const updateValues = [
|
const updateValues = [
|
||||||
[
|
[
|
||||||
data.email || existingRow[2] || "", // C: Email (from Mollie or existing)
|
data.email || existingRow[2] || "", // C: Email (from Mollie or existing)
|
||||||
|
|
@ -141,6 +143,7 @@ export async function updatePaymentStatus(data: PaymentUpdateData): Promise<bool
|
||||||
data.paymentDate || new Date().toISOString(), // N: Payment Date
|
data.paymentDate || new Date().toISOString(), // N: Payment Date
|
||||||
data.accommodationVenue || "", // O: Accommodation Venue
|
data.accommodationVenue || "", // O: Accommodation Venue
|
||||||
data.accommodationType || "", // P: Accommodation Type
|
data.accommodationType || "", // P: Accommodation Type
|
||||||
|
existingRow[16] || "", // Q: Want Food (preserve)
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -169,7 +172,7 @@ export async function initializeSheetHeaders(): Promise<void> {
|
||||||
// Check if first row has data
|
// Check if first row has data
|
||||||
const response = await sheets.spreadsheets.values.get({
|
const response = await sheets.spreadsheets.values.get({
|
||||||
spreadsheetId: SPREADSHEET_ID,
|
spreadsheetId: SPREADSHEET_ID,
|
||||||
range: `${SHEET_NAME}!A1:P1`,
|
range: `${SHEET_NAME}!A1:Q1`,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.data.values || response.data.values.length === 0) {
|
if (!response.data.values || response.data.values.length === 0) {
|
||||||
|
|
@ -192,12 +195,13 @@ export async function initializeSheetHeaders(): Promise<void> {
|
||||||
"Payment Date",
|
"Payment Date",
|
||||||
"Accommodation Venue",
|
"Accommodation Venue",
|
||||||
"Accommodation Type",
|
"Accommodation Type",
|
||||||
|
"Want Food",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
await sheets.spreadsheets.values.update({
|
await sheets.spreadsheets.values.update({
|
||||||
spreadsheetId: SPREADSHEET_ID,
|
spreadsheetId: SPREADSHEET_ID,
|
||||||
range: `${SHEET_NAME}!A1:P1`,
|
range: `${SHEET_NAME}!A1:Q1`,
|
||||||
valueInputOption: "USER_ENTERED",
|
valueInputOption: "USER_ENTERED",
|
||||||
requestBody: { values: headers },
|
requestBody: { values: headers },
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue