Trying to get db schema

This commit is contained in:
Gabriel Brown 2025-05-13 16:06:39 -05:00
parent a542098717
commit 68ba7cc41f
12 changed files with 226 additions and 20 deletions

View File

@ -5,6 +5,15 @@
import './src/env.js'; import './src/env.js';
/** @type {import("next").NextConfig} */ /** @type {import("next").NextConfig} */
const config = {}; const config = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '*.gbrown.org',
},
],
},
};
export default config; export default config;

4
src/actions/image.ts Normal file
View File

@ -0,0 +1,4 @@
'use server';
import 'server-only';
import { createClient } from '@/utils/supabase/server';

View File

@ -4,6 +4,7 @@ import { Geist } from 'next/font/google';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ThemeProvider } from '@/components/context/theme'; import { ThemeProvider } from '@/components/context/theme';
import Navigation from '@/components/navigation'; import Navigation from '@/components/navigation';
import Footer from '@/components/footer';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'T3 Template with Supabase', title: 'T3 Template with Supabase',
@ -46,10 +47,11 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
<main className='min-h-screen flex flex-col items-center'> <main className='min-h-screen flex flex-col items-center'>
<div className='flex-1 w-full flex flex-col gap-20 items-center'> <div className='flex-1 w-full flex flex-col gap-20 items-center'>
<Navigation /> <Navigation />
<div className='flex flex-col gap-20 max-w-5xl p-5'> <div className='flex flex-col gap-20 max-w-5xl p-5 w-full'>
{children} {children}
</div> </div>
</div> </div>
<Footer />
</main> </main>
</ThemeProvider> </ThemeProvider>
</body> </body>

View File

@ -1,13 +1,9 @@
const HomePage = () => { const HomePage = () => {
return ( return (
<main <main className='w-full items-center justify-center'>
className='flex min-h-screen flex-col items-center justify-center <div className='flex p-5 items-center justify-center'>
bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white' <h1>Make sure you can sign in!</h1>
> </div>
<div
className='container flex flex-col items-center justify-center
gap-12 px-4 py-16'
></div>
</main> </main>
); );
}; };

60
src/app/test/page.tsx Normal file
View File

@ -0,0 +1,60 @@
'use server';
import { createClient } from '@/utils/supabase/server';
import Image from 'next/image';
export default async function Page() {
const supabase = await createClient();
// Get authenticated user
const {
data: { user: authUser },
error: userError,
} = await supabase.auth.getUser();
if (userError || !authUser) {
return (
<div>
Error loading user: {userError?.message ?? 'User not authenticated'}
</div>
);
}
// Get user profile
const { data: user, error: profileError } = await supabase
.from('profiles')
.select('*')
.eq('id', authUser.id)
.single();
if (profileError || !user) {
return (
<div>
Error loading profile: {profileError?.message ?? 'Profile not found'}
</div>
);
}
// Check if avatar URL exists
if (!user.avatar_url) {
return <div>No avatar image available</div>;
}
// Get public URL for the avatar
const { data: imageData } = await supabase.storage
.from('avatars')
.createSignedUrl(user.avatar_url, 3600);
return (
<div className='flex justify-center items-center p-8'>
<Image
src={imageData?.signedUrl ?? '/images/default_pfp.png'}
alt='User avatar'
width={150}
height={150}
className='rounded-full'
priority
/>
</div>
);
}

View File

@ -20,7 +20,12 @@ export const ThemeProvider = ({
return <NextThemesProvider {...props}>{children}</NextThemesProvider>; return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}; };
export const ThemeToggle = () => { export interface ThemeToggleProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
size?: number;
}
export const ThemeToggle = ({ size = 1, ...props }: ThemeToggleProps) => {
const { setTheme, resolvedTheme } = useTheme(); const { setTheme, resolvedTheme } = useTheme();
const [mounted, setMounted] = React.useState(false); const [mounted, setMounted] = React.useState(false);
@ -30,8 +35,8 @@ export const ThemeToggle = () => {
if (!mounted) { if (!mounted) {
return ( return (
<Button variant='outline' size='icon'> <Button variant='outline' size='icon' {...props}>
<span className='h-[1.2rem] w-[1.2rem]' /> <span style={{ height: `${size}rem`, width: `${size}rem` }} />
</Button> </Button>
); );
} }
@ -42,14 +47,14 @@ export const ThemeToggle = () => {
}; };
return ( return (
<Button variant='outline' size='icon' onClick={toggleTheme}> <Button variant='outline' size='icon' onClick={toggleTheme} {...props}>
<Sun <Sun
className='h-[1.2rem] w-[1.2rem] rotate-0 scale-100 style={{ height: `${size}rem`, width: `${size}rem` }}
transition-all dark:-rotate-90 dark:scale-0' className='rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0'
/> />
<Moon <Moon
className='absolute h-[1.2rem] w-[1.2rem] rotate-90 style={{ height: `${size}rem`, width: `${size}rem` }}
scale-0 transition-all dark:rotate-0 dark:scale-100' className='absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100'
/> />
<span className='sr-only'>Toggle theme</span> <span className='sr-only'>Toggle theme</span>
</Button> </Button>

View File

@ -0,0 +1,20 @@
'use server';
const FooterTest = () => {
return (
<footer className='w-full flex items-center justify-center border-t mx-auto text-center text-xs gap-8 py-16'>
<p>
Powered by{' '}
<a
href='https://supabase.com/?utm_source=create-next-app&utm_medium=template&utm_term=nextjs'
target='_blank'
className='font-bold hover:underline'
rel='noreferrer'
>
Supabase
</a>
</p>
</footer>
);
};
export default FooterTest;

View File

@ -22,7 +22,7 @@ const NavigationAuth = async () => {
</div> </div>
) : ( ) : (
<div className='flex gap-2'> <div className='flex gap-2'>
<Button asChild size='sm' variant={'outline'}> <Button asChild size='default' variant={'outline'}>
<Link href='/sign-in'>Sign in</Link> <Link href='/sign-in'>Sign in</Link>
</Button> </Button>
<Button asChild size='sm' variant={'default'}> <Button asChild size='sm' variant={'default'}>

View File

@ -3,6 +3,7 @@
import Link from 'next/link'; import Link from 'next/link';
import { Button } from '@/components/ui'; import { Button } from '@/components/ui';
import NavigationAuth from '@/components/navigation/auth'; import NavigationAuth from '@/components/navigation/auth';
import { ThemeToggle } from '@/components/context/theme';
const Navigation = () => { const Navigation = () => {
return ( return (
@ -24,7 +25,10 @@ const Navigation = () => {
</Button> </Button>
</div> </div>
</div> </div>
<NavigationAuth /> <div className='flex items-center gap-2'>
<ThemeToggle />
<NavigationAuth />
</div>
</div> </div>
</nav> </nav>
); );

View File

@ -26,6 +26,7 @@ const buttonVariants = cva(
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9', icon: 'size-9',
smicon: 'size-6',
}, },
}, },
defaultVariants: { defaultVariants: {

0
src/lib/types.ts Normal file
View File

105
src/server/db/schema.sql Normal file
View File

@ -0,0 +1,105 @@
-- Create a table for public profiles
create table profiles (
id uuid references auth.users on delete cascade not null primary key,
updated_at timestamp with time zone,
email text,
full_name text,
avatar_url text,
provider text,
constraint full_name_length check (char_length(full_name) >= 3 and char_length(full_name) <= 50)
);
-- Set up Row Level Security (RLS)
-- See https://supabase.com/docs/guides/auth/row-level-security for more details.
alter table profiles
enable row level security;
create policy "Public profiles are viewable by everyone." on profiles
for select using (true);
create policy "Users can insert their own profile." on profiles
for insert with check ((select auth.uid()) = id);
create policy "Users can update own profile." on profiles
for update using ((select auth.uid()) = id);
-- This trigger automatically creates a profile entry when a new user signs up via Supabase Auth.
-- See https://supabase.com/docs/guides/auth/managing-user-data#using-triggers for more details.
create function public.handle_new_user()
returns trigger
set search_path = ''
as $$
begin
insert into public.profiles (id, email, full_name, avatar_url, provider, updated_at)
values (
new.id,
new.email,
new.raw_user_meta_data->>'full_name',
new.raw_user_meta_data->>'avatar_url'
new.raw_user_meta_data->>'provider',
now()
);
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();
-- Set up Storage!
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
-- Set up access controls for storage.
-- See https://supabase.com/docs/guides/storage#policy-examples for more details.
create policy "Avatar images are publicly accessible." on storage.objects
for select using (bucket_id = 'avatars');
create policy "Anyone can upload an avatar." on storage.objects
for insert with check (bucket_id = 'avatars');
-- -- Create a table for public statuses
-- CREATE TABLE statuses (
-- id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
-- user_id uuid REFERENCES auth.users ON DELETE CASCADE NOT NULL,
-- updated_by_id uuid REFERENCES auth.users ON DELETE SET NULL DEFAULT auth.uid(),
-- created_at timestamp with time zone DEFAULT now() NOT NULL,
-- status text NOT NULL,
-- CONSTRAINT status_length CHECK (char_length(status) >= 3 AND char_length(status) <= 80),
-- CONSTRAINT statuses_user_id_fkey FOREIGN KEY (user_id) REFERENCES profiles(id) ON DELETE CASCADE
-- );
-- -- Set up Row Level Security (RLS)
-- ALTER TABLE statuses
-- ENABLE ROW LEVEL SECURITY;
-- -- Policies
-- CREATE POLICY "Public statuses are viewable by everyone." ON statuses
-- FOR SELECT USING (true);
-- CREATE POLICY "Users can insert statuses for any user." ON statuses
-- FOR INSERT WITH CHECK (auth.role() = 'authenticated');
-- -- Function to add first status
-- CREATE FUNCTION public.handle_first_status()
-- RETURNS TRIGGER
-- SET search_path = ''
-- AS $$
-- BEGIN
-- INSERT INTO public.statuses (user_id, updated_by_id, status)
-- VALUES (
-- NEW.id,
-- NEW.id,
-- 'Just joined!'
-- );
-- RETURN NEW;
-- END;
-- $$ LANGUAGE plpgsql SECURITY DEFINER;
-- -- Create a separate trigger for the status
-- CREATE TRIGGER on_auth_user_created_add_status
-- AFTER INSERT ON auth.users
-- FOR EACH ROW EXECUTE PROCEDURE public.handle_first_status();
-- alter publication supabase_realtime add table statuses;