Skip to content
Open
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
48 changes: 24 additions & 24 deletions src/components/FocusTimer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { useAuth } from '@/contexts/useAuth';
import { supabase } from '@/integrations/supabase/client';
import { Play, Square, Coffee, Clock } from 'lucide-react';
Expand Down Expand Up @@ -41,22 +41,21 @@ export default function FocusTimer() {
fetchProfileState();
}, [user]);

// Timer logic
useEffect(() => {
let interval: NodeJS.Timeout;
const updateProfile = useCallback(async (inFocus: boolean, focusTime?: number) => {
if (!user) return;

if (isActive && timeLeft > 0) {
interval = setInterval(() => {
setTimeLeft((prev) => prev - 1);
}, 1000);
} else if (isActive && timeLeft === 0) {
handleTimerComplete();
const updates: any = { is_in_focus_mode: inFocus };
if (focusTime !== undefined) {
updates.focus_time_this_week = focusTime;
}

return () => clearInterval(interval);
}, [isActive, timeLeft]);
await supabase
.from('profiles')
.update(updates)
.eq('id', user.id);
}, [user]);

const handleTimerComplete = async () => {
const handleTimerComplete = useCallback(async () => {
if (!isBreak) {
// Completed a work session
toast({
Expand All @@ -83,21 +82,22 @@ export default function FocusTimer() {
setIsActive(false);
setTimeLeft(workDuration * 60);
}
};
}, [isBreak, toast, workDuration, breakDuration, user, focusTimeThisWeek, updateProfile]);

const updateProfile = async (inFocus: boolean, focusTime?: number) => {
if (!user) return;
// Timer logic
useEffect(() => {
let interval: NodeJS.Timeout;

const updates: any = { is_in_focus_mode: inFocus };
if (focusTime !== undefined) {
updates.focus_time_this_week = focusTime;
if (isActive && timeLeft > 0) {
interval = setInterval(() => {
setTimeLeft((prev) => prev - 1);
}, 1000);
} else if (isActive && timeLeft === 0) {
handleTimerComplete();
}

await supabase
.from('profiles')
.update(updates)
.eq('id', user.id);
};
return () => clearInterval(interval);
}, [isActive, timeLeft, handleTimerComplete]);

const toggleTimer = async () => {
const newIsActive = !isActive;
Expand Down
60 changes: 30 additions & 30 deletions src/components/GroupPomodoro.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { Play, Square, Coffee, Clock } from 'lucide-react';
import { Button } from '@/components/ui/button';
Expand Down Expand Up @@ -61,32 +61,7 @@ export default function GroupPomodoro({ roomId }: GroupPomodoroProps) {
};
}, [roomId]);

// Countdown logic
useEffect(() => {
let interval: NodeJS.Timeout;

if (timerState !== 'idle' && endTime) {
interval = setInterval(() => {
const now = new Date();
const diff = Math.max(0, Math.floor((endTime.getTime() - now.getTime()) / 1000));

setTimeLeft(diff);

// Timer completed!
if (diff === 0) {
handleTimerComplete();
}
}, 1000);
} else {
setTimeLeft(timerState === 'break' ? breakDuration * 60 : workDuration * 60);
}

return () => {
if (interval) clearInterval(interval);
};
}, [timerState, endTime, workDuration, breakDuration]);

const handleTimerComplete = async () => {
const handleTimerComplete = useCallback(async () => {
if (timerState === 'work') {
toast({
title: "Group Focus Session Complete! 🎉",
Expand All @@ -100,9 +75,9 @@ export default function GroupPomodoro({ roomId }: GroupPomodoroProps) {
});
await setGroupTimer('idle', 0);
}
};
}, [timerState, toast, breakDuration, setGroupTimer]);

const setGroupTimer = async (newState: 'idle' | 'work' | 'break', durationMinutes: number) => {
const setGroupTimer = useCallback(async (newState: 'idle' | 'work' | 'break', durationMinutes: number) => {
let newEndTime = null;

if (newState !== 'idle') {
Expand All @@ -119,7 +94,32 @@ export default function GroupPomodoro({ roomId }: GroupPomodoroProps) {
timer_break_duration: breakDuration
})
.eq('id', roomId);
};
}, [roomId, workDuration, breakDuration]);

// Countdown logic
useEffect(() => {
let interval: NodeJS.Timeout;

if (timerState !== 'idle' && endTime) {
interval = setInterval(() => {
const now = new Date();
const diff = Math.max(0, Math.floor((endTime.getTime() - now.getTime()) / 1000));

setTimeLeft(diff);

// Timer completed!
if (diff === 0) {
handleTimerComplete();
}
}, 1000);
} else {
setTimeLeft(timerState === 'break' ? breakDuration * 60 : workDuration * 60);
}

return () => {
if (interval) clearInterval(interval);
};
}, [timerState, endTime, workDuration, breakDuration, handleTimerComplete]);

const formatTime = (seconds: number) => {
const m = Math.floor(seconds / 60);
Expand Down
61 changes: 30 additions & 31 deletions src/components/Room.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useState, useEffect, useRef } from 'react';
import { useState, useEffect, useRef, useCallback } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { supabase } from '@/integrations/supabase/client';
import { useAuth } from '@/contexts/useAuth';
Expand Down Expand Up @@ -30,6 +29,32 @@ export default function Room() {
const [showInviteUI, setShowInviteUI] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);

const fetchRoomDetails = useCallback(async () => {
const { data, error } = await supabase.from('study_rooms' as any).select('*').eq('id', id).single();
if (error) {
console.error("Error fetching room:", error);
if (error.code === 'PGRST116') {
alert("Room not found or you don't have access.");
navigate('/rooms');
}
}
if (data) setRoom(data);
}, [id, navigate]);

const fetchMessages = useCallback(async () => {
const { data, error } = await supabase
.from('study_room_messages' as any)
.select('*, profiles(name, avatar_url)')
.eq('room_id', id)
.order('created_at', { ascending: true });

if (error) {
console.error("Database fetch error:", error.message, error.details);
} else if (data) {
setMessages(data);
}
}, [id]);

useEffect(() => {
if (!id || !user) return;

Expand Down Expand Up @@ -57,9 +82,9 @@ export default function Room() {

setParticipants(onlineUsers);

setActivities([
setActivities((prev) => [
`${onlineUsers.length} participant(s) online`,
...activities,
...prev,
]);
})
.on('postgres_changes', {
Expand Down Expand Up @@ -95,38 +120,12 @@ export default function Room() {

if (roomChannel) supabase.removeChannel(roomChannel);
};
}, [id, user]);
}, [id, user, fetchMessages, fetchRoomDetails]);

useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);

const fetchRoomDetails = async () => {
const { data, error } = await supabase.from('study_rooms' as any).select('*').eq('id', id).single();
if (error) {
console.error("Error fetching room:", error);
if (error.code === 'PGRST116') {
alert("Room not found or you don't have access.");
navigate('/rooms');
}
}
if (data) setRoom(data);
};

const fetchMessages = async () => {
const { data, error } = await supabase
.from('study_room_messages' as any)
.select('*, profiles(name, avatar_url)')
.eq('room_id', id)
.order('created_at', { ascending: true });

if (error) {
console.error("Database fetch error:", error.message, error.details);
} else if (data) {
setMessages(data);
}
};

const handleSendMessage = async (e: React.FormEvent) => {
e.preventDefault();
if (!newMessage.trim() || !user) return;
Expand Down
9 changes: 4 additions & 5 deletions src/components/Whiteboard/Canvas.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState, useCallback } from "react";
import { supabase } from "@/integrations/supabase/client";
import { useAuth } from "@/contexts/useAuth";
import {
Expand Down Expand Up @@ -105,7 +104,7 @@ export default function Canvas({ roomId }: Props) {
}
};

const replayCanvas = () => {
const replayCanvas = useCallback(() => {
const ctx = getContext();

if (!ctx) return;
Expand All @@ -115,7 +114,7 @@ export default function Canvas({ roomId }: Props) {
for (const event of strokesRef.current) {
drawEvent(ctx, event);
}
};
}, []);

const persistEvent = async (event: WhiteboardEvent) => {
await supabase.from("whiteboard_events" as any).insert({
Expand Down Expand Up @@ -209,7 +208,7 @@ export default function Canvas({ roomId }: Props) {
return () => {
supabase.removeChannel(channel);
};
}, [roomId]);
}, [roomId, user?.id, replayCanvas]);

const getCoordinates = (
e: React.MouseEvent<HTMLCanvasElement>
Expand Down
26 changes: 19 additions & 7 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ const envSchema = z.object({
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: z.string().min(1).optional(),
VITE_VAPID_PUBLIC_KEY: z.string().min(1).optional(),
VITE_API_URL: z.string().url().optional(),
}).refine((data) => {
// Ensure at least one Supabase URL is provided
if (!data.VITE_SUPABASE_URL && !data.NEXT_PUBLIC_SUPABASE_URL) {
return false; // Validation fails
}
// Ensure at least one Supabase Anon Key is provided
if (!data.VITE_SUPABASE_ANON_KEY && !data.VITE_SUPABASE_PUBLISHABLE_KEY && !data.NEXT_PUBLIC_SUPABASE_ANON_KEY && !data.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY) {
return false; // Validation fails
}
return true; // Validation passes
}, {
message: "Supabase URL (VITE_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_URL) and Supabase Anon Key (VITE_SUPABASE_ANON_KEY, VITE_SUPABASE_PUBLISHABLE_KEY, NEXT_PUBLIC_SUPABASE_ANON_KEY, or NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY) are required.",
path: ["SUPABASE_CONFIGURATION"], // Custom path for the error message
});

const _env = envSchema.safeParse(import.meta.env);
Expand All @@ -20,10 +33,9 @@ if (!_env.success) {

export const env = _env.data;

export const supabaseUrl = env.VITE_SUPABASE_URL || env.NEXT_PUBLIC_SUPABASE_URL || "";
export const supabaseAnonKey =
env.VITE_SUPABASE_ANON_KEY ||
env.VITE_SUPABASE_PUBLISHABLE_KEY ||
env.NEXT_PUBLIC_SUPABASE_ANON_KEY ||
env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY ||
"";
export const supabaseUrl: string = env.VITE_SUPABASE_URL ?? env.NEXT_PUBLIC_SUPABASE_URL!;
export const supabaseAnonKey: string =
env.VITE_SUPABASE_ANON_KEY ??
env.VITE_SUPABASE_PUBLISHABLE_KEY ??
env.NEXT_PUBLIC_SUPABASE_ANON_KEY ??
env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!;
Loading
Loading