Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ interface CalendarConnectionCardProps {
connection: CalendarConnection;
}

const getProviderInfo = (provider: string) => {
const providers = {
microsoft: {
name: "Microsoft Calendar",
icon: "/images/product/outlook-calendar.svg",
alt: "Microsoft Calendar",
},
google: {
name: "Google Calendar",
icon: "/images/product/google-calendar.svg",
alt: "Google Calendar",
},
};

return providers[provider as keyof typeof providers] || providers.google;
};

export function CalendarConnectionCard({
connection,
}: CalendarConnectionCardProps) {
Expand All @@ -37,6 +54,8 @@ export function CalendarConnectionCard({
Record<string, boolean>
>({});

const providerInfo = getProviderInfo(connection.provider);

const { execute: executeDisconnect, isExecuting: isDisconnecting } =
useAction(disconnectCalendarAction.bind(null, emailAccountId));
const { execute: executeToggle } = useAction(
Expand Down Expand Up @@ -103,14 +122,14 @@ export function CalendarConnectionCard({
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Image
src="/images/product/google-calendar.svg"
alt="Google Calendar"
src={providerInfo.icon}
alt={providerInfo.alt}
width={32}
height={32}
unoptimized
/>
<div>
<CardTitle className="text-lg">Google Calendar</CardTitle>
<CardTitle className="text-lg">{providerInfo.name}</CardTitle>
<CardDescription className="flex items-center gap-2">
{connection.email}
{!connection.isConnected && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function CalendarConnections() {
{connections.length === 0 ? (
<div className="text-center text-muted-foreground py-10">
<p>No calendar connections found.</p>
<p>Connect your Google Calendar to get started.</p>
<p>Connect your Google or Microsoft Calendar to get started.</p>
</div>
) : (
<div className="grid gap-4">
Expand Down
101 changes: 101 additions & 0 deletions apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { useAccount } from "@/providers/EmailAccountProvider";
import { toastError } from "@/components/Toast";
import type { GetCalendarAuthUrlResponse } from "@/app/api/google/calendar/auth-url/route";
import { fetchWithAccount } from "@/utils/fetch";
import Image from "next/image";

export function ConnectCalendar() {
const { emailAccountId } = useAccount();
const [isConnectingGoogle, setIsConnectingGoogle] = useState(false);
const [isConnectingMicrosoft, setIsConnectingMicrosoft] = useState(false);

const handleConnectGoogle = async () => {
setIsConnectingGoogle(true);
try {
const response = await fetchWithAccount({
url: "/api/google/calendar/auth-url",
emailAccountId,
init: { headers: { "Content-Type": "application/json" } },
});

if (!response.ok) {
throw new Error("Failed to initiate Google calendar connection");
}

const data: GetCalendarAuthUrlResponse = await response.json();
window.location.href = data.url;
} catch (error) {
console.error("Error initiating Google calendar connection:", error);
toastError({
title: "Error initiating Google calendar connection",
description: "Please try again or contact support",
});
setIsConnectingGoogle(false);
}
};

const handleConnectMicrosoft = async () => {
setIsConnectingMicrosoft(true);
try {
const response = await fetchWithAccount({
url: "/api/outlook/calendar/auth-url",
emailAccountId,
init: { headers: { "Content-Type": "application/json" } },
});

if (!response.ok) {
throw new Error("Failed to initiate Microsoft calendar connection");
}

const data: GetCalendarAuthUrlResponse = await response.json();
window.location.href = data.url;
} catch (error) {
console.error("Error initiating Microsoft calendar connection:", error);
toastError({
title: "Error initiating Microsoft calendar connection",
description: "Please try again or contact support",
});
setIsConnectingMicrosoft(false);
}
};

return (
<div className="flex gap-2 flex-wrap md:flex-nowrap">
<Button
onClick={handleConnectGoogle}
disabled={isConnectingGoogle || isConnectingMicrosoft}
variant="outline"
className="flex items-center gap-2 w-full md:w-auto"
>
<Image
src="/images/google.svg"
alt="Google"
width={16}
height={16}
unoptimized
/>
{isConnectingGoogle ? "Connecting..." : "Add Google Calendar"}
</Button>

<Button
onClick={handleConnectMicrosoft}
disabled={isConnectingGoogle || isConnectingMicrosoft}
variant="outline"
className="flex items-center gap-2 w-full md:w-auto"
>
<Image
src="/images/microsoft.svg"
alt="Microsoft"
width={16}
height={16}
unoptimized
/>
{isConnectingMicrosoft ? "Connecting..." : "Add Outlook Calendar"}
</Button>
</div>
);
}

This file was deleted.

6 changes: 3 additions & 3 deletions apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { PageWrapper } from "@/components/PageWrapper";
import { PageHeader } from "@/components/PageHeader";
import { CalendarConnections } from "./CalendarConnections";
import { ConnectCalendarButton } from "@/app/(app)/[emailAccountId]/calendars/ConnectCalendarButton";
import { ConnectCalendar } from "@/app/(app)/[emailAccountId]/calendars/ConnectCalendar";

export default function CalendarsPage() {
return (
<PageWrapper>
<div className="flex items-center justify-between gap-4">
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-3 lg:gap-4">
<PageHeader
title="Calendars"
description="Connect your calendar to allow our AI to suggest meeting times based on your availability when drafting replies."
/>
<ConnectCalendarButton />
<ConnectCalendar />
</div>
<div className="mt-6">
<CalendarConnections />
Expand Down
37 changes: 37 additions & 0 deletions apps/web/app/api/outlook/calendar/auth-url/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { NextResponse } from "next/server";
import { withEmailAccount } from "@/utils/middleware";
import { getCalendarOAuth2Url } from "@/utils/outlook/calendar-client";
import { CALENDAR_STATE_COOKIE_NAME } from "@/utils/calendar/constants";
import {
generateOAuthState,
oauthStateCookieOptions,
} from "@/utils/oauth/state";

export type GetCalendarAuthUrlResponse = { url: string };

const getAuthUrl = ({ emailAccountId }: { emailAccountId: string }) => {
const state = generateOAuthState({
emailAccountId,
type: "calendar",
});

const url = getCalendarOAuth2Url(state);

return { url, state };
};

export const GET = withEmailAccount(async (request) => {
const { emailAccountId } = request.auth;
const { url, state } = getAuthUrl({ emailAccountId });

const res: GetCalendarAuthUrlResponse = { url };
const response = NextResponse.json(res);

response.cookies.set(
CALENDAR_STATE_COOKIE_NAME,
state,
oauthStateCookieOptions,
);

return response;
});
Loading
Loading