forked from calcom/cal.com
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: ability to add guests via app.cal.com/bookings (calcom#14740)
* feat: ability to add guests via app.cal.com/bookings * fix: some update * fix: minor issue * fix: final update * update * update * add requested changes * fix type error * small update * final update * fix type error * fix location * update calender event --------- Co-authored-by: Somay Chauhan <[email protected]>
- Loading branch information
1 parent
b004587
commit baa9045
Showing
15 changed files
with
555 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import type { Dispatch, SetStateAction } from "react"; | ||
import { useState } from "react"; | ||
import { z } from "zod"; | ||
|
||
import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||
import { trpc } from "@calcom/trpc/react"; | ||
import { | ||
Button, | ||
Dialog, | ||
DialogContent, | ||
DialogFooter, | ||
DialogHeader, | ||
MultiEmail, | ||
Icon, | ||
showToast, | ||
} from "@calcom/ui"; | ||
|
||
interface IAddGuestsDialog { | ||
isOpenDialog: boolean; | ||
setIsOpenDialog: Dispatch<SetStateAction<boolean>>; | ||
bookingId: number; | ||
} | ||
|
||
export const AddGuestsDialog = (props: IAddGuestsDialog) => { | ||
const { t } = useLocale(); | ||
const ZAddGuestsInputSchema = z.array(z.string().email()).refine((emails) => { | ||
const uniqueEmails = new Set(emails); | ||
return uniqueEmails.size === emails.length; | ||
}); | ||
const { isOpenDialog, setIsOpenDialog, bookingId } = props; | ||
const utils = trpc.useUtils(); | ||
const [multiEmailValue, setMultiEmailValue] = useState<string[]>([""]); | ||
const [isInvalidEmail, setIsInvalidEmail] = useState(false); | ||
|
||
const addGuestsMutation = trpc.viewer.bookings.addGuests.useMutation({ | ||
onSuccess: async () => { | ||
showToast(t("guests_added"), "success"); | ||
setIsOpenDialog(false); | ||
setMultiEmailValue([""]); | ||
utils.viewer.bookings.invalidate(); | ||
}, | ||
onError: (err) => { | ||
const message = `${err.data?.code}: ${t(err.message)}`; | ||
showToast(message || t("unable_to_add_guests"), "error"); | ||
}, | ||
}); | ||
|
||
const handleAdd = () => { | ||
if (multiEmailValue.length === 0) { | ||
return; | ||
} | ||
const validationResult = ZAddGuestsInputSchema.safeParse(multiEmailValue); | ||
if (validationResult.success) { | ||
addGuestsMutation.mutate({ bookingId, guests: multiEmailValue }); | ||
} else { | ||
setIsInvalidEmail(true); | ||
} | ||
}; | ||
|
||
return ( | ||
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}> | ||
<DialogContent enableOverflow> | ||
<div className="flex flex-row space-x-3"> | ||
<div className="bg-subtle flex h-10 w-10 flex-shrink-0 justify-center rounded-full "> | ||
<Icon name="user-plus" className="m-auto h-6 w-6" /> | ||
</div> | ||
<div className="w-full pt-1"> | ||
<DialogHeader title={t("additional_guests")} /> | ||
<MultiEmail | ||
label={t("add_emails")} | ||
value={multiEmailValue} | ||
readOnly={false} | ||
setValue={setMultiEmailValue} | ||
/> | ||
|
||
{isInvalidEmail && ( | ||
<div className="my-4 flex text-sm text-red-700"> | ||
<div className="flex-shrink-0"> | ||
<Icon name="triangle-alert" className="h-5 w-5" /> | ||
</div> | ||
<div className="ml-3"> | ||
<p className="font-medium">{t("emails_must_be_unique_valid")}</p> | ||
</div> | ||
</div> | ||
)} | ||
|
||
<DialogFooter> | ||
<Button | ||
onClick={() => { | ||
setMultiEmailValue([""]); | ||
setIsInvalidEmail(false); | ||
setIsOpenDialog(false); | ||
}} | ||
type="button" | ||
color="secondary"> | ||
{t("cancel")} | ||
</Button> | ||
<Button data-testid="add_members" loading={addGuestsMutation.isPending} onClick={handleAdd}> | ||
{t("add")} | ||
</Button> | ||
</DialogFooter> | ||
</div> | ||
</div> | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail"; | ||
|
||
export const AttendeeAddGuestsEmail = (props: React.ComponentProps<typeof AttendeeScheduledEmail>) => ( | ||
<AttendeeScheduledEmail | ||
title="new_guests_added" | ||
headerType="calendarCircle" | ||
subject="guests_added_event_type_subject" | ||
{...props} | ||
/> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail"; | ||
|
||
export const OrganizerAddGuestsEmail = (props: React.ComponentProps<typeof OrganizerScheduledEmail>) => ( | ||
<OrganizerScheduledEmail | ||
title="new_guests_added" | ||
headerType="calendarCircle" | ||
subject="guests_added_event_type_subject" | ||
callToAction={null} | ||
{...props} | ||
/> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { renderEmail } from "../"; | ||
import generateIcsString from "../lib/generateIcsString"; | ||
import AttendeeScheduledEmail from "./attendee-scheduled-email"; | ||
|
||
export default class AttendeeAddGuestsEmail extends AttendeeScheduledEmail { | ||
protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { | ||
return { | ||
icalEvent: { | ||
filename: "event.ics", | ||
content: generateIcsString({ | ||
event: this.calEvent, | ||
title: this.t("new_guests_added"), | ||
subtitle: this.t("emailed_you_and_any_other_attendees"), | ||
role: "attendee", | ||
status: "CONFIRMED", | ||
}), | ||
method: "REQUEST", | ||
}, | ||
to: `${this.attendee.name} <${this.attendee.email}>`, | ||
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, | ||
replyTo: this.calEvent.organizer.email, | ||
subject: `${this.t("guests_added_event_type_subject", { | ||
eventType: this.calEvent.type, | ||
name: this.calEvent.team?.name || this.calEvent.organizer.name, | ||
date: this.getFormattedDate(), | ||
})}`, | ||
html: await renderEmail("AttendeeAddGuestsEmail", { | ||
calEvent: this.calEvent, | ||
attendee: this.attendee, | ||
}), | ||
text: this.getTextBody("new_guests_added"), | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { APP_NAME } from "@calcom/lib/constants"; | ||
|
||
import { renderEmail } from "../"; | ||
import generateIcsString from "../lib/generateIcsString"; | ||
import OrganizerScheduledEmail from "./organizer-scheduled-email"; | ||
|
||
export default class OrganizerAddGuestsEmail extends OrganizerScheduledEmail { | ||
protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { | ||
const toAddresses = [this.teamMember?.email || this.calEvent.organizer.email]; | ||
|
||
return { | ||
icalEvent: { | ||
filename: "event.ics", | ||
content: generateIcsString({ | ||
event: this.calEvent, | ||
title: this.t("new_guests_added"), | ||
subtitle: this.t("emailed_you_and_any_other_attendees"), | ||
role: "organizer", | ||
status: "CONFIRMED", | ||
}), | ||
method: "REQUEST", | ||
}, | ||
from: `${APP_NAME} <${this.getMailerOptions().from}>`, | ||
to: toAddresses.join(","), | ||
replyTo: [this.calEvent.organizer.email, ...this.calEvent.attendees.map(({ email }) => email)], | ||
subject: `${this.t("guests_added_event_type_subject", { | ||
eventType: this.calEvent.type, | ||
name: this.calEvent.attendees[0].name, | ||
date: this.getFormattedDate(), | ||
})}`, | ||
html: await renderEmail("OrganizerAddGuestsEmail", { | ||
attendee: this.calEvent.organizer, | ||
calEvent: this.calEvent, | ||
}), | ||
text: this.getTextBody("new_guests_added"), | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.