src/app/layout.tsx ```tsx import '@/styles/globals.css'; import { GeistSans } from 'geist/font/sans'; import { cn } from '@/lib/utils'; import { ThemeProvider } from '@/components/context/Theme'; import { TVModeProvider } from '@/components/context/TVMode'; import { createClient } from '@/utils/supabase/server'; import LoginForm from '@/components/auth/LoginForm'; import Header from '@/components/defaults/Header'; import { type Metadata } from 'next'; export const metadata: Metadata = { title: 'Tech Tracker', description: 'App used by COG IT employees to \ update their status throughout the day.', icons: [ { rel: 'icon', url: '/favicon.ico', }, { rel: 'icon', type: 'image/png', sizes: '32x32', url: '/images/tech_tracker_favicon.png', }, { rel: 'apple-touch-icon', url: '/imges/tech_tracker_appicon.png', }, ], }; const RootLayout = async ({ children, }: Readonly<{ children: React.ReactNode }>) => { return (
{children}
); }; export default RootLayout; ``` src/components/auth/AvatarDropdown.tsx ```tsx 'use client'; import { useState, useEffect } from 'react'; import Image from 'next/image'; import { DropdownMenu, DropdownMenuContent, DropdownMenuLabel, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { createClient } from '@/utils/supabase/client'; import type { Session } from '@supabase/supabase-js'; import { Button } from '@/components/ui/button'; import type { User } from '@/lib/types'; import { getImageUrl } from '@/server/actions/image'; import { useRouter } from 'next/navigation'; const AvatarDropdown = () => { const supabase = createClient(); const router = useRouter(); const [session, setSession] = useState(null); const [loading, setLoading] = useState(true); const [user, setUser] = useState(null); const [pfp, setPfp] = useState('/images/default_user_pfp.png'); useEffect(() => { // Function to fetch the session async function fetchSession() { try { const { data: { session }, } = await supabase.auth.getSession(); setSession(session); if (session?.user?.id) { const { data: userData, error } = await supabase .from('profiles') .select('*') .eq('id', session?.user.id) .single(); if (error) { console.error('Error fetching user data:', error); return; } if (userData) { const user = userData as User; console.log(user); setUser(user); } } } catch (error) { console.error('Error fetching session:', error); } finally { setLoading(false); } } // Call the function fetchSession().catch((error) => { console.error('Error fetching session:', error); }); // Set up auth state change listener const { data: { subscription }, } = supabase.auth.onAuthStateChange((_event, session) => { setSession(session); }); // Clean up the subscription when component unmounts return () => { subscription.unsubscribe(); }; }, [supabase]); useEffect(() => { const downloadImage = async (path: string) => { try { setLoading(true); const url = await getImageUrl('avatars', path); console.log(url); setPfp(url); } catch (error) { console.error( 'Error downloading image:', error instanceof Error ? error.message : error, ); } finally { setLoading(false); } }; if (user?.avatar_url) { try { downloadImage(user.avatar_url).catch((error) => { console.error('Error downloading image:', error); }); } catch (error) { console.error('Error: ', error); } } }, [user, supabase]); const getInitials = (fullName: string | undefined): string => { if (!fullName || fullName.trim() === '' || fullName === 'undefined') return 'NA'; const nameParts = fullName.trim().split(' '); const firstInitial = nameParts[0]?.charAt(0).toUpperCase() ?? 'N'; if (nameParts.length === 1) return 'NA'; const lastIntitial = nameParts[nameParts.length - 1]?.charAt(0).toUpperCase() ?? 'A'; return firstInitial + lastIntitial; }; // Handle sign out const handleSignOut = async () => { await supabase.auth.signOut(); router.push('/'); }; // Show nothing while loading if (loading) { return
; } // If no session, return empty div if (!session) return
; return (
{getInitials(user?.full_name) {user?.full_name}
); }; export default AvatarDropdown; ``` src/components/context/TVMode.tsx ```tsx 'use client'; import React, { createContext, useContext, useState } from 'react'; import Image from 'next/image'; import type { ReactNode } from 'react'; interface TVModeContextProps { tvMode: boolean; toggleTVMode: () => void; } const TVModeContext = createContext(undefined); export const TVModeProvider = ({ children }: { children: ReactNode }) => { const [tvMode, setTVMode] = useState(false); const toggleTVMode = () => { setTVMode((prev) => !prev); }; return ( {children} ); }; export const useTVMode = () => { const context = useContext(TVModeContext); if (!context) { throw new Error('useTVMode must be used within a TVModeProvider'); } return context; }; type TVToggleProps = { width?: number; height?: number; }; export const TVToggle = ({ width = 25, height = 25 }: TVToggleProps) => { const { tvMode, toggleTVMode } = useTVMode(); return ( ); }; ``` src/components/defaults/Header.tsx ```tsx 'use client'; import { useState, useEffect } from 'react'; import Image from 'next/image'; import { TVToggle, useTVMode } from '@/components/context/TVMode'; import { ThemeToggle } from '@/components/context/Theme'; import AvatarDropdown from '@/components/auth/AvatarDropdown'; import { createClient } from '@/utils/supabase/client'; import type { Session } from '@supabase/supabase-js'; const Header = () => { const { tvMode } = useTVMode(); const supabase = createClient(); const [session, setSession] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { // Function to fetch the session async function fetchSession() { try { const { data: { session }, } = await supabase.auth.getSession(); setSession(session); } catch (error) { console.error('Error fetching session:', error); } finally { setLoading(false); } } // Call the function fetchSession().catch((error) => { console.error('Error fetching session:', error); }); // Set up auth state change listener const { data: { subscription }, } = supabase.auth.onAuthStateChange((_event, session) => { setSession(session); }); // Clean up the subscription when component unmounts return () => { subscription.unsubscribe(); }; }, [supabase]); if (tvMode) { return (
{session && !loading && (
)}
); } else { return (
{session && !loading && (
)}
Tech Tracker Logo

Tech Tracker

); } }; export default Header; ``` src/server/actions/auth.ts ```ts 'use server'; import 'server-only'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; import { createClient } from '@/utils/supabase/server'; export const login = async (formData: FormData) => { const supabase = await createClient(); // type-casting here for convenience // in practice, you should validate your inputs const data = { email: formData.get('email') as string, password: formData.get('password') as string, }; const { error } = await supabase.auth.signInWithPassword(data); if (error) { redirect('/error'); } revalidatePath('/', 'layout'); redirect('/'); }; export const signup = async (formData: FormData) => { const supabase = await createClient(); // type-casting here for convenience // in practice, you should validate your inputs const data = { fullName: formData.get('fullName') as string, email: formData.get('email') as string, password: formData.get('password') as string, }; const { error } = await supabase.auth.signUp(data); if (error) { redirect('/error'); } revalidatePath('/', 'layout'); redirect('/'); }; ```