144 lines
3.4 KiB
TypeScript
144 lines
3.4 KiB
TypeScript
import { googleCalendarAuth } from './auth';
|
|
import { startOfMonth, endOfMonth, format } from 'date-fns';
|
|
|
|
const CALENDAR_API_BASE = 'https://www.googleapis.com/calendar/v3';
|
|
|
|
export interface CalendarEvent {
|
|
id: string;
|
|
summary: string;
|
|
description?: string;
|
|
location?: string;
|
|
start: {
|
|
dateTime?: string;
|
|
date?: string;
|
|
timeZone?: string;
|
|
};
|
|
end: {
|
|
dateTime?: string;
|
|
date?: string;
|
|
timeZone?: string;
|
|
};
|
|
status: string;
|
|
colorId?: string;
|
|
}
|
|
|
|
export interface Calendar {
|
|
id: string;
|
|
summary: string;
|
|
description?: string;
|
|
primary?: boolean;
|
|
backgroundColor?: string;
|
|
foregroundColor?: string;
|
|
}
|
|
|
|
export interface CalendarListResponse {
|
|
items: Calendar[];
|
|
}
|
|
|
|
export interface EventsListResponse {
|
|
items: CalendarEvent[];
|
|
nextPageToken?: string;
|
|
}
|
|
|
|
async function fetchWithAuth(url: string): Promise<Response> {
|
|
const token = await googleCalendarAuth.getAccessToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated with Google Calendar');
|
|
}
|
|
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (response.status === 401) {
|
|
// Token might be invalid, clear and throw
|
|
googleCalendarAuth.clearTokens();
|
|
throw new Error('Authentication expired, please re-authenticate');
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Calendar API error: ${response.status}`);
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
export async function getCalendarList(): Promise<Calendar[]> {
|
|
const response = await fetchWithAuth(`${CALENDAR_API_BASE}/users/me/calendarList`);
|
|
const data: CalendarListResponse = await response.json();
|
|
return data.items;
|
|
}
|
|
|
|
export async function getEventsForMonth(
|
|
calendarId: string = 'primary',
|
|
date: Date = new Date()
|
|
): Promise<CalendarEvent[]> {
|
|
const timeMin = startOfMonth(date).toISOString();
|
|
const timeMax = endOfMonth(date).toISOString();
|
|
|
|
const params = new URLSearchParams({
|
|
timeMin,
|
|
timeMax,
|
|
singleEvents: 'true',
|
|
orderBy: 'startTime',
|
|
maxResults: '100',
|
|
});
|
|
|
|
const url = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events?${params}`;
|
|
const response = await fetchWithAuth(url);
|
|
const data: EventsListResponse = await response.json();
|
|
return data.items;
|
|
}
|
|
|
|
export async function getEventsForDay(
|
|
calendarId: string = 'primary',
|
|
date: Date = new Date()
|
|
): Promise<CalendarEvent[]> {
|
|
const dayStart = new Date(date);
|
|
dayStart.setHours(0, 0, 0, 0);
|
|
|
|
const dayEnd = new Date(date);
|
|
dayEnd.setHours(23, 59, 59, 999);
|
|
|
|
const params = new URLSearchParams({
|
|
timeMin: dayStart.toISOString(),
|
|
timeMax: dayEnd.toISOString(),
|
|
singleEvents: 'true',
|
|
orderBy: 'startTime',
|
|
});
|
|
|
|
const url = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events?${params}`;
|
|
const response = await fetchWithAuth(url);
|
|
const data: EventsListResponse = await response.json();
|
|
return data.items;
|
|
}
|
|
|
|
export function getEventTime(event: CalendarEvent): { start: Date; end: Date; allDay: boolean } {
|
|
const allDay = !!event.start.date;
|
|
|
|
let start: Date;
|
|
let end: Date;
|
|
|
|
if (allDay) {
|
|
start = new Date(event.start.date!);
|
|
end = new Date(event.end.date!);
|
|
} else {
|
|
start = new Date(event.start.dateTime!);
|
|
end = new Date(event.end.dateTime!);
|
|
}
|
|
|
|
return { start, end, allDay };
|
|
}
|
|
|
|
export function formatEventTime(event: CalendarEvent): string {
|
|
const { start, allDay } = getEventTime(event);
|
|
|
|
if (allDay) {
|
|
return 'All day';
|
|
}
|
|
|
|
return format(start, 'h:mm a');
|
|
}
|