summaryrefslogtreecommitdiff
path: root/client/src/components/RsvpForm.tsx
diff options
context:
space:
mode:
authorMichael Hunteman <michael@huntm.net>2024-05-17 15:20:30 -0700
committerMichael Hunteman <michael@huntm.net>2024-05-17 15:20:30 -0700
commit7103019890960e793deefb64987a09b33be60b42 (patch)
treec1c9402aa250c68b2cbe13d62598232bbf20b1e2 /client/src/components/RsvpForm.tsx
parentfc5c111bcfe296bec82e1cf9fdb88fc80fb24f89 (diff)
Add golang server
Diffstat (limited to 'client/src/components/RsvpForm.tsx')
-rw-r--r--client/src/components/RsvpForm.tsx242
1 files changed, 242 insertions, 0 deletions
diff --git a/client/src/components/RsvpForm.tsx b/client/src/components/RsvpForm.tsx
new file mode 100644
index 0000000..71db0d8
--- /dev/null
+++ b/client/src/components/RsvpForm.tsx
@@ -0,0 +1,242 @@
+import React from 'react';
+import { useRef } from 'react';
+import { useOutletContext } from 'react-router-dom';
+import {
+ Button,
+ Container,
+ FormControl,
+ FormControlLabel,
+ FormLabel,
+ Grid,
+ Radio,
+ RadioGroup,
+ TextField,
+} from '@mui/material';
+import { useForm, Controller, useFieldArray } from 'react-hook-form';
+import { useUpdateGuestMutation } from '../apiSlice';
+import type { Guest } from '../apiSlice';
+
+type FormValues = {
+ id: number;
+ firstName: string;
+ lastName: string;
+ attendance: string;
+ email: string;
+ partySize: number;
+ message: string;
+ partyList: {
+ firstName: string;
+ lastName: string;
+ }[];
+};
+
+function RsvpForm() {
+ const [updateGuest] = useUpdateGuestMutation();
+ const guest: Guest = useOutletContext();
+ const previousPartySize = useRef(0);
+
+ const {
+ register,
+ handleSubmit,
+ control,
+ watch,
+ formState: { errors },
+ } = useForm<FormValues>({
+ defaultValues: {
+ id: guest?.id,
+ firstName: guest?.firstName,
+ lastName: guest?.lastName,
+ attendance: '',
+ email: '',
+ message: '',
+ partySize: 1,
+ partyList: [],
+ },
+ });
+
+ const onSubmit = async (data: Guest) => {
+ updateGuest({ ...data });
+ };
+
+ const { fields, append, remove } = useFieldArray({
+ control,
+ name: 'partyList',
+ });
+
+ const handleParty = () => {
+ const partySize = Number(watch('partySize')) - 1;
+ if (
+ partySize > previousPartySize.current &&
+ partySize > 0 &&
+ partySize < 10
+ ) {
+ append(
+ new Array(partySize - previousPartySize.current).fill({
+ firstName: '',
+ lastName: '',
+ })
+ );
+ previousPartySize.current = partySize;
+ } else if (partySize < previousPartySize.current && partySize >= 0) {
+ remove(
+ [...Array(previousPartySize.current - partySize).keys()].map(
+ (_, i) => partySize - 1 + i
+ )
+ );
+ previousPartySize.current = partySize;
+ }
+ };
+
+ return (
+ <Container
+ component="form"
+ maxWidth="sm"
+ noValidate
+ onSubmit={handleSubmit(onSubmit)}
+ >
+ <Grid container spacing={2} sx={{ mt: 8 }}>
+ <Grid item xs={12}>
+ <p>
+ Please RSVP for the wedding by March 10, 2025. The ceremony will
+ commence at 3 PM on April 26 in Divine Shepherd. The reception will
+ follow at 5 PM in A Venue on the Ridge.
+ </p>
+ </Grid>
+ <Grid item xs={12}>
+ <div style={{ display: 'flex', justifyContent: 'center' }}>
+ <FormControl>
+ <div style={{ display: 'flex', alignItems: 'center' }}>
+ <FormLabel sx={{ mr: 2 }} error={!!errors.attendance} required>
+ Will you attend?
+ </FormLabel>
+ <Controller
+ name="attendance"
+ control={control}
+ rules={{ required: true }}
+ render={({ field }) => (
+ <RadioGroup {...field} row>
+ <FormControlLabel
+ value="yes"
+ control={<Radio />}
+ label="Yes"
+ />
+ <FormControlLabel
+ value="no"
+ control={<Radio />}
+ label="No"
+ />
+ </RadioGroup>
+ )}
+ />
+ </div>
+ </FormControl>
+ </div>
+ </Grid>
+ <Grid item xs={12} md={6} lg={6}>
+ <TextField
+ label="Email"
+ type="email"
+ variant="outlined"
+ fullWidth
+ error={!!errors.email}
+ helperText={errors.email?.message}
+ required
+ {...register('email', {
+ required: 'This field is required',
+ pattern: {
+ value: /\S+@\S+\.\S+/,
+ message: 'Please enter a valid email address',
+ },
+ })}
+ />
+ </Grid>
+ <Grid item xs={12} md={6} lg={6}>
+ <TextField
+ label="Party Size"
+ type="number"
+ variant="outlined"
+ fullWidth
+ onWheel={(event) => {
+ event.currentTarget.blur();
+ }}
+ error={!!errors.partySize}
+ helperText={errors.partySize?.message}
+ required
+ {...register('partySize', {
+ onChange: handleParty,
+ required: 'This field is required',
+ min: { value: 1, message: 'Please enter a positive integer' },
+ max: {
+ value: 9,
+ message: 'Please enter an integer less than 10',
+ },
+ })}
+ />
+ </Grid>
+ <Grid item xs={12}>
+ <TextField
+ label="Message to the couple"
+ variant="outlined"
+ fullWidth
+ multiline
+ rows={3}
+ {...register('message')}
+ />
+ </Grid>
+ {fields.map((field, index) => {
+ return (
+ <Grid
+ container
+ item
+ columnSpacing={2}
+ rowSpacing={{ xs: 1 }}
+ key={field.id}
+ >
+ <Grid item xs={12} md={6} lg={6}>
+ <TextField
+ label="First Name"
+ variant="outlined"
+ fullWidth
+ error={!!errors.partyList?.[index]?.firstName}
+ helperText={errors.partyList?.[index]?.firstName?.message}
+ required
+ {...register(`partyList.${index}.firstName`, {
+ required: 'This field is required',
+ })}
+ />
+ </Grid>
+ <Grid item xs={12} md={6} lg={6}>
+ <TextField
+ label="Last Name"
+ variant="outlined"
+ fullWidth
+ error={!!errors.partyList?.[index]?.lastName}
+ helperText={errors.partyList?.[index]?.lastName?.message}
+ required
+ {...register(`partyList.${index}.lastName`, {
+ required: 'This field is required',
+ })}
+ />
+ </Grid>
+ </Grid>
+ );
+ })}
+ <Grid item xs={12}>
+ <div
+ style={{
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ }}
+ >
+ <Button type="submit" variant="contained">
+ RSVP
+ </Button>
+ </div>
+ </Grid>
+ </Grid>
+ </Container>
+ );
+}
+
+export default RsvpForm;