diff --git a/src/App.tsx b/src/App.tsx index c15753e..563d1bd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,6 +36,8 @@ const ResourceHub = React.lazy(() => import("@/pages/ResourceHub")); import BecomeMentor from "./pages/BecomeMentor"; import { useAuth } from "@/contexts/useAuth"; +import CreateSession from "./pages/createSession"; +import SessionDetail from "./pages/SessionDetail"; const queryClient = new QueryClient(); @@ -47,18 +49,15 @@ const WithNav = ({ children }: { children: React.ReactNode }) => ( ); function App() { - const { user } = useAuth(); // ✨ Sparkle Effect useEffect(() => { - const container = document.getElementById("sparkle-container"); if (!container) return; const createSparkle = (x: number, y: number) => { - const sparkle = document.createElement("div"); sparkle.className = "sparkle"; @@ -74,12 +73,10 @@ function App() { }; const handleMouseMove = (e: MouseEvent) => { - for (let i = 0; i < 2; i++) { - createSparkle( e.clientX + Math.random() * 10 - 5, - e.clientY + Math.random() * 10 - 5 + e.clientY + Math.random() * 10 - 5, ); } }; @@ -89,215 +86,199 @@ function App() { return () => { window.removeEventListener("mousemove", handleMouseMove); }; - }, []); return ( - - - - -
- - - - - ) : ( - - ) - } -/> - } /> - - } /> - - } /> - - } - /> - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - } /> - - - - - - - } - /> - - - - - - - } - /> - - {/* UPDATED MESSAGES ROUTE */} - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - } - /> - - - - - } - /> - - } /> - - - - - } - /> - - - - +
+ + + : } + /> + } /> + + } /> + + } /> + + } /> + + } /> + + } /> + + } /> + + } /> + + + + + + + } + /> + + + + + + + } + /> + + + + + + + } + /> + + } /> + + + + + + + } + /> + + + + + + + } + /> + + {/* UPDATED MESSAGES ROUTE */} + + + + + + } + /> + + + + + + + } + /> + + + + + + + } + /> + + + + + + + } + /> + + + + + + + } + /> + + + + + + + } + /> + + + + + } + /> + + + + + } + /> + + } /> + + + + + } + /> + + + -
-
-
); } -export default App; \ No newline at end of file +export default App; diff --git a/src/integrations/supabase/client.ts b/src/integrations/supabase/client.ts index ff09237..752f486 100644 --- a/src/integrations/supabase/client.ts +++ b/src/integrations/supabase/client.ts @@ -14,17 +14,11 @@ const isMisconfigured = export const supabaseMisconfigured = isMisconfigured; - console.log(SUPABASE_URL); console.log(SUPABASE_PUBLISHABLE_KEY); // Import the supabase client like this: // import { supabase } from "@/integrations/supabase/client"; -export const supabase = createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, { - auth: { - storage: localStorage, - persistSession: true, - autoRefreshToken: true, export const supabase = createClient( isMisconfigured ? "https://placeholder.supabase.co" : SUPABASE_URL, isMisconfigured ? "placeholder-key" : SUPABASE_PUBLISHABLE_KEY, @@ -34,5 +28,5 @@ export const supabase = createClient( persistSession: true, autoRefreshToken: true, }, - } -); \ No newline at end of file + }, +); diff --git a/src/lib/supabase.js b/src/lib/supabase.js index 2cda990..cfd2e8a 100644 --- a/src/lib/supabase.js +++ b/src/lib/supabase.js @@ -1,7 +1,7 @@ -import { createClient } from '@supabase/supabase-js' +import { createClient } from "@supabase/supabase-js"; -const supabaseUrl = import.meta.env.VITE_SUPABASE_URL -const supabaseKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; +const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY; -export const supabase = createClient(supabaseUrl, supabaseKey) -export { supabase } from "@/integrations/supabase/client"; +export const supabase = createClient(supabaseUrl, supabaseKey); +// export { supabase } from "@/integrations/supabase/client"; diff --git a/src/pages/Landing.tsx b/src/pages/Landing.tsx index 7c4d006..869d280 100644 --- a/src/pages/Landing.tsx +++ b/src/pages/Landing.tsx @@ -109,7 +109,8 @@ export default function Landing() { initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.8 }} -className="relative min-h-screen overflow-x-hidden bg-gradient-to-br from-[#020617] via-[#071127] to-[#020B1F] text-white" > + className="relative min-h-screen overflow-x-hidden bg-gradient-to-br from-[#020617] via-[#071127] to-[#020B1F] text-white" + > {/* Animated Background */}
@@ -156,10 +157,10 @@ className="relative min-h-screen overflow-x-hidden bg-gradient-to-br from-[#0206 /> ))} - - {/* Navbar */} -
-
- © 2026 PeerLearn -
+
© 2026 PeerLearn
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index a3ba080..1336c96 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,4 +1,7 @@ -import { supabase, supabaseMisconfigured } from "@/integrations/supabase/client"; +import { + supabase, + supabaseMisconfigured, +} from "@/integrations/supabase/client"; import { useState } from "react"; import { Link, useNavigate, Navigate } from "react-router-dom"; import { motion } from "framer-motion"; @@ -53,6 +56,7 @@ const Login = () => { email, password, }); + console.log("LOGIN ERROR:", error); setIsLoading(false); @@ -72,31 +76,31 @@ const Login = () => { }; const handleGoogleLogin = async () => { - if (supabaseMisconfigured) { - toast({ - title: "Not configured", - description: - "Supabase environment variables are not set. Ask the project owner to configure VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY.", - variant: "destructive", - }); - return; - } + if (supabaseMisconfigured) { + toast({ + title: "Not configured", + description: + "Supabase environment variables are not set. Ask the project owner to configure VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY.", + variant: "destructive", + }); + return; + } - const { error } = await supabase.auth.signInWithOAuth({ - provider: "google", - options: { - redirectTo: `${window.location.origin}/dashboard`, - }, - }); - - if (error) { - toast({ - title: "Google login failed", - description: error.message, - variant: "destructive", + const { error } = await supabase.auth.signInWithOAuth({ + provider: "google", + options: { + redirectTo: `${window.location.origin}/dashboard`, + }, }); - } -}; + + if (error) { + toast({ + title: "Google login failed", + description: error.message, + variant: "destructive", + }); + } + }; if (loading) { return ( @@ -108,7 +112,6 @@ const Login = () => { return (
- {/* GRID BACKGROUND */}
@@ -124,7 +127,6 @@ const Login = () => { transition={{ duration: 0.8 }} className="max-w-xl" > -
✨ Student Powered Learning Ecosystem
@@ -144,9 +146,9 @@ const Login = () => {

- Join live mentorship sessions, collaborate with classmates, - solve doubts instantly, and become part of a futuristic - collaborative learning community. + Join live mentorship sessions, collaborate with classmates, solve + doubts instantly, and become part of a futuristic collaborative + learning community.

@@ -180,18 +182,15 @@ const Login = () => { {/* RIGHT LOGIN CARD */}
- - {/* LOGO */}
-
@@ -201,9 +200,7 @@ const Login = () => { -

- Welcome Back -

+

Welcome Back

Continue your futuristic learning journey @@ -212,7 +209,6 @@ const Login = () => { {/* FORM */}

-
{ /> {errors.email && ( -

- {errors.email} -

+

{errors.email}

)}
@@ -247,9 +241,7 @@ const Login = () => {
{errors.password && ( -

- {errors.password} -

+

{errors.password}

)}
@@ -259,9 +251,7 @@ const Login = () => { onCheckedChange={(c) => setRememberMe(!!c)} /> - +
{
{/* LOGIN BUTTON */} - + @@ -321,4 +307,4 @@ const Login = () => { ); }; -export default Login; \ No newline at end of file +export default Login; diff --git a/src/pages/SessionDetail.tsx b/src/pages/SessionDetail.tsx new file mode 100644 index 0000000..56e1186 --- /dev/null +++ b/src/pages/SessionDetail.tsx @@ -0,0 +1,63 @@ +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import { supabase } from "@/integrations/supabase/client"; +import { useAuth } from "@/contexts/useAuth"; + +const SessionDetail = () => { + const { id } = useParams(); + const { user } = useAuth(); + + const [session, setSession] = useState(null); + + useEffect(() => { + fetchSession(); + }, []); + + const fetchSession = async () => { + const { data } = await supabase + .from("sessions") + .select("*") + .eq("id", id) + .single(); + + setSession(data); + }; + + const joinSession = async () => { + const { error } = await supabase.from("session_participants").insert({ + session_id: id, + user_id: user?.id, + }); + + if (error) { + alert("Already joined"); + return; + } + + alert("Joined 🚀"); + }; + + if (!session) return

Loading...

; + + return ( +
+

{session.title}

+ +

{session.description}

+ +
+ Seats: + {session.participants}/{session.seats} +
+ + +
+ ); +}; + +export default SessionDetail; diff --git a/src/pages/Sessions.tsx b/src/pages/Sessions.tsx index 3e4e38f..9c88763 100644 --- a/src/pages/Sessions.tsx +++ b/src/pages/Sessions.tsx @@ -1,437 +1,349 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import { motion } from "framer-motion"; +import { Calendar, Clock, Users, Send, Plus, Search } from "lucide-react"; -import { - Calendar, - Users, - Clock, - Flame, - Send, - Search, - Sparkles, -} from "lucide-react"; - +import { useNavigate } from "react-router-dom"; import { supabase } from "@/integrations/supabase/client"; import { useAuth } from "@/contexts/useAuth"; -const tabs = [ - "Upcoming", - "Joined", - "Completed", -]; +const tabs = ["Upcoming", "Joined", "Completed"]; const Sessions = () => { const { user } = useAuth(); + const navigate = useNavigate(); const [sessions, setSessions] = useState([]); - const [filteredSessions, setFilteredSessions] = - useState([]); + const [filteredSessions, setFilteredSessions] = useState([]); + const [selectedSession, setSelectedSession] = useState(null); const [messages, setMessages] = useState([]); - - const [selectedTab, setSelectedTab] = - useState("Upcoming"); - - const [selectedSession, setSelectedSession] = - useState(null); - const [message, setMessage] = useState(""); const [search, setSearch] = useState(""); + const [selectedTab, setSelectedTab] = useState("Upcoming"); + + const [showCreate, setShowCreate] = useState(false); const messagesEndRef = useRef(null); - // FETCH SESSIONS - useEffect(() => { - const fetchSessions = async () => { - const { data, error } = await (supabase as any) - .from("sessions") - .select("*") - .order("created_at", { - ascending: false, - }); - - if (error) { - console.log(error); - } else { - setSessions(data); - - if (data.length > 0) { - setSelectedSession(data[0]); - } - } - }; + const [form, setForm] = useState({ + title: "", + description: "", + timing: "", + duration: "", + seats: 20, + }); + + // ---------------- FETCH SESSIONS ---------------- + + const fetchSessions = async () => { + const { data, error } = await supabase + .from("sessions") + .select("*") + .order("created_at", { ascending: false }); + + if (error) { + console.log(error); + return; + } + + setSessions(data || []); + + if (data?.length) { + setSelectedSession(data[0]); + } + }; + useEffect(() => { fetchSessions(); }, []); - // FILTER SESSIONS - useEffect(() => { - let filtered = sessions; + // ---------------- FILTER ---------------- - // TAB FILTER - filtered = filtered.filter( - (s) => - s.status?.toLowerCase() === - selectedTab.toLowerCase() + useEffect(() => { + let filtered = sessions.filter( + (s) => s.status?.toLowerCase() === selectedTab.toLowerCase(), ); - // SEARCH if (search) { - filtered = filtered.filter( - (s) => - s.title - ?.toLowerCase() - .includes(search.toLowerCase()) || - s.tags - ?.join(" ") - .toLowerCase() - .includes(search.toLowerCase()) + filtered = filtered.filter((s) => + s.title?.toLowerCase().includes(search.toLowerCase()), ); } setFilteredSessions(filtered); }, [sessions, selectedTab, search]); - // FETCH MESSAGES + // ---------------- CREATE SESSION ---------------- + + const createSession = async () => { + const { error } = await supabase.from("sessions").insert({ + ...form, + mentor: user?.email, + participants: 0, + status: "Upcoming", + }); + + if (error) { + console.log(error); + return; + } + + setShowCreate(false); + fetchSessions(); + }; + + // ---------------- JOIN SESSION ---------------- + + const joinSession = async (session: any, e: React.MouseEvent) => { + e.stopPropagation(); + + if (!user) { + alert("Login first"); + return; + } + + if (session.participants >= session.seats) { + alert("Session full"); + return; + } + + const { error } = await supabase.from("session_participants").insert({ + session_id: session.id, + user_id: user.id, + }); + + if (error) { + alert("Already joined"); + return; + } + + await supabase + .from("sessions") + .update({ + participants: session.participants + 1, + }) + .eq("id", session.id); + + fetchSessions(); + + alert("Joined 🚀"); + }; + + // ---------------- CHAT ---------------- + useEffect(() => { if (!selectedSession) return; const fetchMessages = async () => { - const { data } = await (supabase as any) + const { data } = await supabase .from("messages") .select("*") .eq("session_id", selectedSession.id) - .order("created_at", { - ascending: true, - }); + .order("created_at", { ascending: true }); setMessages(data || []); }; fetchMessages(); - - // REALTIME - const channel = supabase - .channel("realtime-messages") - .on( - "postgres_changes", - { - event: "INSERT", - schema: "public", - table: "messages", - }, - (payload: any) => { - if ( - payload.new.session_id === - selectedSession.id - ) { - setMessages((prev) => [ - ...prev, - payload.new, - ]); - } - } - ) - .subscribe(); - - return () => { - supabase.removeChannel(channel); - }; }, [selectedSession]); - // AUTO SCROLL useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth", }); }, [messages]); - // SEND MESSAGE const sendMessage = async () => { if (!message.trim()) return; - await (supabase as any) - .from("messages") - .insert({ - session_id: selectedSession.id, - user_id: user?.id, - username: - user?.user_metadata?.full_name || - "Anonymous", - message, - }); + await supabase.from("messages").insert({ + session_id: selectedSession.id, + user_id: user?.id, + username: user?.email, + message, + }); setMessage(""); }; return ( -
- - {/* BACKGROUND */} -
-
- -
- - {/* HEADER */} -
-

- Sessions -

- -

- Join collaborative learning sessions 🚀 -

-
+
+ {/* HEADER */} + +
+

Sessions

+ + +
+ + {/* SEARCH */} + +
+ + + setSearch(e.target.value)} + className="w-full p-3 pl-12 bg-black rounded" + /> +
+ + {/* CREATE */} + + {showCreate && ( +
+ + setForm({ + ...form, + title: e.target.value, + }) + } + /> - {/* SEARCH */} -
- + setForm({ + ...form, + description: e.target.value, + }) + } /> - setSearch(e.target.value) + setForm({ + ...form, + timing: e.target.value, + }) } - className="w-full bg-white/5 border border-white/10 backdrop-blur-xl rounded-2xl py-4 pl-14 pr-4 outline-none focus:border-cyan-400 transition" /> -
- {/* TABS */} -
- {tabs.map((tab) => ( - - ))} + + setForm({ + ...form, + duration: e.target.value, + }) + } + /> + + + setForm({ + ...form, + seats: Number(e.target.value), + }) + } + /> + +
+ )} + + {/* TABS */} + +
+ {tabs.map((tab) => ( + + ))} +
- {/* CONTENT */} -
- - {/* LEFT SIDE */} -
- - {/* TITLE */} -
- - -

- Live Learning Sessions -

-
- - {/* SESSIONS */} -
- {filteredSessions.length > 0 ? ( - filteredSessions.map((s) => ( - - setSelectedSession(s) - } - className={`cursor-pointer rounded-3xl p-6 border backdrop-blur-xl transition-all ${ - selectedSession?.id === s.id - ? "border-cyan-400 bg-cyan-500/10" - : "border-white/10 bg-white/5 hover:border-cyan-400/30" - }`} - > - {/* LIVE BADGE */} - {s.is_live && ( -
- 🔴 LIVE NOW -
- )} - - {/* TITLE */} -

- {s.title} -

- - {/* DESCRIPTION */} -

- {s.description} -

- - {/* DETAILS */} -
- -
- - {s.timing || "Today"} -
- -
- - {s.participants || 0} learners -
- -
- - {s.duration || "1 Hour"} -
-
- - {/* MENTOR */} -
-

- Mentor -

- -

- {s.mentor || "Sarah"} -

-
- - {/* TAGS */} -
- {s.tags?.map( - ( - tag: string, - index: number - ) => ( - - {tag} - - ) - )} -
- - {/* BUTTON */} - -
- )) - ) : ( -
- - -

- No Sessions Found -

- -

- Try another tab or search. -

-
- )} -
-
+
+ {/* SESSION LIST */} + +
+ {filteredSessions.map((session) => ( + navigate(`/sessions/${session.id}`)} + className="bg-white/5 p-6 rounded mb-5 cursor-pointer" + > +

{session.title}

- {/* RIGHT SIDE CHAT */} -
+

{session.description}

- {/* CHAT HEADER */} -
-

- Session Chat 💬 -

+
+ + + {session.timing} + -

- {selectedSession?.title} -

+ + + {session.duration} + -
- 🟢 42 learners online + + + {session.participants}/{session.seats} +
-
- - {/* MESSAGES */} -
- {messages.map((msg, index) => { - const isCurrentUser = - msg.user_id === user?.id; - - return ( -
-
- {!isCurrentUser && ( -

- {msg.username} -

- )} - -

{msg.message}

- -

- {new Date( - msg.created_at - ).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - })} -

-
-
- ); - })} - -
-
- - {/* INPUT */} -
- - setMessage(e.target.value) - } - onKeyDown={(e) => { - if (e.key === "Enter") { - sendMessage(); - } - }} - className="flex-1 bg-white/10 border border-white/10 rounded-2xl px-4 py-3 outline-none focus:border-cyan-400" - /> -
+ + ))} +
+ + {/* CHAT */} + +
+

Session Chat

+ +
+ {messages.map((m) => ( +
+ {m.username} + +

{m.message}

+
+ ))} + +
+
+ +
+ setMessage(e.target.value)} + className="flex-1 bg-black p-3" + /> + +
@@ -439,4 +351,4 @@ const Sessions = () => { ); }; -export default Sessions; \ No newline at end of file +export default Sessions; diff --git a/src/pages/Signup.tsx b/src/pages/Signup.tsx index c239b2e..c471784 100644 --- a/src/pages/Signup.tsx +++ b/src/pages/Signup.tsx @@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useAuth } from "@/contexts/useAuth"; import { useToast } from "@/hooks/use-toast"; -import { supabase } from "@/lib/supabase"; +import { supabase } from "@/integrations/supabase/client"; // ✅ Proper TypeScript type type FormErrors = { @@ -61,6 +61,7 @@ const Signup = () => { setIsLoading(true); const { error } = await signUp(email, password, name); + console.log("SIGNUP ERROR:", error); setIsLoading(false); @@ -79,7 +80,6 @@ const Signup = () => { console.log("INSERT ERROR:", insertError); }*/ - /* if (error) { setIsLoading(false); toast({ @@ -128,7 +128,6 @@ const Signup = () => { return (
- {/* Glow background */}
@@ -138,7 +137,6 @@ const Signup = () => { transition={{ duration: 0.5 }} className="relative w-full max-w-md backdrop-blur-xl bg-white/5 border border-white/10 rounded-2xl p-8 shadow-[0_0_40px_rgba(34,197,94,0.15)]" > - {/* Logo */}
@@ -160,7 +158,6 @@ const Signup = () => { {/* Form */}
- {/* Name */} { onChange={(e) => setEmail(e.target.value)} className="bg-white/5 border border-white/10 text-emerald-100 placeholder:text-emerald-400/50 focus:border-green-400 focus:ring-1 focus:ring-green-400" /> - {errors.email &&

{errors.email}

} + {errors.email && ( +

{errors.email}

+ )} {/* Password */}
@@ -197,7 +196,9 @@ const Signup = () => { {showPassword ? : }
- {errors.password &&

{errors.password}

} + {errors.password && ( +

{errors.password}

+ )} {/* Confirm Password */}
@@ -212,18 +213,14 @@ const Signup = () => {
- {errors.confirmPassword &&

{errors.confirmPassword}

} + {errors.confirmPassword && ( +

{errors.confirmPassword}

+ )} {/* Button */} @@ -235,7 +232,6 @@ const Signup = () => { {isLoading ? "Creating..." : "Sign Up"} -
{/* Login redirect */} @@ -245,10 +241,9 @@ const Signup = () => { Log in

-
); }; -export default Signup; \ No newline at end of file +export default Signup; diff --git a/src/pages/createSession.tsx b/src/pages/createSession.tsx new file mode 100644 index 0000000..602e08f --- /dev/null +++ b/src/pages/createSession.tsx @@ -0,0 +1,89 @@ +import { useState } from "react"; +import { supabase } from "@/lib/supabase"; +import { useNavigate } from "react-router-dom"; + +export default function CreateSession() { + const navigate = useNavigate(); + + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [date, setDate] = useState(""); + const [time, setTime] = useState(""); + const [seats, setSeats] = useState(10); + const [category, setCategory] = useState(""); + + const handleCreate = async () => { + const { + data: { user }, + } = await supabase.auth.getUser(); + + const { error } = await supabase.from("sessions").insert([ + { + title, + description, + date, + time, + seats, + category, + mentor_id: user?.id, + }, + ]); + + if (!error) { + alert("Session created"); + navigate("/sessions"); + } + + console.log(error); + }; + + return ( +
+

Create Session

+ + setTitle(e.target.value)} + className="border p-2 w-full mb-4" + /> + +