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..59841d8 100644 --- a/frontend/app/lib/supabase.ts +++ b/frontend/app/lib/supabase.ts @@ -1,12 +1,18 @@ import { createClient } 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 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 +export const supabase = + isSupabaseConfigured + ? createClient(supabaseUrl!, supabaseKey!, { + auth: { + persistSession: true, + autoRefreshToken: true, + detectSessionInUrl: true, + }, + }) + : null; \ 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