Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -8,8 +8,7 @@ import {
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Calendar, Trash2, CheckCircle, XCircle } from "lucide-react";
import { Trash2, CheckCircle, XCircle } from "lucide-react";
import { CalendarList } from "./CalendarList";
import { useAction } from "next-safe-action/hooks";
import {
Expand All @@ -20,6 +19,8 @@ import { useAccount } from "@/providers/EmailAccountProvider";
import { useCalendars } from "@/hooks/useCalendars";
import { useState } from "react";
import type { GetCalendarsResponse } from "@/app/api/user/calendars/route";
import Image from "next/image";
import { TypographyP } from "@/components/Typography";

type CalendarConnection = GetCalendarsResponse["connections"][0];

Expand Down Expand Up @@ -96,30 +97,23 @@ export function CalendarConnectionCard({
}
};

const enabledCalendars =
connection.calendars?.filter((cal) => cal.isEnabled)?.length ?? 0;
const totalCalendars = connection.calendars?.length ?? 0;

return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-100 rounded-lg">
<Calendar className="h-5 w-5 text-blue-600" />
</div>
<Image
src="/images/product/google-calendar.svg"
alt="Google Calendar"
width={32}
height={32}
unoptimized
/>
<div>
<CardTitle className="text-lg">{connection.email}</CardTitle>
<CardTitle className="text-lg">Google Calendar</CardTitle>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify future provider support.

The title is hardcoded to "Google Calendar". If the application plans to support other calendar providers (Outlook, Apple Calendar, etc.) in the future, this will need to become dynamic based on the connection's provider.

Run the following script to check if there are multiple calendar providers in the codebase:


🏁 Script executed:

#!/bin/bash
# Description: Search for calendar provider types or enums to determine if multiple providers are planned

# Search for calendar provider types
echo "=== Searching for calendar provider types ==="
rg -nP --type=ts 'CalendarProvider|calendar.*provider|provider.*calendar' -A 3 -B 3

# Search for calendar connection provider field
echo -e "\n=== Searching for provider field in calendar schema ==="
rg -nP 'calendarConnection|CalendarConnection' -A 10 | rg -P 'provider'

# Check for Outlook/Microsoft calendar references
echo -e "\n=== Checking for non-Google calendar references ==="
rg -nP --type=ts 'outlook|microsoft.*calendar|apple.*calendar' -i

Length of output: 36031


🏁 Script executed:

#!/bin/bash
# Display context around CalendarConnectionCard.tsx line 113
FILE=$(fd -p 'CalendarConnectionCard.tsx' -t f | head -n 1)
echo "File: $FILE"
sed -n '90,140p' "$FILE"

Length of output: 1612


Make calendar provider title and icon dynamic
Update CalendarConnectionCard.tsx to use connection.provider (e.g. “google” | “microsoft”) to select the icon and title instead of hardcoding “Google Calendar.”
File: apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx (around line 113)

🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/calendars/CalendarConnectionCard.tsx
around line 113, the card title and icon are hardcoded to "Google Calendar";
change them to derive from connection.provider (e.g. "google" or "microsoft").
Implement a small mapping or switch that returns the proper display title
("Google Calendar", "Microsoft Calendar" etc.) and corresponding icon component
based on connection.provider, then render the mapped icon and title in place of
the hardcoded values; ensure you fall back to a sensible default for unknown
providers.

Comment thread
elie222 marked this conversation as resolved.
<CardDescription className="flex items-center gap-2">
<Badge variant="secondary" className="capitalize">
{connection.provider}
</Badge>
{connection.isConnected ? (
<div className="flex items-center gap-1 text-green-600">
<CheckCircle className="h-3 w-3" />
<span className="text-xs">Connected</span>
</div>
) : (
{connection.email}
{!connection.isConnected && (
<div className="flex items-center gap-1 text-red-600">
<XCircle className="h-3 w-3" />
<span className="text-xs">Disconnected</span>
Expand All @@ -144,14 +138,10 @@ export function CalendarConnectionCard({
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium">Calendars</p>
<p className="text-xs text-muted-foreground">
{enabledCalendars} of {totalCalendars} calendars enabled
</p>
</div>
</div>
<TypographyP className="text-sm">
Toggle the calendars you want to check for conflicts to prevent
double bookings.
</TypographyP>

{connection.calendars && connection.calendars.length > 0 ? (
<CalendarList
Expand Down
11 changes: 7 additions & 4 deletions apps/web/app/(app)/[emailAccountId]/calendars/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { PageHeading } from "@/components/Typography";
import { PageWrapper } from "@/components/PageWrapper";
import { PageHeader } from "@/components/PageHeader";
import { CalendarConnections } from "./CalendarConnections";
import { ConnectCalendarButton } from "@/app/(app)/[emailAccountId]/calendars/ConnectCalendarButton";

export default function CalendarsPage() {
return (
<PageWrapper>
<div className="flex items-center justify-between">
<PageHeading>Calendars</PageHeading>
<div className="flex items-center justify-between 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 />
</div>
<div className="mt-4">
<div className="mt-6">
<CalendarConnections />
</div>
</PageWrapper>
Expand Down
18 changes: 18 additions & 0 deletions apps/web/app/(app)/[emailAccountId]/setup/SetupContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
BotIcon,
type LucideIcon,
ChromeIcon,
CalendarIcon,
} from "lucide-react";
import { useLocalStorage } from "usehooks-ts";
import { PageHeading, SectionDescription } from "@/components/Typography";
Expand Down Expand Up @@ -211,6 +212,7 @@ function Checklist({
isReplyTrackerConfigured,
isBulkUnsubscribeConfigured,
isAiAssistantConfigured,
isCalendarConnected,
}: {
emailAccountId: string;
provider: string;
Expand All @@ -220,6 +222,7 @@ function Checklist({
isReplyTrackerConfigured: boolean;
isBulkUnsubscribeConfigured: boolean;
isAiAssistantConfigured: boolean;
isCalendarConnected: boolean;
}) {
const [isExtensionInstalled, setIsExtensionInstalled] = useLocalStorage(
"inbox-zero-extension-installed",
Expand Down Expand Up @@ -282,6 +285,17 @@ function Checklist({
actionText="View"
/>

<StepItem
href={prefixPath(emailAccountId, "/calendars")}
icon={<CalendarIcon size={20} />}
iconBg="bg-yellow-100 dark:bg-yellow-900/50"
iconColor="text-yellow-600 dark:text-yellow-400"
title="Connect your calendar"
timeEstimate="2 minutes"
completed={isCalendarConnected}
actionText="Connect"
/>

{isGoogleProvider(provider) && (
<StepItem
href={EXTENSION_URL}
Expand Down Expand Up @@ -314,6 +328,7 @@ export function SetupContent() {
isReplyTrackerConfigured={data.steps.replyTracker}
isAiAssistantConfigured={data.steps.aiAssistant}
isBulkUnsubscribeConfigured={data.steps.bulkUnsubscribe}
isCalendarConnected={data.steps.calendarConnected}
completedCount={data.completed}
totalSteps={data.total}
isSetupComplete={data.isComplete}
Expand All @@ -329,6 +344,7 @@ function SetupPageContent({
isReplyTrackerConfigured,
isBulkUnsubscribeConfigured,
isAiAssistantConfigured,
isCalendarConnected,
completedCount,
totalSteps,
isSetupComplete,
Expand All @@ -338,6 +354,7 @@ function SetupPageContent({
isReplyTrackerConfigured: boolean;
isBulkUnsubscribeConfigured: boolean;
isAiAssistantConfigured: boolean;
isCalendarConnected: boolean;
completedCount: number;
totalSteps: number;
isSetupComplete: boolean;
Expand All @@ -364,6 +381,7 @@ function SetupPageContent({
isReplyTrackerConfigured={isReplyTrackerConfigured}
isBulkUnsubscribeConfigured={isBulkUnsubscribeConfigured}
isAiAssistantConfigured={isAiAssistantConfigured}
isCalendarConnected={isCalendarConnected}
completedCount={completedCount}
totalSteps={totalSteps}
progressPercentage={progressPercentage}
Expand Down
2 changes: 2 additions & 0 deletions apps/web/app/api/user/setup-progress/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ async function getSetupProgress({
where: { status: { not: null } },
take: 1,
},
calendarConnections: { select: { id: true }, take: 1 },
},
});

Expand All @@ -43,6 +44,7 @@ async function getSetupProgress({
aiAssistant: emailAccount.rules.length > 0,
bulkUnsubscribe: emailAccount.newsletters.length > 0,
replyTracker: isReplyTrackerConfigured,
calendarConnected: emailAccount.calendarConnections.length > 0,
};

const completed = Object.values(steps).filter(Boolean).length;
Expand Down
6 changes: 6 additions & 0 deletions apps/web/components/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
BarChartBigIcon,
BookIcon,
BrushIcon,
CalendarIcon,
ChevronDownIcon,
ChevronRightIcon,
CrownIcon,
Expand Down Expand Up @@ -101,6 +102,11 @@ export const useNavigation = () => {
href: prefixPath(currentEmailAccountId, "/stats"),
icon: BarChartBigIcon,
},
{
name: "Calendars",
href: prefixPath(currentEmailAccountId, "/calendars"),
icon: CalendarIcon,
},
],
[currentEmailAccountId, provider],
);
Expand Down
1 change: 1 addition & 0 deletions apps/web/public/images/product/google-calendar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.14.2
v2.15.0
Loading