mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-25 20:27:44 +02:00
✨ Store participant timezone in response (#1811)
This commit is contained in:
parent
968e513dba
commit
965e969fd5
5 changed files with 101 additions and 87 deletions
|
@ -13,6 +13,7 @@ import z from "zod";
|
||||||
import { usePoll } from "@/contexts/poll";
|
import { usePoll } from "@/contexts/poll";
|
||||||
import { useTranslation } from "@/i18n/client";
|
import { useTranslation } from "@/i18n/client";
|
||||||
|
|
||||||
|
import { useTimezone } from "@/features/timezone";
|
||||||
import { useAddParticipantMutation } from "./poll/mutations";
|
import { useAddParticipantMutation } from "./poll/mutations";
|
||||||
import VoteIcon from "./poll/vote-icon";
|
import VoteIcon from "./poll/vote-icon";
|
||||||
import { useUser } from "./user-provider";
|
import { useUser } from "./user-provider";
|
||||||
|
@ -89,7 +90,7 @@ export const NewParticipantForm = (props: NewParticipantModalProps) => {
|
||||||
const poll = usePoll();
|
const poll = usePoll();
|
||||||
|
|
||||||
const isEmailRequired = poll.requireParticipantEmail;
|
const isEmailRequired = poll.requireParticipantEmail;
|
||||||
|
const { timezone } = useTimezone();
|
||||||
const { user, createGuestIfNeeded } = useUser();
|
const { user, createGuestIfNeeded } = useUser();
|
||||||
const isLoggedIn = !user.isGuest;
|
const isLoggedIn = !user.isGuest;
|
||||||
const { register, setError, formState, handleSubmit } =
|
const { register, setError, formState, handleSubmit } =
|
||||||
|
@ -117,6 +118,7 @@ export const NewParticipantForm = (props: NewParticipantModalProps) => {
|
||||||
votes: props.votes,
|
votes: props.votes,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
pollId: poll.id,
|
pollId: poll.id,
|
||||||
|
timeZone: timezone,
|
||||||
});
|
});
|
||||||
props.onSubmit?.(newParticipant);
|
props.onSubmit?.(newParticipant);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -574,6 +574,7 @@ export const polls = router({
|
||||||
name: true,
|
name: true,
|
||||||
email: true,
|
email: true,
|
||||||
locale: true,
|
locale: true,
|
||||||
|
timeZone: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
email: true,
|
email: true,
|
||||||
|
@ -661,7 +662,8 @@ export const polls = router({
|
||||||
inviteeName: p.name,
|
inviteeName: p.name,
|
||||||
inviteeEmail:
|
inviteeEmail:
|
||||||
p.user?.email ?? p.email ?? `${p.id}@rallly.co`,
|
p.user?.email ?? p.email ?? `${p.id}@rallly.co`,
|
||||||
inviteeTimeZone: p.user?.timeZone ?? poll.timeZone, // We should track participant's timezone
|
inviteeTimeZone:
|
||||||
|
p.user?.timeZone ?? p.timeZone ?? poll.timeZone,
|
||||||
status: (
|
status: (
|
||||||
{
|
{
|
||||||
yes: "accepted",
|
yes: "accepted",
|
||||||
|
@ -758,6 +760,7 @@ export const polls = router({
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
locale: string | undefined;
|
locale: string | undefined;
|
||||||
|
timeZone: string | null;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
if (input.notify === "all") {
|
if (input.notify === "all") {
|
||||||
|
@ -768,6 +771,7 @@ export const polls = router({
|
||||||
name: p.name,
|
name: p.name,
|
||||||
email: p.email,
|
email: p.email,
|
||||||
locale: p.locale ?? undefined,
|
locale: p.locale ?? undefined,
|
||||||
|
timeZone: p.timeZone,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -781,6 +785,7 @@ export const polls = router({
|
||||||
name: p.name,
|
name: p.name,
|
||||||
email: p.email,
|
email: p.email,
|
||||||
locale: p.locale ?? undefined,
|
locale: p.locale ?? undefined,
|
||||||
|
timeZone: p.timeZone,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -821,7 +826,7 @@ export const polls = router({
|
||||||
end: scheduledEvent.end,
|
end: scheduledEvent.end,
|
||||||
allDay: scheduledEvent.allDay,
|
allDay: scheduledEvent.allDay,
|
||||||
timeZone: scheduledEvent.timeZone,
|
timeZone: scheduledEvent.timeZone,
|
||||||
// inviteeTimeZone: p.timeZone, // TODO: implement this
|
inviteeTimeZone: p.timeZone,
|
||||||
});
|
});
|
||||||
getEmailClient(p.locale ?? undefined).queueTemplate(
|
getEmailClient(p.locale ?? undefined).queueTemplate(
|
||||||
"FinalizeParticipantEmail",
|
"FinalizeParticipantEmail",
|
||||||
|
|
|
@ -129,6 +129,7 @@ export const participants = router({
|
||||||
pollId: z.string(),
|
pollId: z.string(),
|
||||||
name: z.string().min(1, "Participant name is required").max(100),
|
name: z.string().min(1, "Participant name is required").max(100),
|
||||||
email: z.string().optional(),
|
email: z.string().optional(),
|
||||||
|
timeZone: z.string().optional(),
|
||||||
votes: z
|
votes: z
|
||||||
.object({
|
.object({
|
||||||
optionId: z.string(),
|
optionId: z.string(),
|
||||||
|
@ -137,100 +138,103 @@ export const participants = router({
|
||||||
.array(),
|
.array(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input: { pollId, votes, name, email } }) => {
|
.mutation(
|
||||||
const { user } = ctx;
|
async ({ ctx, input: { pollId, votes, name, email, timeZone } }) => {
|
||||||
|
const { user } = ctx;
|
||||||
|
|
||||||
const participant = await prisma.$transaction(async (prisma) => {
|
const participant = await prisma.$transaction(async (prisma) => {
|
||||||
const participantCount = await prisma.participant.count({
|
const participantCount = await prisma.participant.count({
|
||||||
where: {
|
where: {
|
||||||
pollId,
|
pollId,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
|
||||||
if (participantCount >= MAX_PARTICIPANTS) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: `This poll has reached its maximum limit of ${MAX_PARTICIPANTS} participants`,
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const participant = await prisma.participant.create({
|
if (participantCount >= MAX_PARTICIPANTS) {
|
||||||
data: {
|
throw new TRPCError({
|
||||||
pollId: pollId,
|
code: "BAD_REQUEST",
|
||||||
name: name,
|
message: `This poll has reached its maximum limit of ${MAX_PARTICIPANTS} participants`,
|
||||||
email,
|
});
|
||||||
...(user.isGuest ? { guestId: user.id } : { userId: user.id }),
|
}
|
||||||
locale: user.locale ?? undefined,
|
|
||||||
},
|
const participant = await prisma.participant.create({
|
||||||
include: {
|
data: {
|
||||||
poll: {
|
pollId: pollId,
|
||||||
select: {
|
name: name,
|
||||||
id: true,
|
email,
|
||||||
title: true,
|
timeZone,
|
||||||
|
...(user.isGuest ? { guestId: user.id } : { userId: user.id }),
|
||||||
|
locale: user.locale ?? undefined,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
poll: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const options = await prisma.option.findMany({
|
const options = await prisma.option.findMany({
|
||||||
where: {
|
where: {
|
||||||
pollId,
|
pollId,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingOptionIds = new Set(options.map((option) => option.id));
|
|
||||||
|
|
||||||
const validVotes = votes.filter(({ optionId }) =>
|
|
||||||
existingOptionIds.has(optionId),
|
|
||||||
);
|
|
||||||
|
|
||||||
await prisma.vote.createMany({
|
|
||||||
data: validVotes.map(({ optionId, type }) => ({
|
|
||||||
optionId,
|
|
||||||
type,
|
|
||||||
pollId,
|
|
||||||
participantId: participant.id,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
return participant;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
const token = await createToken(
|
|
||||||
{ userId: user.id },
|
|
||||||
{
|
|
||||||
ttl: 0, // basically forever
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.user
|
|
||||||
.getEmailClient()
|
|
||||||
.queueTemplate("NewParticipantConfirmationEmail", {
|
|
||||||
to: email,
|
|
||||||
props: {
|
|
||||||
title: participant.poll.title,
|
|
||||||
editSubmissionUrl: absoluteUrl(
|
|
||||||
`/invite/${participant.poll.id}?token=${token}`,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
waitUntil(
|
const existingOptionIds = new Set(options.map((option) => option.id));
|
||||||
sendNewParticipantNotifcationEmail({
|
|
||||||
pollId,
|
|
||||||
pollTitle: participant.poll.title,
|
|
||||||
participantName: participant.name,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return participant;
|
const validVotes = votes.filter(({ optionId }) =>
|
||||||
}),
|
existingOptionIds.has(optionId),
|
||||||
|
);
|
||||||
|
|
||||||
|
await prisma.vote.createMany({
|
||||||
|
data: validVotes.map(({ optionId, type }) => ({
|
||||||
|
optionId,
|
||||||
|
type,
|
||||||
|
pollId,
|
||||||
|
participantId: participant.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
return participant;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
const token = await createToken(
|
||||||
|
{ userId: user.id },
|
||||||
|
{
|
||||||
|
ttl: 0, // basically forever
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.user
|
||||||
|
.getEmailClient()
|
||||||
|
.queueTemplate("NewParticipantConfirmationEmail", {
|
||||||
|
to: email,
|
||||||
|
props: {
|
||||||
|
title: participant.poll.title,
|
||||||
|
editSubmissionUrl: absoluteUrl(
|
||||||
|
`/invite/${participant.poll.id}?token=${token}`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
waitUntil(
|
||||||
|
sendNewParticipantNotifcationEmail({
|
||||||
|
pollId,
|
||||||
|
pollTitle: participant.poll.title,
|
||||||
|
participantName: participant.name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return participant;
|
||||||
|
},
|
||||||
|
),
|
||||||
rename: publicProcedure
|
rename: publicProcedure
|
||||||
.input(z.object({ participantId: z.string(), newName: z.string() }))
|
.input(z.object({ participantId: z.string(), newName: z.string() }))
|
||||||
.mutation(async ({ input: { participantId, newName } }) => {
|
.mutation(async ({ input: { participantId, newName } }) => {
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "participants" ADD COLUMN "time_zone" TEXT;
|
|
@ -76,6 +76,7 @@ model Participant {
|
||||||
guestId String? @map("guest_id")
|
guestId String? @map("guest_id")
|
||||||
pollId String @map("poll_id")
|
pollId String @map("poll_id")
|
||||||
locale String?
|
locale String?
|
||||||
|
timeZone String? @map("time_zone")
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime? @updatedAt @map("updated_at")
|
updatedAt DateTime? @updatedAt @map("updated_at")
|
||||||
deleted Boolean @default(false)
|
deleted Boolean @default(false)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue