import { appPath } from "@agent-native/core/client/api-path";
import { DevDatabaseLink } from "@agent-native/core/client/db-admin";
import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
import { openCommandMenu } from "@agent-native/core/client/navigation";
import { OrgSwitcher } from "@agent-native/core/client/org";
import { FeedbackButton } from "@agent-native/core/client/ui";
import { SidebarFooterActions } from "@agent-native/toolkit/app-shell";
import {
IconCalendar,
IconSettings,
IconLink,
IconExternalLink,
IconChevronUp,
IconChevronDown,
IconChevronLeft,
IconChevronRight,
IconPlus,
IconX,
IconInfoCircle,
IconCheck,
IconEye,
IconEyeOff,
IconLayoutSidebarLeftCollapse,
IconLayoutSidebarLeftExpand,
IconSearch,
} from "@tabler/icons-react";
import {
startOfMonth,
endOfMonth,
startOfWeek,
endOfWeek,
addDays,
addMonths,
subMonths,
isSameMonth,
isSameDay,
isToday,
format,
setMonth,
setYear,
getMonth,
getYear,
} from "date-fns";
import { useState, useEffect, useMemo } from "react";
import { Link, useLocation, useNavigate } from "react-router";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
useExternalCalendars,
useRemoveExternalCalendar,
useUpdateExternalCalendarColor,
} from "@/hooks/use-external-calendars";
import {
useGoogleAuthStatus,
useGoogleAuthUrl,
useGoogleAddAccountUrl,
useGoogleDesktopAuth,
} from "@/hooks/use-google-auth";
import {
useOverlayPeople,
useRemoveOverlayPerson,
useUpdateOverlayPersonColor,
} from "@/hooks/use-overlay-people";
import { useViewPreferences } from "@/hooks/use-view-preferences";
import {
CALENDAR_COLORS,
defaultColorForAccount,
type CalendarColorMode,
} from "@/lib/calendar-view-preferences";
import { EVENT_CATEGORY_COLORS } from "@/lib/event-colors";
import { cn } from "@/lib/utils";
import { useCalendarContext } from "./AppLayout";
const navItems = [
{ path: "/", labelKey: "navigation.calendar", icon: IconCalendar },
{
path: "/booking-links",
labelKey: "navigation.bookingLinks",
icon: IconLink,
},
];
const bottomNavItems = [
{ path: "/settings", labelKey: "navigation.settings", icon: IconSettings },
];
interface SidebarProps {
open: boolean;
onClose: () => void;
collapsed?: boolean;
onCollapsedChange?: (collapsed: boolean) => void;
}
const MONTH_LABELS = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
function MonthYearPicker({
viewMonth,
onPick,
onClose,
}: {
viewMonth: Date;
onPick: (date: Date) => void;
onClose: () => void;
}) {
const [year, setYearState] = useState(() => getYear(viewMonth));
const today = new Date();
const todayMonth = getMonth(today);
const todayYear = getYear(today);
const viewedMonthIdx = getMonth(viewMonth);
const viewedYear = getYear(viewMonth);
const t = useT();
return (
{year}
{MONTH_LABELS.map((label, idx) => {
const isCurrent = idx === todayMonth && year === todayYear;
const isSelected = idx === viewedMonthIdx && year === viewedYear;
return (
);
})}
);
}
function MiniCalendar({
selectedDate,
onDateSelect,
}: {
selectedDate: Date;
onDateSelect: (date: Date) => void;
}) {
const [viewMonth, setViewMonth] = useState(() => startOfMonth(selectedDate));
const [pickerOpen, setPickerOpen] = useState(false);
// Sync viewMonth when selectedDate changes to a different month
useEffect(() => {
if (!isSameMonth(viewMonth, selectedDate)) {
setViewMonth(startOfMonth(selectedDate));
}
}, [selectedDate]);
const days = useMemo(() => {
const monthStart = startOfMonth(viewMonth);
const monthEnd = endOfMonth(viewMonth);
const calStart = startOfWeek(monthStart);
const calEnd = endOfWeek(monthEnd);
const result: Date[] = [];
let current = calStart;
while (current <= calEnd) {
result.push(current);
current = addDays(current, 1);
}
return result;
}, [viewMonth]);
const weekdays = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
return (
{/* Month header with navigation */}
setViewMonth(startOfMonth(d))}
onClose={() => setPickerOpen(false)}
/>
{/* Weekday headers */}
{weekdays.map((d) => (
{d}
))}
{/* Date grid */}
{days.map((day) => {
const inMonth = isSameMonth(day, viewMonth);
const today = isToday(day);
const selected = isSameDay(day, selectedDate);
return (
);
})}
);
}
function GoogleConnectSidebarButton() {
const t = useT();
const [wantAuthUrl, setWantAuthUrl] = useState(false);
const authUrl = useGoogleAuthUrl(wantAuthUrl);
const {
isDesktopGoogleAuth,
isGoogleDesktopAuthPending,
startDesktopGoogleAuth,
} = useGoogleDesktopAuth({
onError: (issue) =>
toast.error(issue.message || issue.error || t("settings.googleFailed")),
onSuccess: () => window.location.reload(),
});
useEffect(() => {
if (!wantAuthUrl || !authUrl.data?.url) return;
setWantAuthUrl(false);
window.open(authUrl.data.url, "_blank");
}, [wantAuthUrl, authUrl.data]);
useEffect(() => {
if (!authUrl.error) return;
toast.error(authUrl.error.message);
setWantAuthUrl(false);
}, [authUrl.error]);
function handleConnect() {
if (isDesktopGoogleAuth) {
startDesktopGoogleAuth({ previousAccountCount: 0 });
return;
}
setWantAuthUrl(true);
}
return (
{t("settings.connectGoogleCalendar")}
{t("settings.connectGoogleDescription")}
);
}
/** A conic-gradient dot indicating "multiple colors" (by-type mode) */
function MultiColorDot({ className }: { className?: string }) {
const colors = Object.values(EVENT_CATEGORY_COLORS).filter((_, i) => i < 4);
const pct = 100 / colors.length;
const stops = colors
.map((c, i) => `${c} ${i * pct}% ${(i + 1) * pct}%`)
.join(", ");
return (
);
}
/** Popover color picker for a single-color selection */
function ColorPickerPopover({
color,
onColorChange,
children,
}: {
color: string;
onColorChange: (color: string) => void;
children: React.ReactNode;
}) {
return (
{children}
{CALENDAR_COLORS.map((c) => (
))}
);
}
function GoogleAccountsSection({
accounts,
onClose,
}: {
accounts: Array<{ email: string }>;
onClose: () => void;
}) {
const t = useT();
const { toggleHiddenCalendar, isHiddenCalendar } = useCalendarContext();
const {
prefs: { colorMode, singleColor, accountColorModes, accountColors },
updateAccountColor,
updateAccountColorMode,
} = useViewPreferences();
const accountEmails = useMemo(
() => accounts.map((account) => account.email),
[accounts],
);
const [wantAddAccount, setWantAddAccount] = useState(false);
const addAccountUrl = useGoogleAddAccountUrl(wantAddAccount);
const {
isDesktopGoogleAuth,
isGoogleDesktopAuthPending,
startDesktopGoogleAuth,
} = useGoogleDesktopAuth({
onError: (issue) =>
toast.error(issue.message || issue.error || t("settings.googleFailed")),
onSuccess: () => window.location.reload(),
});
useEffect(() => {
if (!wantAddAccount || !addAccountUrl.data?.url) return;
window.open(addAccountUrl.data.url, "_blank");
setWantAddAccount(false);
}, [wantAddAccount, addAccountUrl.data]);
useEffect(() => {
if (!addAccountUrl.error) return;
toast.error(addAccountUrl.error.message);
setWantAddAccount(false);
}, [addAccountUrl.error]);
function handleAddAccount() {
if (isDesktopGoogleAuth) {
startDesktopGoogleAuth({
addAccount: true,
previousAccountCount: accounts.length,
});
return;
}
setWantAddAccount(true);
}
function accountColorMode(email: string): CalendarColorMode {
return accountColorModes[email] ?? colorMode;
}
function accountColor(email: string): string {
return (
accountColors[email] ??
singleColor ??
defaultColorForAccount(email, accountEmails)
);
}
function handlePickColor(email: string, color: string) {
updateAccountColor(email, color);
}
function handleSetColorMode(email: string, mode: CalendarColorMode) {
updateAccountColorMode(email, mode);
}
return (
{t("sidebar.myCalendars")}
{t("sidebar.googleCalendarSettings")}
{t("sidebar.addGoogleAccount")}
{accounts.map((account) => {
const mode = accountColorMode(account.email);
const color = accountColor(account.email);
return (
{/* Multicolor "by type" option */}
{t("sidebar.colorByMeetingType")}
{/* Single color options */}
{CALENDAR_COLORS.map((c) => (
))}
{account.email}
);
})}
);
}
export function Sidebar({
open,
onClose,
collapsed = false,
onCollapsedChange,
}: SidebarProps) {
const location = useLocation();
const navigate = useNavigate();
const t = useT();
const {
selectedDate,
setSelectedDate,
setAddCalendarOpen,
setAddCalendarDefaultTab,
toggleHiddenCalendar,
isHiddenCalendar,
} = useCalendarContext();
const googleStatus = useGoogleAuthStatus();
const { data: rawOverlayPeople } = useOverlayPeople();
const overlayPeople = Array.isArray(rawOverlayPeople) ? rawOverlayPeople : [];
const removePerson = useRemoveOverlayPerson();
const updatePersonColor = useUpdateOverlayPersonColor();
const { data: rawExternalCalendars } = useExternalCalendars();
const externalCalendars = Array.isArray(rawExternalCalendars)
? rawExternalCalendars
: [];
const removeExternal = useRemoveExternalCalendar();
const updateExternalColor = useUpdateExternalCalendarColor();
const isConnected = googleStatus.data?.connected ?? false;
const [peopleGroupOpen, setPeopleGroupOpen] = useState(
() => overlayPeople.length <= 2, // i18n-ignore scanner false positive
);
const [feedsGroupOpen, setFeedsGroupOpen] = useState(
() => externalCalendars.length <= 2, // i18n-ignore scanner false positive
);
useEffect(() => {
if (overlayPeople.length <= 2) setPeopleGroupOpen(true);
}, [overlayPeople.length]);
useEffect(() => {
if (externalCalendars.length <= 2) setFeedsGroupOpen(true);
}, [externalCalendars.length]);
function handleMiniCalendarDateSelect(date: Date) {
setSelectedDate(date);
if (location.pathname !== "/") {
navigate("/");
}
onClose();
}
const collapseButton = onCollapsedChange ? (
{collapsed ? t("sidebar.expandSidebar") : t("sidebar.collapseSidebar")}
) : null;
const searchButton = (
{t("root.commandSearch")}
);
const translateButton = (
);
const feedbackButton = (
);
return (
<>
{/* Mobile overlay */}
{open && (
)}
>
);
}