Getting started on Tech Tracker. Added TV Context

This commit is contained in:
2025-06-09 06:59:38 -05:00
parent 5f2d25f9dd
commit cc225fae80
28 changed files with 284 additions and 91 deletions

View File

@ -35,7 +35,7 @@ export const GET = async (request: NextRequest) => {
if (type === 'invite') return redirect('/sign-up');
}
return redirect(
`/?error=${encodeURIComponent(error?.message || 'Unknown error')}`,
`/?error=${encodeURIComponent(error?.message ?? 'Unknown error')}`,
);
}

View File

@ -1,12 +1,12 @@
'use client';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { Loader2 } from 'lucide-react';
const AuthSuccessPage = () => {
const { refreshUserData, isAuthenticated } = useAuth();
const { refreshUserData } = useAuth();
const router = useRouter();
useEffect(() => {

View File

@ -19,7 +19,7 @@ import {
import Link from 'next/link';
import { forgotPassword } from '@/lib/actions';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import { useEffect, useState } from 'react';
import { StatusMessage, SubmitButton } from '@/components/default';

View File

@ -1,5 +1,5 @@
'use client';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import {

View File

@ -20,7 +20,7 @@ import {
import Link from 'next/link';
import { signIn } from '@/lib/actions';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import { useEffect, useState } from 'react';
import { StatusMessage, SubmitButton } from '@/components/default';
import { Separator } from '@/components/ui';

View File

@ -7,7 +7,7 @@ import Link from 'next/link';
import { signUp } from '@/lib/actions';
import { StatusMessage, SubmitButton } from '@/components/default';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import {
Card,
CardContent,
@ -26,7 +26,7 @@ import {
import { useEffect, useState } from 'react';
import {
SignInWithApple,
SignInWithMicrosoft
SignInWithMicrosoft,
} from '@/components/default/auth';
const formSchema = z
@ -201,8 +201,8 @@ const SignUp = () => {
<span className='text-sm text-muted-foreground'>or</span>
<Separator className='flex-1 bg-accent py-0.5' />
</div>
<SignInWithMicrosoft type='signUp' />
<SignInWithApple type='signUp' />
<SignInWithMicrosoft />
<SignInWithApple />
</CardContent>
</Card>
);

View File

@ -2,8 +2,7 @@
import '@/styles/globals.css';
import { cn } from '@/lib/utils';
import { ThemeProvider } from '@/components/context/theme';
import { AuthProvider } from '@/components/context/auth';
import { AuthProvider, ThemeProvider } from '@/components/context';
import Navigation from '@/components/default/navigation';
import Footer from '@/components/default/footer';
import { Button, Toaster } from '@/components/ui';
@ -60,21 +59,6 @@ const GlobalError = ({ error, reset = undefined }: GlobalErrorProps) => {
</body>
</html>
);
return (
<html lang='en'>
<body>
{/* `NextError` is the default Next.js error page component. Its type
definition requires a `statusCode` prop. However, since the App Router
does not expose status codes for errors, we simply pass 0 to render a
generic error message. */}
<NextError statusCode={0} />
{reset !== undefined && (
<Button onClick={() => reset()}>Try again</Button>
)}
</body>
</html>
);
};
export default GlobalError;

View File

@ -2,8 +2,11 @@ import type { Metadata } from 'next';
import '@/styles/globals.css';
import { Geist } from 'next/font/google';
import { cn } from '@/lib/utils';
import { ThemeProvider } from '@/components/context/theme';
import { AuthProvider } from '@/components/context/auth';
import {
AuthProvider,
ThemeProvider,
TVModeProvider,
} from '@/components/context';
import Navigation from '@/components/default/navigation';
import Footer from '@/components/default/footer';
import { Toaster } from '@/components/ui';
@ -357,19 +360,21 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
disableTransitionOnChange
>
<AuthProvider>
<main className='min-h-screen flex flex-col items-center'>
<div className='flex-1 w-full flex flex-col gap-20 items-center'>
<Navigation />
<div
className='flex flex-col gap-20 max-w-5xl
p-5 w-full items-center'
>
{children}
<TVModeProvider>
<main className='min-h-screen flex flex-col items-center'>
<div className='flex-1 w-full flex flex-col gap-20 items-center'>
<Navigation />
<div
className='flex flex-col gap-20 max-w-5xl
p-5 w-full items-center'
>
{children}
</div>
</div>
</div>
<Footer />
</main>
<Toaster />
<Footer />
</main>
<Toaster />
</TVModeProvider>
</AuthProvider>
</ThemeProvider>
</body>

View File

@ -16,7 +16,7 @@ import {
import {
SignInSignUp,
SignInWithApple,
SignInWithMicrosoft
SignInWithMicrosoft,
} from '@/components/default/auth';
const HomePage = async () => {

View File

@ -110,7 +110,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange(async (event, session) => {
} = supabase.auth.onAuthStateChange(async (event, _session) => {
console.log('Auth state change:', event); // Debug log
if (event === 'SIGNED_IN') {

View File

@ -0,0 +1,79 @@
'use client';
import Image from 'next/image';
import React, { createContext, useContext, useState } from 'react';
import type { ReactNode } from 'react';
import { useAuth } from '@/components/context';
import { Button, type buttonVariants } from '@/components/ui';
import { type ComponentProps } from 'react';
import { type VariantProps } from 'class-variance-authority';
type TVModeContextProps = {
tvMode: boolean;
toggleTVMode: () => void;
};
type TVToggleProps = {
className?: ComponentProps<'button'>['className'];
buttonSize?: VariantProps<typeof buttonVariants>['size'];
buttonVariant?: VariantProps<typeof buttonVariants>['variant'];
imageWidth?: number;
imageHeight?: number;
};
const TVModeContext = createContext<TVModeContextProps | undefined>(undefined);
export const TVModeProvider = ({ children }: { children: ReactNode }) => {
const [tvMode, setTVMode] = useState(false);
const toggleTVMode = () => {
setTVMode((prev) => !prev);
};
return (
<TVModeContext.Provider value={{ tvMode, toggleTVMode }}>
{children}
</TVModeContext.Provider>
);
};
export const useTVMode = () => {
const context = useContext(TVModeContext);
if (!context) {
throw new Error('useTVMode must be used within a TVModeProvider');
}
return context;
};
export const TVToggle = ({
className = 'my-auto cursor-pointer',
buttonSize = 'default',
buttonVariant = 'link',
imageWidth = 25,
imageHeight = 25,
}: TVToggleProps) => {
const { tvMode, toggleTVMode } = useTVMode();
const { isAuthenticated } = useAuth();
if (!isAuthenticated) return <div />;
return (
<Button
onClick={toggleTVMode}
className={className}
size={buttonSize}
variant={buttonVariant}
>
{tvMode ? (
<Image
src='/icons/tv/exit.svg'
alt='Exit TV Mode'
width={imageWidth}
height={imageHeight}
/>
) : (
<Image
src='/icons/tv/enter.svg'
alt='Exit TV Mode'
width={imageWidth}
height={imageHeight}
/>
)}
</Button>
);
};

View File

@ -20,10 +20,9 @@ export const ThemeProvider = ({
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
};
export interface ThemeToggleProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
type ThemeToggleProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
size?: number;
}
};
export const ThemeToggle = ({ size = 1, ...props }: ThemeToggleProps) => {
const { setTheme, resolvedTheme } = useTheme();

View File

@ -0,0 +1,3 @@
export { AuthProvider, useAuth } from './Auth';
export { ThemeProvider, ThemeToggle } from './Theme';
export { TVModeProvider, useTVMode, TVToggle } from './TVMode';

View File

@ -1,7 +1,7 @@
'use client';
import { signInWithApple } from '@/lib/actions';
import { StatusMessage, SubmitButton } from '@/components/default';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import Image from 'next/image';
@ -37,7 +37,7 @@ export const SignInWithApple = ({
// Redirect to Apple OAuth page
window.location.href = result.data;
} else {
setStatusMessage(`Error: ${result.error}`);
setStatusMessage(`Error signing in with Apple!`);
}
} catch (error) {
setStatusMessage(

View File

@ -1,7 +1,7 @@
'use client';
import { signInWithMicrosoft } from '@/lib/actions';
import { StatusMessage, SubmitButton } from '@/components/default';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import { useState } from 'react';
import Image from 'next/image';
import { type buttonVariants } from '@/components/ui';
@ -35,7 +35,7 @@ export const SignInWithMicrosoft = ({
// Redirect to Microsoft OAuth page
window.location.href = result.data;
} else {
setStatusMessage(`Error: ${result.error}`);
setStatusMessage(`Error: Could not sign in with Microsoft!`);
}
} catch (error) {
setStatusMessage(

View File

@ -12,7 +12,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import { useRouter } from 'next/navigation';
import { signOut } from '@/lib/actions';
import { User } from 'lucide-react';

View File

@ -3,7 +3,7 @@
import Link from 'next/link';
import { Button } from '@/components/ui';
import NavigationAuth from './auth';
import { ThemeToggle } from '@/components/context/theme';
import { ThemeToggle, TVToggle } from '@/components/context';
import Image from 'next/image';
const Navigation = () => {
@ -30,6 +30,7 @@ const Navigation = () => {
</div>
</div>
<div className='flex items-center gap-2'>
<TVToggle />
<ThemeToggle />
<NavigationAuth />
</div>

View File

@ -1,5 +1,5 @@
import { useFileUpload } from '@/lib/hooks/useFileUpload';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import {
Avatar,
AvatarFallback,

View File

@ -13,7 +13,7 @@ import {
Input,
} from '@/components/ui';
import { useEffect } from 'react';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import { SubmitButton } from '@/components/default';
const formSchema = z.object({

View File

@ -122,18 +122,15 @@ export const ResetPasswordForm = ({
</FormItem>
)}
/>
{statusMessage && (
<div
className={`text-sm text-center ${
statusMessage.includes('Error') ||
statusMessage.includes('failed')
? 'text-destructive'
: 'text-green-600'
}`}
>
{statusMessage}
</div>
)}
{statusMessage &&
(statusMessage.includes('Error') ||
statusMessage.includes('error') ||
statusMessage.includes('failed') ||
statusMessage.includes('invalid') ? (
<StatusMessage message={{ error: statusMessage }} />
) : (
<StatusMessage message={{ message: statusMessage }} />
))}
<div className='flex justify-center'>
<SubmitButton
disabled={isLoading}

View File

@ -3,7 +3,7 @@
import { CardHeader } from '@/components/ui';
import { SubmitButton } from '@/components/default';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import { signOut } from '@/lib/actions';
export const SignOut = () => {

View File

@ -149,7 +149,7 @@ export const getUser = async (): Promise<Result<User>> => {
const { data, error } = await supabase.auth.getUser();
if (error) throw error;
return { success: true, data: data.user };
} catch (error) {
return { success: false, error: 'Could not get user!' };
} catch {
return { success: false, error: `Could not get user!` };
}
};

View File

@ -1,6 +1,5 @@
export * from './auth';
export * from './public';
//export * from './resizeImage';
export * from './storage';
export * from './useFileUpload';

View File

@ -3,7 +3,7 @@
import { useState, useRef } from 'react';
import { replaceFile, uploadFile } from '@/lib/hooks';
import { toast } from 'sonner';
import { useAuth } from '@/components/context/auth';
import { useAuth } from '@/components/context';
import { resizeImage } from '@/lib/hooks';
import type { Result } from '.';