weddingsite/pages_old/rsvp/[id].js

493 lines
14 KiB
JavaScript

import Head from 'next/head';
import { useState } from 'react';
import styled, { keyframes } from 'styled-components';
import { unescape } from 'html-escaper';
import GuestRSVP from '../../components/GuestRSVP';
import Layout from '../../components/Layout';
import useForm from '../../lib/useForm';
import useUser from '../../lib/useUser';
import fetchJson from '../../lib/fetchJson';
import Group from '../../models/Group';
import Guest from '../../models/Guest';
import connectDb from '../../utils_old/db';
import { CalendarIcon, MapIcon } from '../../lib/svgs';
import Modal from '../../components/Modal';
import { handleUmamiEvent } from '../../utils_old/handleUmamiEvent';
const RSVPGroupStyles = styled.div`
h2 {
margin: 2rem 0;
}
`;
const loadingFrame = keyframes`
from {
background-position: 0 0;
}
to {
background-position: 100% 100%;
}
`;
const FormStyles = styled.form`
display: grid;
gap: 2rem;
margin-top: 3rem;
&::before,
&::after {
height: 10px;
content: '';
display: block;
border-radius: 4px;
background-image: linear-gradient(
to right,
var(--primary) 0%,
var(--lightViolet) 50%,
var(--primary) 100%
);
}
&[aria-busy='true']::before,
&[aria-busy='true']::after {
background-size: 50% auto;
animation: ${loadingFrame} 0.5s linear infinite;
}
label {
display: grid;
gap: 1rem;
}
input,
textarea,
select {
width: 100%;
padding: 1rem;
border: 1px solid var(--primary);
&:focus {
outline: 0;
border-color: var(--lightViolet);
}
}
button[type='submit'],
input[type='submit'] {
width: 100%;
font-size: 2rem;
font-weight: 600;
padding: 1.2rem 1.2rem;
}
hr {
display: block;
max-width: 100%;
height: 0;
max-height: 0;
border: solid;
width: 100%;
border-width: thin 0 0 0;
transition: inherit;
border-color: var(--lightShade);
color: var(--lightShade);
margin: 2.5rem 0;
}
button:disabled {
background: var(--lightGray);
}
fieldset {
border-radius: 4px;
&[disabled] {
opacity: 0.5;
}
}
`;
const AddressStyles = styled.div`
button {
background: none;
color: var(--primary);
padding: 0;
}
`;
const QuestionStyles = styled.div`
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 0.5rem;
@media (max-width: 600px) {
grid-template-columns: auto;
}
`;
const ErrorContactStyles = styled.p`
font-weight: bold;
a {
text-decoration: underline;
}
`;
const ModalContentStyles = styled.div`
display: flex;
flex-direction: column;
align-items: center;
font-size: 2rem;
text-align: center;
`;
export default function SingleGroupPage({ group }) {
const { guests, note, statusUpdatable } = group;
const { user } = useUser({ redirectTo: '/login' });
const [errorMsg, setErrorMsg] = useState('');
const [errorCount, setErrorCount] = useState(0);
const [message, setMessage] = useState('');
const [loading, setLoading] = useState(false);
const [groupHasPlusOne, setGroupHasPlusOne] = useState(false);
const [showModal, setShowModal] = useState(false);
const address = 'Central Park, New York, New York, USA';
const openModal = () => setShowModal(true);
const closeModal = () => setShowModal(false);
function getInitialFormData() {
const initial = {};
for (const guest of guests) {
const guestData = {
rsvpStatus: guest?.rsvpStatus || '',
dietaryNotes: guest?.dietaryNotes || '',
songRequests: guest?.songRequests || '',
hasPlusOne: guest?.hasPlusOne || false,
plusOne: guest?.plusOne || false,
plusOneFirstName: guest?.plusOneFirstName || '',
plusOneLastName: guest?.plusOneLastName || '',
};
initial[guest.id] = guestData;
if (guest.hasPlusOne) {
setGroupHasPlusOne(true);
}
}
initial.note = note || '';
return initial;
}
const { inputs, handleChange } = useForm(getInitialFormData);
if (!user || user.isLoggedIn === false) {
return <Layout>Loading...</Layout>;
}
if (group?.guests?.length === 0) return <p>Loading...</p>;
async function handleSubmit(groupId) {
const keys = Object.keys(inputs);
const guestData = [];
keys.forEach((key) => {
if (key !== 'note') {
guestData.push({
id: key,
...inputs[key],
});
}
});
const body = {
groupId,
note: inputs.note,
guests: guestData,
};
try {
const res = await fetchJson('/api/group', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
console.log('res.message', res.message);
if (res.message === 'SUCCESS') {
handleUmamiEvent('RSVP SUCCESS', 'success-rsvp');
setMessage(
`Successfully submitted your RSVP${
body.guests.length > 1 ? 's' : ''
}. Don't forget to save the date!!`
);
openModal();
} else {
handleUmamiEvent('RSVP FAILURE', 'failure-rsvp');
setErrorCount(errorCount + 1);
setErrorMsg('Unable to RSVP Your Group');
}
} catch (error) {
// console.error('An unexpected error happened:', error);
setErrorCount(errorCount + 1);
setErrorMsg(error.data.message);
}
}
return (
<div>
<Head>
<title key="title">N & N | RSVP</title>
</Head>
<RSVPGroupStyles>
<h2>Saying I Do</h2>
<div>
<a href="/myevents.ics" aria-label="Add to calendar">
<CalendarIcon />
</a>{' '}
Monday, June 3rd, 2030 at 5:00 PM
</div>
<AddressStyles>
<p>
<a
target="_blank"
href="https://share.here.com/p/s-YmI9LTczLjk4MTYxJTJDNDAuNzY0MjclMkMtNzMuOTQ4ODIlMkM0MC44MDA0OTtjPWFkbWluaXN0cmF0aXZlLXJlZ2lvbjtsYXQ9NDAuNzgyMzg7bG9uPS03My45NjUyMTtuPUNlbnRyYWwrUGFyazt6PTE0O2g9MWM3MDIz"
rel="noopener noreferrer nofollow"
aria-label="Go to map"
>
<MapIcon />
</a>{' '}
Wedding Event Location
</p>
<p>{address}</p>
</AddressStyles>
</RSVPGroupStyles>
<FormStyles
onSubmit={async (e) => {
e.preventDefault();
setLoading(true);
await handleSubmit(group.id);
setLoading(false);
}}
aria-busy={loading}
disabled={loading}
>
{errorMsg && <p className="error">Error: {errorMsg}</p>}
{errorMsg && errorCount > 3 && (
<ErrorContactStyles>
Support contact:{' '}
<a
href={`mailto:name@example.com?subject=RSVP Group Failed: ${errorMsg} ${group.id}`}
>
name@example.com
</a>
</ErrorContactStyles>
)}
<fieldset aria-busy={loading} disabled={loading || !statusUpdatable}>
<legend>RSVP Invitation</legend>
<p>Make sure to click the "Submit RSVP" button at the bottom!</p>
{statusUpdatable && (
<p>
Please accept or decline for each person in your party. You can
always update your response until the RSVP deadline.
</p>
)}
{!statusUpdatable && (
<p>
This section is no longer updatable. If you need to make changes
please contact us at{' '}
<a href="mailto:name@example.com">name@example.com</a>
</p>
)}
{group.guests.map((guest) => (
<GuestRSVP
key={guest.id}
guest={guest}
inputs={inputs}
handleChange={handleChange}
/>
))}
</fieldset>
<fieldset aria-busy={loading} disabled={loading}>
<legend>
Do you {groupHasPlusOne ? 'or your plus one ' : ''}have any dietary
restrictions?
</legend>
<div
style={{
display: 'grid',
gap: '1rem',
}}
>
{
// eslint-disable-next-line react/prop-types
group?.guests?.map((guest) =>
!guest.isPlusOne ? (
<QuestionStyles key={`${guest?.id}_dietaryNotes`}>
<p>
{guest.firstName} {guest.lastName} :
</p>
<label htmlFor={`${guest.id}_dietaryNotes`}>
<p className="sr-only">{`Enter dietery restrictions for ${guest.firstName} ${guest.lastName}`}</p>
<textarea
name={`${guest.id}_dietaryNotes`}
id={`${guest.id}_dietaryNotes`}
cols="30"
rows="2"
placeholder="Example: Nut allergy, Fish, etc."
value={inputs[guest.id]?.dietaryNotes}
onChange={handleChange}
/>
</label>
</QuestionStyles>
) : (
''
)
)
}
</div>
</fieldset>
<fieldset aria-busy={loading} disabled={loading}>
<legend>Do you have any song requests?</legend>
<div
style={{
display: 'grid',
gap: '1rem',
}}
>
{
// eslint-disable-next-line react/prop-types
group?.guests?.map((guest) =>
!guest?.isPlusOne ? (
<QuestionStyles key={`${guest?.id}_songRequests`}>
<p>
{guest.firstName} {guest.lastName}:
</p>
<label htmlFor={`${guest.id}_songRequests`}>
<p className="sr-only">{`Enter song requests for ${guest.firstName} ${guest.lastName}`}</p>
<textarea
name={`${guest.id}_songRequests`}
id={`${guest.id}_songRequests`}
cols="30"
rows="2"
placeholder="Example: Paint It Black - Rolling Stones"
value={inputs[guest.id]?.songRequests}
onChange={handleChange}
/>
</label>
</QuestionStyles>
) : (
''
)
)
}
</div>
</fieldset>
<fieldset aria-busy={loading} disabled={loading}>
<legend>Additional Notes?</legend>
<label htmlFor="note">
<p className="sr-only">
Enter additional notes your want to ask us?
</p>
<textarea
name="note"
id="note"
cols="30"
rows="10"
value={inputs.note}
onChange={handleChange}
placeholder="Anything you want to ask us?"
/>
</label>
</fieldset>
<hr />
{errorMsg && <p className="error">Error: {errorMsg}</p>}
{errorMsg && errorCount > 3 && (
<ErrorContactStyles>
Support contact:{' '}
<a
href={`mailto:name@example.com?subject=RSVP Group Failed: ${errorMsg}`}
>
name@example.com
</a>
</ErrorContactStyles>
)}
<button type="submit">Submit RSVP</button>
</FormStyles>
<Modal
isOpen={showModal}
onHide={closeModal}
contentLabel="RSVP Success"
headerCaption={<h2>RSVP Success</h2>}
>
<ModalContentStyles>
<p>{message}</p>
<div>
<p>Saturday, June 25, 2022 at 5:00 PM</p>
<a href="/myevents.ics" aria-label="Click to add to calendar">
<CalendarIcon /> Add to Calendar
</a>
</div>
</ModalContentStyles>
</Modal>
</div>
);
}
export async function getServerSideProps({ params }) {
try {
const group = {};
// TODO: REMOVE THIS WHEN TAKING YOUR SITE TO PRODUCTION
if (process.env.SITE_ENV === 'TEST_SITE') {
group.id = params.id;
group.guests = [
{
id: 'TEST-GUEST-ID-12345',
firstName: 'Test',
lastName: 'Lastname',
rsvpStatus: false,
dietaryNotes: '',
songRequests: '',
hasPlusOne: true,
plusOne: false,
plusOneFirstName: '',
plusOneLastName: '',
},
];
group.note = '';
group.statusUpdatable = true;
return { props: { group } };
}
await connectDb();
const groupData = await Group.findById(params.id);
group.id = params.id;
const guestList = [];
for (const guestId of groupData?.guests || []) {
const guestData = await Guest.findById(guestId);
const guest = {
id: guestData.id,
firstName: guestData.firstName,
lastName: guestData.lastName,
rsvpStatus: guestData.rsvpStatus || '',
dietaryNotes: guestData.dietaryNotes
? unescape(guestData.dietaryNotes)
: '',
songRequests: guestData.songRequests
? unescape(guestData.songRequests)
: '',
hasPlusOne: guestData.hasPlusOne || false,
plusOne: guestData.plusOne || false,
plusOneFirstName: guestData.plusOneFirstName || '',
plusOneLastName: guestData.plusOneLastName || '',
};
guestList.push(guest);
}
group.guests = guestList;
group.note = groupData.note ? unescape(groupData.note) : '';
return { props: { group } };
} catch (error) {
console.error(error);
return { notFound: true };
}
}