mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-26 20:57:24 +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 { useTranslation } from "@/i18n/client";
|
||||
|
||||
import { useTimezone } from "@/features/timezone";
|
||||
import { useAddParticipantMutation } from "./poll/mutations";
|
||||
import VoteIcon from "./poll/vote-icon";
|
||||
import { useUser } from "./user-provider";
|
||||
|
@ -89,7 +90,7 @@ export const NewParticipantForm = (props: NewParticipantModalProps) => {
|
|||
const poll = usePoll();
|
||||
|
||||
const isEmailRequired = poll.requireParticipantEmail;
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
const { user, createGuestIfNeeded } = useUser();
|
||||
const isLoggedIn = !user.isGuest;
|
||||
const { register, setError, formState, handleSubmit } =
|
||||
|
@ -117,6 +118,7 @@ export const NewParticipantForm = (props: NewParticipantModalProps) => {
|
|||
votes: props.votes,
|
||||
email: data.email,
|
||||
pollId: poll.id,
|
||||
timeZone: timezone,
|
||||
});
|
||||
props.onSubmit?.(newParticipant);
|
||||
} catch (error) {
|
||||
|
|
|
@ -574,6 +574,7 @@ export const polls = router({
|
|||
name: true,
|
||||
email: true,
|
||||
locale: true,
|
||||
timeZone: true,
|
||||
user: {
|
||||
select: {
|
||||
email: true,
|
||||
|
@ -661,7 +662,8 @@ export const polls = router({
|
|||
inviteeName: p.name,
|
||||
inviteeEmail:
|
||||
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: (
|
||||
{
|
||||
yes: "accepted",
|
||||
|
@ -758,6 +760,7 @@ export const polls = router({
|
|||
name: string;
|
||||
email: string;
|
||||
locale: string | undefined;
|
||||
timeZone: string | null;
|
||||
}> = [];
|
||||
|
||||
if (input.notify === "all") {
|
||||
|
@ -768,6 +771,7 @@ export const polls = router({
|
|||
name: p.name,
|
||||
email: p.email,
|
||||
locale: p.locale ?? undefined,
|
||||
timeZone: p.timeZone,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -781,6 +785,7 @@ export const polls = router({
|
|||
name: p.name,
|
||||
email: p.email,
|
||||
locale: p.locale ?? undefined,
|
||||
timeZone: p.timeZone,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -821,7 +826,7 @@ export const polls = router({
|
|||
end: scheduledEvent.end,
|
||||
allDay: scheduledEvent.allDay,
|
||||
timeZone: scheduledEvent.timeZone,
|
||||
// inviteeTimeZone: p.timeZone, // TODO: implement this
|
||||
inviteeTimeZone: p.timeZone,
|
||||
});
|
||||
getEmailClient(p.locale ?? undefined).queueTemplate(
|
||||
"FinalizeParticipantEmail",
|
||||
|
|
|
@ -129,6 +129,7 @@ export const participants = router({
|
|||
pollId: z.string(),
|
||||
name: z.string().min(1, "Participant name is required").max(100),
|
||||
email: z.string().optional(),
|
||||
timeZone: z.string().optional(),
|
||||
votes: z
|
||||
.object({
|
||||
optionId: z.string(),
|
||||
|
@ -137,100 +138,103 @@ export const participants = router({
|
|||
.array(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input: { pollId, votes, name, email } }) => {
|
||||
const { user } = ctx;
|
||||
.mutation(
|
||||
async ({ ctx, input: { pollId, votes, name, email, timeZone } }) => {
|
||||
const { user } = ctx;
|
||||
|
||||
const participant = await prisma.$transaction(async (prisma) => {
|
||||
const participantCount = await prisma.participant.count({
|
||||
where: {
|
||||
pollId,
|
||||
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.$transaction(async (prisma) => {
|
||||
const participantCount = await prisma.participant.count({
|
||||
where: {
|
||||
pollId,
|
||||
deleted: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const participant = await prisma.participant.create({
|
||||
data: {
|
||||
pollId: pollId,
|
||||
name: name,
|
||||
email,
|
||||
...(user.isGuest ? { guestId: user.id } : { userId: user.id }),
|
||||
locale: user.locale ?? undefined,
|
||||
},
|
||||
include: {
|
||||
poll: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
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({
|
||||
data: {
|
||||
pollId: pollId,
|
||||
name: name,
|
||||
email,
|
||||
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({
|
||||
where: {
|
||||
pollId,
|
||||
},
|
||||
select: {
|
||||
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}`,
|
||||
),
|
||||
const options = await prisma.option.findMany({
|
||||
where: {
|
||||
pollId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
waitUntil(
|
||||
sendNewParticipantNotifcationEmail({
|
||||
pollId,
|
||||
pollTitle: participant.poll.title,
|
||||
participantName: participant.name,
|
||||
}),
|
||||
);
|
||||
const existingOptionIds = new Set(options.map((option) => option.id));
|
||||
|
||||
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
|
||||
.input(z.object({ participantId: z.string(), newName: z.string() }))
|
||||
.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")
|
||||
pollId String @map("poll_id")
|
||||
locale String?
|
||||
timeZone String? @map("time_zone")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime? @updatedAt @map("updated_at")
|
||||
deleted Boolean @default(false)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue