Making progress on rewrite. Looking into supabase cache helpers.

This commit is contained in:
2025-06-23 16:59:02 -05:00
parent 63574f0729
commit 13cf089870
21 changed files with 740 additions and 13 deletions

View File

@@ -0,0 +1,4 @@
export { useIsMobile } from './use-mobile';
export { ReactQueryClientProvider } from './use-query';
export { ThemeProvider, ThemeToggle } from './use-theme';
export { TVModeProvider, useTVMode, TVToggle } from './use-tv-mode';

View File

@@ -0,0 +1,11 @@
'use client';
import React, {
type ReactNode,
createContext,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from 'react';

View File

@@ -0,0 +1,23 @@
import * as React from 'react';
const MOBILE_BREAKPOINT = 768;
const useIsMobile = () => {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
undefined,
);
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
};
mql.addEventListener('change', onChange);
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
return () => mql.removeEventListener('change', onChange);
}, []);
return !!isMobile;
};
export { useIsMobile };

View File

@@ -0,0 +1,22 @@
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
const ReactQueryClientProvider = ({ children }: { children: React.ReactNode }) => {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
})
)
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
};
export { ReactQueryClientProvider };

View File

@@ -0,0 +1,69 @@
'use client';
import * as React from 'react';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui';
const ThemeProvider = ({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) => {
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
};
type ThemeToggleProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
size?: number;
};
const ThemeToggle = ({ size = 1, ...props }: ThemeToggleProps) => {
const { setTheme, resolvedTheme } = useTheme();
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return (
<Button variant='outline' size='icon' {...props}>
<span style={{ height: `${size}rem`, width: `${size}rem` }} />
</Button>
);
}
const toggleTheme = () => {
if (resolvedTheme === 'dark') setTheme('light');
else setTheme('dark');
};
return (
<Button
variant='outline'
size='icon'
className='cursor-pointer'
onClick={toggleTheme}
{...props}
>
<Sun
style={{ height: `${size}rem`, width: `${size}rem` }}
className='rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0'
/>
<Moon
style={{ height: `${size}rem`, width: `${size}rem` }}
className='absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100'
/>
<span className='sr-only'>Toggle theme</span>
</Button>
);
};
export { ThemeProvider, ThemeToggle };

View File

@@ -0,0 +1,78 @@
'use client';
import Image from 'next/image';
import React, { createContext, useContext, useState } from 'react';
import type { ReactNode } from 'react';
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);
const TVModeProvider = ({ children }: { children: ReactNode }) => {
const [tvMode, setTVMode] = useState(false);
const toggleTVMode = () => {
setTVMode((prev) => !prev);
};
return (
<TVModeContext.Provider value={{ tvMode, toggleTVMode }}>
{children}
</TVModeContext.Provider>
);
};
const useTVMode = () => {
const context = useContext(TVModeContext);
if (!context) {
throw new Error('useTVMode must be used within a TVModeProvider');
}
return context;
};
const TVToggle = ({
className = 'my-auto cursor-pointer',
buttonSize = 'default',
buttonVariant = 'link',
imageWidth = 25,
imageHeight = 25,
}: TVToggleProps) => {
const { tvMode, toggleTVMode } = useTVMode();
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>
);
};
export { TVModeProvider, useTVMode, TVToggle };