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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ A modern React application for managing and organizing meetings efficiently. Bui
- Calendar data import and parsing
- Drag-and-drop meeting prioritization
- Meeting statistics and time tracking
- Meeting Rating
- Modern UI with shadcn/ui components
- Persistent storage with Zustand

Expand Down
15 changes: 12 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,21 @@ function App() {

useEffect(() => {
const init = async () => {
await googleCalendarService.initializeGoogleApi(
import.meta.env.VITE_GOOGLE_CLIENT_ID
);
try {
// Initialize Google API with proper error handling
await googleCalendarService.initializeGoogleApi(
import.meta.env.VITE_GOOGLE_CLIENT_ID
);
} catch (error) {
console.error("Failed to initialize Google API:", error);
// Continue with the app even if Google API fails
}

// Initialize store regardless of Google API status
await initializeStore();
setStoreInitialized(true);
};

init();
}, [initializeStore]);

Expand Down
57 changes: 53 additions & 4 deletions src/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,29 @@ interface Props {

interface State {
hasError: boolean;
errorMessage: string;
isNetworkError: boolean;
}

class ErrorBoundary extends Component<Props, State> {
public state: State = {
hasError: false,
errorMessage: "",
isNetworkError: false,
};

public static getDerivedStateFromError(): State {
return { hasError: true };
public static getDerivedStateFromError(error: Error): State {
// Check if it's a port disconnection or CORS-related error
const isNetworkError =
error.message.includes("disconnected port") ||
error.message.includes("Cross-Origin") ||
error.message.includes("NetworkError");

return {
hasError: true,
errorMessage: error.message,
isNetworkError,
};
}

public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
Expand All @@ -23,9 +37,44 @@ class ErrorBoundary extends Component<Props, State> {

public render() {
if (this.state.hasError) {
if (this.state.isNetworkError) {
return (
<div className="min-h-screen flex flex-col items-center justify-center p-6 text-center">
<h1 className="text-xl font-semibold mb-4">
Network Issue Detected
</h1>
<p className="mb-4">
We're having trouble connecting to some services. This might be
due to:
</p>
<ul className="list-disc text-left mb-6">
<li className="ml-6">Network connection problems</li>
<li className="ml-6">Browser security settings</li>
<li className="ml-6">
Cross-origin resource sharing (CORS) restrictions
</li>
</ul>
<p>Try refreshing the page or check your internet connection.</p>
<button
onClick={() => window.location.reload()}
className="mt-4 px-4 py-2 bg-primary text-primary-foreground rounded-md"
>
Refresh Page
</button>
</div>
);
}

return (
<div className="min-h-screen flex items-center justify-center">
<h1>Sorry.. there was an error</h1>
<div className="min-h-screen flex flex-col items-center justify-center p-6 text-center">
<h1 className="text-xl font-semibold mb-4">Something went wrong</h1>
<p className="mb-6">We're sorry, but an error has occurred.</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-primary text-primary-foreground rounded-md"
>
Refresh Page
</button>
</div>
);
}
Expand Down
131 changes: 110 additions & 21 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,26 @@ import {
CalendarIcon,
} from "@radix-ui/react-icons";
import { Meeting } from "@/types";
import { importCalendarData } from "@/services/calendarService";
import {
importCalendarData,
importGoogleCalendar,
setDaysAhead,
getDaysBetween,
DAYS_AGO,
} from "@/services/calendarService";
import { googleCalendarService } from "@/services/googleCalendarService";
import { importGoogleCalendar } from "@/services/calendarService";
import { db } from "@/services/db";
import { ExportInstructions } from "./ExportInstructions";
import { EmailDialog } from "./EmailDialog";
import { mockMeetings } from "@/mockData";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
DialogDescription,
} from "./ui/dialog";

interface HeaderProps {
meetings: Meeting[];
Expand All @@ -34,6 +47,20 @@ export function Header({
const [isClearing, setIsClearing] = useState(false);
const [emailDialogOpen, setEmailDialogOpen] = useState(false);
const [showEmailSuccess, setShowEmailSuccess] = useState(false);
const [toDate, setToDate] = useState<Date>(getDefaultEndDate());
const [datePickerOpen, setDatePickerOpen] = useState(false);

// Calculate default end date (7 days from now)
function getDefaultEndDate(): Date {
const date = new Date();
date.setDate(date.getDate() + 7);
return date;
}

// Format date for date input
const formatDateForInput = (date: Date): string => {
return date.toISOString().split("T")[0];
};

const handleFileUpload = async (
event: React.ChangeEvent<HTMLInputElement>
Expand Down Expand Up @@ -72,6 +99,44 @@ export function Header({
setUseMockData(true);
};

const handleGoogleImport = async () => {
try {
setIsImporting(true);
setDatePickerOpen(false); // Close the date picker dialog

// Update the DAYS_AHEAD parameter based on the selected end date
const today = new Date();
today.setHours(0, 0, 0, 0);
const daysAhead = getDaysBetween(today, toDate);
setDaysAhead(daysAhead);

await googleCalendarService.authenticate();

// Create the date range with DAYS_AGO as start and the selected end date
const startDate = new Date();
startDate.setDate(startDate.getDate() - DAYS_AGO); // Use the constant
startDate.setHours(0, 0, 0, 0);

const dateRange = {
startDate: startDate,
endDate: toDate,
};

const meetings = await importGoogleCalendar(dateRange);

if (meetings.length === 0) {
alert("No meetings found in the specified date range");
} else {
setMeetings(meetings);
}
} catch (error) {
console.error("Error importing from Google Calendar:", error);
alert("Failed to import calendar. Please try again.");
} finally {
setIsImporting(false);
}
};

return (
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
Expand Down Expand Up @@ -113,30 +178,14 @@ export function Header({
<CalendarIcon className="w-4 h-4 mr-2" />
Use Mock Data
</Button>

<Button
variant="outline"
disabled={isImporting}
onClick={async () => {
try {
setIsImporting(true);
await googleCalendarService.authenticate();
const meetings = await importGoogleCalendar();

if (meetings.length === 0) {
alert("No meetings found in the specified date range");
} else {
setMeetings(meetings);
}
} catch (error) {
console.error("Error importing from Google Calendar:", error);
alert("Failed to import calendar. Please try again.");
} finally {
setIsImporting(false);
}
}}
onClick={() => setDatePickerOpen(true)}
>
<CalendarIcon className="w-4 h-4 mr-2" />
{isImporting ? "Importing..." : "Import from Google"}
Import from Google
</Button>
</div>

Expand All @@ -149,6 +198,46 @@ export function Header({
setTimeout(() => setShowEmailSuccess(false), 2000);
}}
/>

<Dialog open={datePickerOpen} onOpenChange={setDatePickerOpen}>
<DialogContent
className="sm:max-w-[425px]"
aria-describedby="date-picker-description"
>
<DialogHeader>
<DialogTitle>Select End Date</DialogTitle>
<DialogDescription id="date-picker-description">
Choose the end date for importing Google Calendar events.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="flex flex-col gap-2">
<p className="text-sm text-muted-foreground">
Events will be imported from {DAYS_AGO} days ago until the
selected date.
</p>
<label htmlFor="end-date" className="text-sm font-medium">
End Date
</label>
<Input
id="end-date"
type="date"
value={formatDateForInput(toDate)}
onChange={(e) => setToDate(new Date(e.target.value))}
className="col-span-3"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setDatePickerOpen(false)}>
Cancel
</Button>
<Button onClick={handleGoogleImport} disabled={isImporting}>
{isImporting ? "Importing..." : "Import"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}
11 changes: 7 additions & 4 deletions src/components/Review.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useMemo } from "react";
import { useSettingsStore } from "../stores/settingsStore";
import { WeeklyMeetingLoad } from "./WeeklyMeetingLoad";
import { WeeklyFreeMeetingHours } from "./WeeklyFreeMeetingHours";
Expand All @@ -24,8 +24,11 @@ interface DashboardItem {

export function Review() {
const { meetings: storedMeetings, targetHours } = useSettingsStore();
const dateRanges = getDateRanges();
const [meetings, setMeetings] = useState(

// Use useMemo to memoize the dateRanges calculation
const dateRanges = useMemo(() => getDateRanges(), []);

const [meetings, setMeetings] = useState(() =>
filterMeetingsByDateRange(
storedMeetings,
dateRanges.review.start,
Expand Down Expand Up @@ -66,9 +69,9 @@ export function Review() {
]);
}, [
storedMeetings,
targetHours,
dateRanges.review.start,
dateRanges.review.end,
targetHours,
]);

const handleDragEnd = (result: DropResult) => {
Expand Down
4 changes: 4 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { createRoot } from "react-dom/client";
import ErrorBoundary from "./ErrorBoundary";
import App from "./App";
import "./index.css";
import { setupGlobalErrorHandlers } from "./utils/corsHandler";

// Set up global error handlers
setupGlobalErrorHandlers();

const rootElement = document.getElementById("root");

Expand Down
39 changes: 33 additions & 6 deletions src/services/calendarService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,19 @@ import { googleCalendarService } from "@/services/googleCalendarService";

// Constants for date ranges
export const DAYS_AGO = 90;
export const DAYS_AHEAD = 7;
export let DAYS_AHEAD = 7;

// Function to update DAYS_AHEAD
export const setDaysAhead = (days: number) => {
DAYS_AHEAD = days;
return DAYS_AHEAD;
};

// Calculate days between two dates
export const getDaysBetween = (startDate: Date, endDate: Date): number => {
const difference = endDate.getTime() - startDate.getTime();
return Math.ceil(difference / (1000 * 3600 * 24));
};

export const getDateRanges = () => {
const today = new Date();
Expand Down Expand Up @@ -39,16 +51,31 @@ export const getDateRanges = () => {
};
};

export const importGoogleCalendar = async () => {
export const importGoogleCalendar = async (dateRange?: {
startDate: Date;
endDate: Date;
}) => {
try {
// If no date range is provided, use the default date ranges
const dateRanges = getDateRanges();

// Use provided date range or default to fetching both review and planned meetings
const startDate = dateRange?.startDate || dateRanges.review.start;
const endDate = dateRange?.endDate || dateRanges.plan.end;

// console.log(
// "Fetching Google Calendar events from:",
// startDate.toISOString(),
// "to:",
// endDate.toISOString()
// );

const events = await googleCalendarService.getCalendarEvents(
dateRanges.review.start,
dateRanges.plan.end
startDate,
endDate
);

console.log("Fetched events count:", events?.length);
// console.log("Fetched events count:", events?.length);

if (!events || events.length === 0) {
return [];
Expand All @@ -66,7 +93,7 @@ export const importGoogleCalendar = async () => {
comment: "",
}));

console.log("Transformed events count:", transformedEvents.length);
// console.log("Transformed events count:", transformedEvents.length);
return transformedEvents;
} catch (error) {
console.error("Failed to import Google Calendar:", error);
Expand Down
Loading