import { CalendarDays, Check, ChevronLeft, RefreshCw, type LucideIcon } from 'lucide-react' import { useId, useState, type ReactNode } from 'react' import { useNavigate } from 'react-router' import { Button } from '../ui/Button.tsx' import buttonStyles from '../ui/Button.module.css' import { Card } from '../ui/Card.tsx' import { ErrorBanner } from '../ui/ErrorBanner.tsx' import { Icon } from '../ui/Icon.tsx' import { IconButton } from '../ui/IconButton.tsx' import { Select } from '../ui/Select.tsx' import { Skeleton } from '../ui/Skeleton.tsx' import { GOOGLE_CONNECT_PATH, useChooseGoogleCalendar, useDisconnectGoogle, useGoogleCalendars, useGoogleConnection, type GoogleConnection, } from './calendarApi.ts' import styles from './CalendarSettingsPage.module.css' import { Confirm } from './LeaveGuard.tsx' // Settings · Google Calendar: connect the household's Google account, choose // the calendar Push to Calendar writes the week's dinners to, and // disconnect. A grant Google has refused shows a grape banner asking to // reconnect, and nothing that needs Google is offered until then. export function CalendarSettingsPage() { const connection = useGoogleConnection() return (

Push to Calendar adds one event per dinner. It is one-way: edits made in Google Calendar do not come back.

{connection.isPending && } {connection.isError && ( void connection.refetch()}> Google Calendar status could not load: {connection.error.message} )} {connection.isSuccess && }
) } function Header() { const navigate = useNavigate() return (
void navigate('/settings')}>

Settings

Google Calendar

) } function Connection({ connection }: { connection: GoogleConnection }) { switch (connection.status) { case 'not_connected': return (

Connect the household's Google account to push each week's plan to one of its calendars.

Connect Google Calendar
) case 'reconnect_required': return ( <>

{connection.calendar_name === null ? 'No calendar chosen. Choose one once Google Calendar is reconnected.' : `Dinners go to ${connection.calendar_name} once Google Calendar is reconnected.`}

) case 'connected': return (

{connection.calendar_name === null ? 'Connected' : `Connected · ${connection.calendar_name}`}

) } } // ReconnectBanner is the grape banner of a grant Google refused, with why // and Reconnect. Why and what to do are an alert, as ErrorBanner's message // is: a calendar request that meets the refusal turns the screen to it. function ReconnectBanner({ error }: { error: string | null }) { const titleId = useId() return (

Reconnect Google Calendar

{error !== null &&

{error}

}

Nothing can be pushed to Calendar until you connect again.

Reconnect
) } function Section({ title, children }: { title: string; children: ReactNode }) { const titleId = useId() return (

{title}

{children}
) } // ConnectLink opens the connect endpoint as a page: the server sends the // browser on to Google's consent screen, and back here once Google answers. function ConnectLink({ icon = CalendarDays, children }: { icon?: LucideIcon; children: ReactNode }) { return ( {children} ) } // CalendarPicker chooses, from the calendars the account can write to, the // one dinners are pushed to; a choice is saved at once. A calendar chosen // before that has since been deleted or become read-only is no longer // listed, and is said to be so. function CalendarPicker({ connection }: { connection: GoogleConnection }) { const calendars = useGoogleCalendars(true) const choose = useChooseGoogleCalendar() if (calendars.isPending) return if (calendars.isError) { return ( void calendars.refetch()}> Your calendars could not load: {calendars.error.message} ) } if (calendars.data.length === 0) { return

This Google account has no calendar MealQuest can add dinners to.

} const listed = calendars.data.some((calendar) => calendar.id === connection.calendar_id) const value = choose.isPending ? choose.variables : listed ? connection.calendar_id! : '' return ( <> {connection.calendar_id === null &&

Choose the calendar to push dinners to.

} {connection.calendar_id !== null && !listed && (

{connection.calendar_name} was deleted or MealQuest can no longer add dinners to it. Choose another calendar.

)}

{choose.isPending && 'Saving…'}

{choose.isError && (

Not saved: {choose.error.message}

)}

After choosing another calendar, the next push writes the whole week there. Dinners already pushed stay where they are.

) } // Disconnect revokes MealQuest's access at Google, after asking. function Disconnect({ calendar }: { calendar: string | null }) { const [confirming, setConfirming] = useState(false) const disconnect = useDisconnectGoogle() return ( <>
setConfirming(false)} onConfirm={() => disconnect.mutate(undefined, { onSuccess: () => setConfirming(false) })} > MealQuest gives up its access to your Google account and stops pushing dinners.{' '} {calendar === null ? 'Dinners already pushed' : `Dinners already pushed to ${calendar}`} stay on the calendar. ) }