Skip to content

Commit

Permalink
Edit responses (#36)
Browse files Browse the repository at this point in the history
This PR allows the user who RSVP'd to an event to edit their own RSVP
without needing to contact support.
  • Loading branch information
mplewis authored Nov 11, 2024
1 parent 9f7b6a1 commit c335656
Show file tree
Hide file tree
Showing 16 changed files with 363 additions and 147 deletions.
14 changes: 12 additions & 2 deletions NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
- [ ] Add * asterisk markers to mandatory fields
- [ ] Look into new OG image features
- [ ] Replace MetaTags with Metadata
- [ ] Edit RSVP from confirmation link
- [ ] Notify organizer on cancel?
- [ ] "Upgrade" from no RSVP to RSVP
- [ ] Allow host to delete RSVPs
Expand All @@ -30,9 +29,9 @@
- [ ] Use background functions for saving event preview image
- [ ] Upgrade to Bulma 1.0 (or away)
- [ ] Fix hamburger menu not working on mobile
- [ ] Why are hamburger items missing on Confirm RSVP page?
- [ ] URL builder helpers
- [ ] Write README
- [ ] Unify `typ` styles for `pageTitle` and `head`
- [ ] Form to request resend link(s) for email address
- [ ] Hold RSVP locally with cookie
- [ ] Resend confirmation if an RSVP enters an existing email
Expand All @@ -42,6 +41,17 @@
- [ ] Use `navigate` instead of `a href`
- [ ] Diff dates in `notify` so that they are omitted when unchanged
- [ ] Investigate why some emailed URLs use incorrect hosts for Netlify Deploy Preview
- [ ] Stably sort RSVP list
- [ ] Allow sorting of RSVP list with UI
- [ ] Placeholder values for form fields
- [ ] Closely investigate form button spacing (update + cancel, etc.)
- [ ] Add calendar invite links to RSVP page
- [ ] Notify event owner when RSVP details change
- [ ] Allow event owner to customize their level of email participation
- [ ] Automate flow tests
- [ ] Spinners on loading pages with fun text
- [x] Unify `typ` styles for `pageTitle` and `head`
- [x] Edit RSVP from confirmation link
- [x] More prominent RSVP button
- [x] Delete RSVP
- [x] Discord webhook notifications
Expand Down
17 changes: 16 additions & 1 deletion api/src/graphql/responses.sdl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ export const schema = gql`
comment: String!
}
type UpdatableResponse {
id: Int!
event: Event!
eventId: Int!
createdAt: DateTime!
updatedAt: DateTime!
name: String!
editToken: String!
email: String!
confirmed: Boolean!
headCount: Int!
comment: String!
remindPriorSec: Int
}
type ResponseSummary {
headCountTotal: Int!
responseCountTotal: Int!
Expand All @@ -28,7 +43,7 @@ export const schema = gql`
type Query {
responses: [Response!]! @requireAuth
response(id: Int!): Response @requireAuth
responseByEditToken(editToken: String!): Response @skipAuth
responseByEditToken(editToken: String!): UpdatableResponse @skipAuth
}
input CreateResponseInput {
Expand Down
3 changes: 1 addition & 2 deletions api/src/lib/email/template/reminder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('with test handler', () => {
"handler": "nodemailer",
"handlerOptions": undefined,
"headers": {},
"htmlContent": "Hello from Freevite! You asked for a reminder about this event when you RSVPed:<br><br>Emma&#39;s Holiday Party<br>Starts at: Sun Dec 25, 2022, 7:00 AM EST (in a day))<br>Ends at: Sun Dec 25, 2022, 10:00 AM EST (3 hours long)<br><br>View the event here:<br>https://example.com/events/emmas-holiday-party<br><br>If you need to modify or delete your RSVP, just reply to this email.<br>Thanks for using Freevite!",
"htmlContent": "Hello from Freevite! You asked for a reminder about this event when you RSVPed:<br><br>Emma&#39;s Holiday Party<br>Starts at: Sun Dec 25, 2022, 7:00 AM EST (in a day))<br>Ends at: Sun Dec 25, 2022, 10:00 AM EST (3 hours long)<br><br>View the event here:<br>https://example.com/events/emmas-holiday-party<br><br>Thanks for using Freevite!",
"renderer": "plain",
"rendererOptions": {},
"replyTo": undefined,
Expand All @@ -51,7 +51,6 @@ Ends at: Sun Dec 25, 2022, 10:00 AM EST (3 hours long)
View the event here:
https://example.com/events/emmas-holiday-party
If you need to modify or delete your RSVP, just reply to this email.
Thanks for using Freevite!",
"to": [
"[email protected]",
Expand Down
1 change: 0 additions & 1 deletion api/src/lib/email/template/reminder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export async function sendReminder({
View the event here:
https://${SITE_HOST}/events/${event.slug}
If you need to modify or delete your RSVP, just reply to this email.
Thanks for using Freevite!
`,
})
Expand Down
21 changes: 10 additions & 11 deletions api/src/lib/email/template/response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,20 @@ describe('with test handler', () => {
"handler": "nodemailer",
"handlerOptions": undefined,
"headers": {},
"htmlContent": "Hello from Freevite! Click this link to confirm your attendance of Emma&#39;s Holiday Party:<br><br>https://example.com/rsvp?token=SOME-EDIT-TOKEN<br><br>You must click the above link to confirm your RSVP.<br>Once you do, we&#39;ll let the organizer know you are attending.<br><br>If you did not RSVP to this event, you can ignore this email.<br><br>To modify or delete your RSVP, just reply to this email. Thanks for using Freevite!",
"htmlContent": "Hello from Freevite!<br><br>Click here to confirm you&#39;re attending Emma&#39;s Holiday Party:<br>https://example.com/rsvp?token=SOME-EDIT-TOKEN<br>Once you do, we&#39;ll confirm your RSVP and notify the event organizer.<br><br>To change your response or delete your RSVP, click the link above.<br><br>If you did not RSVP to this event, you can ignore this email.",
"renderer": "plain",
"rendererOptions": {},
"replyTo": undefined,
"subject": "Confirm your RSVP to Emma's Holiday Party",
"textContent": "Hello from Freevite! Click this link to confirm your attendance of Emma's Holiday Party:
"textContent": "Hello from Freevite!
Click here to confirm you're attending Emma's Holiday Party:
https://example.com/rsvp?token=SOME-EDIT-TOKEN
Once you do, we'll confirm your RSVP and notify the event organizer.
You must click the above link to confirm your RSVP.
Once you do, we'll let the organizer know you are attending.
To change your response or delete your RSVP, click the link above.
If you did not RSVP to this event, you can ignore this email.
To modify or delete your RSVP, just reply to this email. Thanks for using Freevite!",
If you did not RSVP to this event, you can ignore this email.",
"to": [
"[email protected]",
],
Expand Down Expand Up @@ -84,7 +83,7 @@ To modify or delete your RSVP, just reply to this email. Thanks for using Freevi
"handler": "nodemailer",
"handlerOptions": undefined,
"headers": {},
"htmlContent": "Hello from Freevite! Sherlock Holmes has confirmed they are attending Emma&#39;s Holiday Party:<br><br>Name: Sherlock Holmes<br>Guests: 2<br>Comment: Looking forward to it!<br><br>To view all responses and manage your event, click here:<br>https://example.com/edit?token=SOME-EDIT-TOKEN<br><br>If you need any help, just reply to this email. Thanks for using Freevite!",
"htmlContent": "Hello from Freevite! Sherlock Holmes has confirmed they are attending Emma&#39;s Holiday Party:<br><br>Name: Sherlock Holmes<br>Guests: 2<br>Comment: Looking forward to it!<br><br>To view all responses and manage your event, click here:<br>https://example.com/edit?token=SOME-EDIT-TOKEN<br><br>Thanks for using Freevite!",
"renderer": "plain",
"rendererOptions": {},
"replyTo": undefined,
Expand All @@ -98,7 +97,7 @@ Comment: Looking forward to it!
To view all responses and manage your event, click here:
https://example.com/edit?token=SOME-EDIT-TOKEN
If you need any help, just reply to this email. Thanks for using Freevite!",
Thanks for using Freevite!",
"to": [
"[email protected]",
],
Expand Down Expand Up @@ -131,7 +130,7 @@ If you need any help, just reply to this email. Thanks for using Freevite!",
"handler": "nodemailer",
"handlerOptions": undefined,
"headers": {},
"htmlContent": "Hello from Freevite! Sherlock Holmes has canceled their RSVP to Emma&#39;s Holiday Party.<br><br>Here are the details of the RSVP before it was canceled:<br>Name: Sherlock Holmes<br>Guests: 2<br>Comment: Looking forward to it!<br><br>To view all RSVPs and manage your event, click here:<br>https://example.com/edit?token=SOME-EDIT-TOKEN<br><br>If you need any help, just reply to this email. Thanks for using Freevite!",
"htmlContent": "Hello from Freevite! Sherlock Holmes has canceled their RSVP to Emma&#39;s Holiday Party.<br><br>Here are the details of the RSVP before it was canceled:<br>Name: Sherlock Holmes<br>Guests: 2<br>Comment: Looking forward to it!<br><br>To view all RSVPs and manage your event, click here:<br>https://example.com/edit?token=SOME-EDIT-TOKEN<br><br>Thanks for using Freevite!",
"renderer": "plain",
"rendererOptions": {},
"replyTo": undefined,
Expand All @@ -146,7 +145,7 @@ Comment: Looking forward to it!
To view all RSVPs and manage your event, click here:
https://example.com/edit?token=SOME-EDIT-TOKEN
If you need any help, just reply to this email. Thanks for using Freevite!",
Thanks for using Freevite!",
"to": [
"[email protected]",
],
Expand Down
13 changes: 6 additions & 7 deletions api/src/lib/email/template/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ export async function sendResponseConfirmation({
to: response.email,
subject: `Confirm your RSVP to ${event.title}`,
text: stripIndent`
Hello from Freevite! Click this link to confirm your attendance of ${event.title}:
Hello from Freevite!
Click here to confirm you're attending ${event.title}:
${SITE_URL}/rsvp?token=${response.editToken}
Once you do, we'll confirm your RSVP and notify the event organizer.
You must click the above link to confirm your RSVP.
Once you do, we'll let the organizer know you are attending.
To change your response or delete your RSVP, click the link above.
If you did not RSVP to this event, you can ignore this email.
To modify or delete your RSVP, just reply to this email. Thanks for using Freevite!
`,
})
}
Expand Down Expand Up @@ -64,7 +63,7 @@ export async function sendNewResponseReceived({
To view all responses and manage your event, click here:
${SITE_URL}/edit?token=${event.editToken}
If you need any help, just reply to this email. Thanks for using Freevite!
Thanks for using Freevite!
`,
})
}
Expand Down Expand Up @@ -96,7 +95,7 @@ export async function sendResponseDeleted({
To view all RSVPs and manage your event, click here:
${SITE_URL}/edit?token=${event.editToken}
If you need any help, just reply to this email. Thanks for using Freevite!
Thanks for using Freevite!
`,
})
}
9 changes: 9 additions & 0 deletions api/src/lib/reminder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import dayjs from './dayjs'

export const reminderDurations = {
'1 day': dayjs.duration(1, 'day').asSeconds(),
'1 hour': dayjs.duration(1, 'hour').asSeconds(),
never: null,
} as const

export type ReminderDuration = keyof typeof reminderDurations
24 changes: 24 additions & 0 deletions api/src/services/responses/responses.scenarios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,30 @@ export const standard = defineScenario<Prisma.ResponseCreateArgs>({
},
},
},
withReminder: {
data: {
editToken: 'editTokenWithReminder',
email: 'String',
event: {
create: {
editToken: 'eventEditTokenWithReminder',
previewToken: 'eventPreviewTokenWithReminder',
ownerEmail: 'String',
slug: 'myEventSlugWithReminder',
title: 'myEventTitleWithReminder',
description: 'String',
start: '2024-01-07T12:00:00Z',
end: '2024-01-07T18:00:00Z',
responseConfig: 'SHOW_ALL',
},
},
reminders: {
create: {
sendAt: '2024-01-07T11:00:00Z',
},
},
},
},
},
})

Expand Down
63 changes: 63 additions & 0 deletions api/src/services/responses/responses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
createResponse,
updateResponse,
deleteResponse,
pickRemindPriorSec,
responseByEditToken,
} from './responses'
import type { StandardScenario } from './responses.scenarios'

Expand Down Expand Up @@ -119,4 +121,65 @@ describe('responses', () => {

expect(result.reminders.length).toEqual(0)
})

// TODO: Test that fetching the response with the edit token marks the response as confirmed
scenario(
'returns the expected response when a reminder exists',
async (scenario: StandardScenario) => {
const result = await responseByEditToken({
editToken: scenario.response.withReminder.editToken,
})

expect(result.remindPriorSec).toEqual(3_600)
}
)
})

describe('addRemindPriorSec', () => {
const eventStart = new Date('2024-01-07T12:00:00Z')

describe('with no reminders', () => {
const reminders = []
it('builds the expected data', () => {
const result = pickRemindPriorSec({ reminders, eventStart })
expect(result).toEqual(null)
})
})

describe('with one reminder', () => {
const variants = [
{ sendAt: '2024-01-07T11:00:00Z', expected: 3_600 },
{ sendAt: '2024-01-07T10:45:00Z', expected: 3_600 },
{ sendAt: '2024-01-07T11:15:00Z', expected: 3_600 },
{ sendAt: '2024-01-06T12:00:00Z', expected: 86_400 },
{ sendAt: '2024-01-06T11:59:00Z', expected: 86_400 },
]

variants.forEach(({ sendAt, expected }) => {
it(`builds the expected data for ${sendAt}`, () => {
const reminders = [{ sendAt: new Date(sendAt) }]
const result = pickRemindPriorSec({ reminders, eventStart })
expect(result).toEqual(expected)
})
})
})

describe('with more than one reminder', () => {
it('determinstically selects the earliest reminder', () => {
const reminderSets = [
[
{ sendAt: new Date('2024-01-06T12:00:00Z') },
{ sendAt: new Date('2024-01-07T11:00:00Z') },
],
[
{ sendAt: new Date('2024-01-07T11:00:00Z') },
{ sendAt: new Date('2024-01-06T12:00:00Z') },
],
]
for (const reminders of reminderSets) {
const result = pickRemindPriorSec({ reminders, eventStart })
expect(result).toEqual(86_400)
}
})
})
})
Loading

0 comments on commit c335656

Please sign in to comment.