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 (
{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
);
}
};
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('/');
};
```