129 lines
4.4 KiB
TypeScript
129 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useTransition } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
disconnectCalendarAction,
|
|
listAvailableCalendarsAction,
|
|
selectCalendarAction,
|
|
syncNowAction,
|
|
} from "../actions/calendar";
|
|
import type { RemoteCalendarInfo } from "@/services/calendar/types";
|
|
|
|
export interface ConnectionState {
|
|
connected: boolean;
|
|
accountEmail?: string;
|
|
selectedCalendarName?: string;
|
|
lastSyncAt?: string;
|
|
lastSyncError?: string | null;
|
|
}
|
|
|
|
export function CalendarConnectionPanel({ initial, configured }: { initial: ConnectionState; configured: boolean }) {
|
|
const [state, setState] = useState(initial);
|
|
const [isPending, startTransition] = useTransition();
|
|
const [message, setMessage] = useState<string | null>(null);
|
|
const [calendars, setCalendars] = useState<RemoteCalendarInfo[] | null>(null);
|
|
|
|
const sync = () => {
|
|
setMessage(null);
|
|
startTransition(async () => {
|
|
const result = await syncNowAction();
|
|
setMessage(result.success ? "Sync complete." : (result.error ?? "Sync failed."));
|
|
});
|
|
};
|
|
|
|
const disconnect = () => {
|
|
setMessage(null);
|
|
startTransition(async () => {
|
|
await disconnectCalendarAction();
|
|
setState({ connected: false });
|
|
setMessage("Disconnected. Existing calendar events were left as they are.");
|
|
});
|
|
};
|
|
|
|
const loadCalendars = () => {
|
|
setMessage(null);
|
|
startTransition(async () => {
|
|
const result = await listAvailableCalendarsAction();
|
|
if (!result.success) {
|
|
setMessage(result.error ?? "Couldn't load calendars.");
|
|
return;
|
|
}
|
|
setCalendars(result.calendars ?? []);
|
|
});
|
|
};
|
|
|
|
const chooseCalendar = (calendar: RemoteCalendarInfo) => {
|
|
startTransition(async () => {
|
|
const result = await selectCalendarAction(calendar.id, calendar.name);
|
|
if (result.success) {
|
|
setState((prev) => ({ ...prev, selectedCalendarName: calendar.name }));
|
|
setCalendars(null);
|
|
setMessage(`Now syncing to "${calendar.name}".`);
|
|
}
|
|
});
|
|
};
|
|
|
|
if (!state.connected) {
|
|
return (
|
|
<div className="border-line rounded-jumbo border bg-white p-5">
|
|
<p className="mb-1 text-sm font-bold">Google Calendar</p>
|
|
<p className="text-muted mb-4 text-xs">
|
|
Sync your confirmed appointments to your own Google Calendar. This connects a Google account separately
|
|
from sign-in — you can use a different one if you like.
|
|
</p>
|
|
{message && <p className="text-danger mb-3 text-xs font-medium">{message}</p>}
|
|
{configured ? (
|
|
<Button asChild size="sm">
|
|
<a href="/api/staff/calendar/google/authorize">Connect Google Calendar</a>
|
|
</Button>
|
|
) : (
|
|
<p className="text-muted text-xs">Google Calendar sync is not configured on this deployment yet.</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="border-line rounded-jumbo border bg-white p-5">
|
|
<p className="mb-1 text-sm font-bold">Google Calendar</p>
|
|
<div className="text-muted mb-4 space-y-1 text-xs">
|
|
<p>Connected as {state.accountEmail}</p>
|
|
<p>Syncing to “{state.selectedCalendarName}”</p>
|
|
{state.lastSyncAt && <p>Last synced {new Date(state.lastSyncAt).toLocaleString()}</p>}
|
|
{state.lastSyncError && <p className="text-danger">Last sync error: {state.lastSyncError}</p>}
|
|
</div>
|
|
|
|
{message && <p className="text-muted mb-3 text-xs font-medium">{message}</p>}
|
|
|
|
{calendars ? (
|
|
<ul className="mb-4 space-y-2">
|
|
{calendars.map((calendar) => (
|
|
<li key={calendar.id}>
|
|
<button
|
|
type="button"
|
|
onClick={() => chooseCalendar(calendar)}
|
|
className="border-line w-full rounded-lg border px-3 py-2 text-left text-xs font-bold hover:bg-stone"
|
|
>
|
|
{calendar.name} {calendar.primary && "(primary)"}
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : null}
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
<Button size="sm" isLoading={isPending} onClick={sync}>
|
|
Sync Now
|
|
</Button>
|
|
<Button size="sm" variant="secondary" isLoading={isPending} onClick={loadCalendars}>
|
|
Change Calendar
|
|
</Button>
|
|
<Button size="sm" variant="destructive" isLoading={isPending} onClick={disconnect}>
|
|
Disconnect
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|