From 2d99b5740998817dc2eb5d8546bd480aaa191c57 Mon Sep 17 00:00:00 2001 From: SrashtiChauhan Date: Thu, 28 May 2026 13:49:05 +0530 Subject: [PATCH 1/2] feat: add global notifications center with activity tracking --- frontend/.gitignore | 1 + frontend/app/components/AppShell.tsx | 26 +- .../app/components/NotificationsPanel.tsx | 229 ++++++++++++++++++ .../analytics-comp/AnalyticsHeader.tsx | 6 +- frontend/app/dashboard/page.tsx | 11 +- frontend/app/lib/supabase.ts | 32 ++- frontend/app/login/page.tsx | 48 +++- frontend/middleware.ts | 36 +-- 8 files changed, 325 insertions(+), 64 deletions(-) create mode 100644 frontend/app/components/NotificationsPanel.tsx diff --git a/frontend/.gitignore b/frontend/.gitignore index 7b8da95..fb1b8a7 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -34,6 +34,7 @@ yarn-error.log* .env* !.env.example + # vercel .vercel diff --git a/frontend/app/components/AppShell.tsx b/frontend/app/components/AppShell.tsx index e7f9b39..69bb3b2 100644 --- a/frontend/app/components/AppShell.tsx +++ b/frontend/app/components/AppShell.tsx @@ -1,5 +1,6 @@ "use client"; import CommandPalette from "./CommandPalette"; +import NotificationsPanel from "./NotificationsPanel"; import { usePathname } from "next/navigation"; import Sidebar from "./Sidebar"; @@ -19,14 +20,31 @@ export default function AppShell({ children }: { children: React.ReactNode }) { : "min-w-0 flex-1 overflow-x-hidden p-6"; return ( -
+
{!hideSidebar && } -
- {children} -
+
+ {!isPublicRoute && ( +
+ +
+ )} + +
+ {children} +
+
); } diff --git a/frontend/app/components/NotificationsPanel.tsx b/frontend/app/components/NotificationsPanel.tsx new file mode 100644 index 0000000..dbb489d --- /dev/null +++ b/frontend/app/components/NotificationsPanel.tsx @@ -0,0 +1,229 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { useRouter } from "next/navigation"; + +export default function NotificationsPanel() { + const [open, setOpen] = useState(false); + const router = useRouter(); + + const [notifications, setNotifications] = useState([ + { + id: 1, + title: "Task moved to Review", + description: "Landing page redesign moved to review stage.", + time: "2m ago", + unread: true, + href: "/projects", + }, + { + id: 2, + title: "New chat message", + description: "Alex sent a new team message.", + time: "10m ago", + unread: true, + href: "/chat", + }, + { + id: 3, + title: "Project created", + description: "FlowForge Mobile App project was created.", + time: "1h ago", + unread: false, + href: "/projects", + }, + ]); + const panelRef = useRef(null); + + useEffect(() => { + function handleOutsideClick(event: MouseEvent) { + if ( + panelRef.current && + !panelRef.current.contains(event.target as Node) + ) { + setOpen(false); + } + } + function handleEscape(event: KeyboardEvent) { + if (event.key === "Escape") { + setOpen(false); + } + } + + document.addEventListener("mousedown", handleOutsideClick); + window.addEventListener("keydown", handleEscape); + + return () => { + document.removeEventListener( + "mousedown", + handleOutsideClick + ); + window.removeEventListener( + "keydown", + handleEscape + ); + }; + }, []); + + const unreadCount = notifications.filter( + (notification) => notification.unread + ).length; + + function markAllAsRead() { + setNotifications((prev) => + prev.map((notification) => ({ + ...notification, + unread: false, + })) + ); + } + + return ( +
+ + + {open && ( +
+
+
+

+ Notifications +

+ +

+ Recent activity and updates +

+
+ + +
+ +
+ {notifications.length === 0 ? ( +
+ + notifications_off + + +

+ No notifications yet +

+ +

+ You're all caught up. +

+
+ ) : ( + notifications.map((notification) => ( + + )) + )} +
+
+ )} +
+); +} \ No newline at end of file diff --git a/frontend/app/components/analytics-comp/AnalyticsHeader.tsx b/frontend/app/components/analytics-comp/AnalyticsHeader.tsx index bbb6014..d897491 100644 --- a/frontend/app/components/analytics-comp/AnalyticsHeader.tsx +++ b/frontend/app/components/analytics-comp/AnalyticsHeader.tsx @@ -1,6 +1,7 @@ "use client"; import Link from "next/link"; +import NotificationsPanel from "@/app/components/NotificationsPanel"; type AnalyticsHeaderProps = { sprint: string; @@ -60,9 +61,8 @@ export default function AnalyticsHeader({
- + + diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx index 1300cb3..b0f4fab 100644 --- a/frontend/app/dashboard/page.tsx +++ b/frontend/app/dashboard/page.tsx @@ -80,9 +80,16 @@ export default function Dashboard() { if (!session) { setTeamLoading(false); return; } const { data, error } = await supabase .from("profiles") - .select("name, email") + .select("full_name, email") .limit(10); - if (!error && data) setTeam(data); + if (!error && data) { + setTeam( + data.map((member) => ({ + name: member.full_name || "User", + email: member.email, + })) + ); + } } catch (err) { console.error("Failed to fetch team members:", err); } finally { diff --git a/frontend/app/lib/supabase.ts b/frontend/app/lib/supabase.ts index b45c420..5a9421b 100644 --- a/frontend/app/lib/supabase.ts +++ b/frontend/app/lib/supabase.ts @@ -1,12 +1,26 @@ -import { createClient } from "@supabase/supabase-js"; +import { createClient, SupabaseClient } from "@supabase/supabase-js"; -const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; -const supabaseKey = - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || - process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY; +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; +const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; -export const isSupabaseConfigured = !!supabaseUrl && !!supabaseKey; +export const isSupabaseConfigured = + !!supabaseUrl && !!supabaseKey; -export const supabase = isSupabaseConfigured - ? createClient(supabaseUrl!, supabaseKey!) - : null; \ No newline at end of file +declare global { + // eslint-disable-next-line no-var + var __supabase: SupabaseClient | undefined; +} + +export const supabase = + globalThis.__supabase ?? + createClient(supabaseUrl, supabaseKey, { + auth: { + persistSession: true, + autoRefreshToken: true, + detectSessionInUrl: true, + }, + }); + +if (typeof window !== "undefined") { + globalThis.__supabase = supabase; +} \ No newline at end of file diff --git a/frontend/app/login/page.tsx b/frontend/app/login/page.tsx index d6ecb6e..041d21d 100644 --- a/frontend/app/login/page.tsx +++ b/frontend/app/login/page.tsx @@ -32,29 +32,51 @@ export default function Login() { return; } - setLoading(true); - const { error } = await supabase.auth.signInWithPassword({ - email, - password, - }); - setLoading(false); + setLoading(true); - if (error) showToast(error.message, "error"); - else router.push(redirectPath); - }; + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + + console.log("LOGIN DATA:", data); + console.log("LOGIN ERROR:", error); + + setLoading(false); + + if (error) { + console.error(error); + alert(error.message); + showToast(error.message, "error"); + return; + } + + const { + data: { session }, + } = await supabase.auth.getSession(); + + console.log("SESSION:", session); + + if (session) { + router.push("/dashboard"); + router.refresh(); + } +}; return (
-
+
{ e.preventDefault(); handleLogin(); }} className="bg-zinc-900 p-6 rounded-xl w-80" >

Login

setEmail(e.target.value)} /> - +
+
); } diff --git a/frontend/middleware.ts b/frontend/middleware.ts index 038f5fd..7e1ce27 100644 --- a/frontend/middleware.ts +++ b/frontend/middleware.ts @@ -1,35 +1,5 @@ -import { NextRequest, NextResponse } from "next/server"; - -const protectedRoutes = [ - "/dashboard", - "/projects", - "/workspace", - "/chat", - "/insights", -]; - -export function middleware(req: NextRequest) { - const token = - req.cookies.get("sb-access-token")?.value || - req.headers.get("authorization"); - - const isProtectedRoute = protectedRoutes.some((route) => - req.nextUrl.pathname.startsWith(route) - ); - - if (isProtectedRoute && !token) { - return NextResponse.redirect(new URL("/login", req.url)); - } +import { NextResponse } from "next/server"; +export function middleware() { return NextResponse.next(); -} - -export const config = { - matcher: [ - "/dashboard/:path*", - "/projects/:path*", - "/workspace/:path*", - "/chat/:path*", - "/insights/:path*", - ], -}; \ No newline at end of file +} \ No newline at end of file From cc128a44c2924918f243d35b54c4f30650711024 Mon Sep 17 00:00:00 2001 From: SrashtiChauhan Date: Thu, 28 May 2026 17:07:51 +0530 Subject: [PATCH 2/2] fix: prevent Supabase build crash on Vercel --- frontend/app/lib/supabase.ts | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/frontend/app/lib/supabase.ts b/frontend/app/lib/supabase.ts index 5a9421b..59841d8 100644 --- a/frontend/app/lib/supabase.ts +++ b/frontend/app/lib/supabase.ts @@ -1,26 +1,18 @@ -import { createClient, SupabaseClient } from "@supabase/supabase-js"; +import { createClient } from "@supabase/supabase-js"; -const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; -const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; +const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; export const isSupabaseConfigured = !!supabaseUrl && !!supabaseKey; -declare global { - // eslint-disable-next-line no-var - var __supabase: SupabaseClient | undefined; -} - export const supabase = - globalThis.__supabase ?? - createClient(supabaseUrl, supabaseKey, { - auth: { - persistSession: true, - autoRefreshToken: true, - detectSessionInUrl: true, - }, - }); - -if (typeof window !== "undefined") { - globalThis.__supabase = supabase; -} \ No newline at end of file + isSupabaseConfigured + ? createClient(supabaseUrl!, supabaseKey!, { + auth: { + persistSession: true, + autoRefreshToken: true, + detectSessionInUrl: true, + }, + }) + : null; \ No newline at end of file