feat: Fix Proton Calendar with ICS (#15454)
* feat: fix proton calendar with ics * ics-feed: add note & clarify checkbox * fix review --------- Co-authored-by: Keith Williams <keithwillcode@gmail.com> Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
This commit is contained in:
co-authored by
Keith Williams
Amit Sharma
parent
3f4a0eff7f
commit
7046f6f80a
@@ -2494,7 +2494,9 @@
|
||||
"USER_PENDING_MEMBER_OF_THE_ORG": "User is a pending member of the organization",
|
||||
"USER_ALREADY_INVITED_OR_MEMBER": "User is already invited or a member",
|
||||
"USER_MEMBER_OF_OTHER_ORGANIZATION": "User is member of an organization that this team is not a part of.",
|
||||
"skip_writing_to_calendar": "Do not write to the ICS feed",
|
||||
"rescheduling_not_possible": "Rescheduling is not possible as the event has expired",
|
||||
"event_expired": "This event is expired",
|
||||
"skip_writing_to_calendar_note": "If your ICS link is read-only (e.g., Proton Calendar), check the box above to avoid errors. You'll also need to manually update your calendar for changes.",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { CalendarService } from "../lib";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method === "POST") {
|
||||
const { urls } = req.body;
|
||||
const { urls, skipWriting } = req.body;
|
||||
// Get user
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
@@ -24,7 +24,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
const data = {
|
||||
type: appConfig.type,
|
||||
key: symmetricEncrypt(JSON.stringify({ urls }), process.env.CALENDSO_ENCRYPTION_KEY || ""),
|
||||
key: symmetricEncrypt(JSON.stringify({ urls, skipWriting }), process.env.CALENDSO_ENCRYPTION_KEY || ""),
|
||||
userId: user.id,
|
||||
teamId: null,
|
||||
appId: appConfig.slug,
|
||||
|
||||
@@ -40,18 +40,28 @@ const CALENDSO_ENCRYPTION_KEY = process.env.CALENDSO_ENCRYPTION_KEY || "";
|
||||
|
||||
export default class ICSFeedCalendarService implements Calendar {
|
||||
private urls: string[] = [];
|
||||
private skipWriting = false;
|
||||
protected integrationName = "ics-feed_calendar";
|
||||
|
||||
constructor(credential: CredentialPayload) {
|
||||
const { urls } = JSON.parse(symmetricDecrypt(credential.key as string, CALENDSO_ENCRYPTION_KEY));
|
||||
const { urls, skipWriting } = JSON.parse(
|
||||
symmetricDecrypt(credential.key as string, CALENDSO_ENCRYPTION_KEY)
|
||||
);
|
||||
this.urls = urls;
|
||||
this.skipWriting = skipWriting;
|
||||
}
|
||||
|
||||
createEvent(_event: CalendarEvent, _credentialId: number): Promise<NewCalendarEventType> {
|
||||
if (this.skipWriting) {
|
||||
return Promise.reject(new Error("Event creation is disabled for this calendar."));
|
||||
}
|
||||
throw new Error("createEvent called on read-only ICS feed");
|
||||
}
|
||||
|
||||
deleteEvent(_uid: string, _event: CalendarEvent, _externalCalendarId?: string): Promise<unknown> {
|
||||
if (this.skipWriting) {
|
||||
return Promise.reject(new Error("Event creation is disabled for this calendar."));
|
||||
}
|
||||
throw new Error("deleteEvent called on read-only ICS feed");
|
||||
}
|
||||
|
||||
@@ -60,6 +70,9 @@ export default class ICSFeedCalendarService implements Calendar {
|
||||
_event: CalendarEvent,
|
||||
_externalCalendarId?: string
|
||||
): Promise<NewCalendarEventType | NewCalendarEventType[]> {
|
||||
if (this.skipWriting) {
|
||||
return Promise.reject(new Error("Event creation is disabled for this calendar."));
|
||||
}
|
||||
throw new Error("updateEvent called on read-only ICS feed");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useForm } from "react-hook-form";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Alert, Button, Form, TextField } from "@calcom/ui";
|
||||
import { Alert, Button, Form, TextField, CheckboxField } from "@calcom/ui";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
export default function ICSFeedSetup() {
|
||||
@@ -17,6 +17,7 @@ export default function ICSFeedSetup() {
|
||||
const [urls, setUrls] = useState<string[]>([""]);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [errorActionUrl, setErrorActionUrl] = useState("");
|
||||
const [skipWriting, setSkipWriting] = useState(false); // track if user opts out of writing to any calendar
|
||||
|
||||
return (
|
||||
<div className="bg-emphasis flex h-screen">
|
||||
@@ -40,7 +41,7 @@ export default function ICSFeedSetup() {
|
||||
setErrorMessage("");
|
||||
const res = await fetch("/api/integrations/ics-feedcalendar/add", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ urls }),
|
||||
body: JSON.stringify({ urls, skipWriting }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -88,9 +89,27 @@ export default function ICSFeedSetup() {
|
||||
onClick={() => {
|
||||
setUrls((urls) => urls.concat(""));
|
||||
}}>
|
||||
{t("add")} <Icon name="plus" size={16} />
|
||||
{t("add")} <Icon className="inline" name="plus" size={16} />
|
||||
</button>
|
||||
|
||||
<div className="mt-3 flex items-center">
|
||||
<CheckboxField
|
||||
type="checkbox"
|
||||
id="skipWriting"
|
||||
checked={skipWriting}
|
||||
onChange={(e) => setSkipWriting(e.target.checked)}
|
||||
className="mr-2"
|
||||
description={t("skip_writing_to_calendar")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Alert
|
||||
className="mt-3"
|
||||
severity="info"
|
||||
title={t("notes")}
|
||||
message={t("skip_writing_to_calendar_note")}
|
||||
/>
|
||||
|
||||
{errorMessage && (
|
||||
<Alert
|
||||
severity="error"
|
||||
|
||||
Reference in New Issue
Block a user