diff --git a/.gitignore b/.gitignore index c74ff1b..07e6e47 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -.env /node_modules diff --git a/.notes..swp b/.notes..swp new file mode 100644 index 0000000..e44a7cf Binary files /dev/null and b/.notes..swp differ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c2a77cc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "workbench.colorTheme": "G Dark (Haiti)" +} \ No newline at end of file diff --git a/README.md b/README.md index 2c3ace4..755e18f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# NextJS Supabase Dashboard +########## Appovideo Libay Manager Tool +## via NextJS Supabase Dashboard This is a dashboard starter template for the [NextJS](https://nextjs.org) 14 app router using supabase based on [shadcn-ui](https://ui.shadcn.com). diff --git a/app/api/auth/callback/route.ts b/app/api/auth/callback/route.ts index cf7b868..f606e05 100644 --- a/app/api/auth/callback/route.ts +++ b/app/api/auth/callback/route.ts @@ -1,45 +1,25 @@ +// @/app/api/auth/callback/route.ts +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' import { cookies } from 'next/headers' import { NextResponse } from 'next/server' -import { type CookieOptions, createServerClient } from '@supabase/ssr' -/** - * OAuth with PKCE flow for SSR - * - * @link https://supabase.com/docs/guides/auth/server-side/oauth-with-pkce-flow-for-ssr - */ export async function GET(request: Request) { - const { searchParams, origin } = new URL(request.url) - const code = searchParams.get('code') - // if "next" is in param, use it as the redirect URL - const next = searchParams.get('next') ?? '/' + const requestUrl = new URL(request.url) + const code = requestUrl.searchParams.get('code') + const next = requestUrl.searchParams.get('next') || '/dashboard' if (code) { - const cookieStore = cookies() - const supabase = createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - { - cookies: { - get(name: string) { - return cookieStore.get(name)?.value - }, - set(name: string, value: string, options: CookieOptions) { - cookieStore.set({ name, value, ...options }) - }, - remove(name: string, options: CookieOptions) { - cookieStore.delete({ name, ...options }) - }, - }, - } - ) - + const supabase = createRouteHandlerClient({ cookies }) const { error } = await supabase.auth.exchangeCodeForSession(code) - - if (!error) { - return NextResponse.redirect(`${origin}${next}`) + + if (error) { + console.error('Auth callback error:', error) + return NextResponse.redirect( + new URL('/auth/signin?error=' + encodeURIComponent(error.message), request.url) + ) } } - // return the user to an error page with instructions - return NextResponse.redirect(`${origin}/auth/auth-code-error`) -} + // Redirect to the dashboard or specified next page + return NextResponse.redirect(new URL(next, request.url)) +} \ No newline at end of file diff --git a/app/api/auth/confirm/route.ts b/app/api/auth/confirm/route.ts index bd2c07d..a2fbdf6 100644 --- a/app/api/auth/confirm/route.ts +++ b/app/api/auth/confirm/route.ts @@ -1,23 +1,23 @@ +// app/api/auth/confirm/route.ts import { createServerClient, type CookieOptions } from '@supabase/ssr' import { type EmailOtpType } from '@supabase/supabase-js' import { cookies } from 'next/headers' import { NextResponse, type NextRequest } from 'next/server' -/** - * Email Auth with PKCE flow for SSR - * - * @link https://supabase.com/docs/guides/auth/server-side/email-based-auth-with-pkce-flow-for-ssr - */ -export async function GET(request: NextRequest) { - const { searchParams } = new URL(request.url) - const token_hash = searchParams.get('token_hash') as string +export async function GET(request: NextRequest) + { + try { + const { searchParams, origin } = new URL(request.url) // Define origin here + const token_hash = searchParams.get('token_hash') const type = searchParams.get('type') as EmailOtpType | null // if "next" is in param, use it as the redirect URL const next = (searchParams.get('next') as string) ?? '/' const redirectTo = request.nextUrl.clone() redirectTo.pathname = next - if (token_hash && type) { + if (!token_hash || !type) { + throw new Error('Missing token_hash or type') + } const cookieStore = cookies() const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, @@ -39,12 +39,14 @@ export async function GET(request: NextRequest) { const { error } = await supabase.auth.verifyOtp({ type, token_hash }) - if (!error) { - return NextResponse.redirect(redirectTo) + if (error) { + throw error // Re-throw the error to be caught by the catch block } - } - // return the user to an error page with some instructions - redirectTo.pathname = '/auth/auth-code-error' - return NextResponse.redirect(redirectTo) + return NextResponse.redirect(redirectTo) + } catch (error) { + console.error('Error in /auth/confirm:', error) + const redirectTo = new URL('/auth/auth-code-error', request.nextUrl.origin) + return NextResponse.redirect(redirectTo) + } } diff --git a/app/auth-test/page.tsx b/app/auth-test/page.tsx new file mode 100644 index 0000000..574988a --- /dev/null +++ b/app/auth-test/page.tsx @@ -0,0 +1,12 @@ + +// @/app/auth-test/page.tsx +import AuthTest from '@/components/auth-test' + +export default function TestPage() { + return ( +
+

Auth Test Page

+ +
+ ) +} \ No newline at end of file diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx new file mode 100644 index 0000000..e7ae203 --- /dev/null +++ b/app/auth/callback/page.tsx @@ -0,0 +1,48 @@ +// app/auth/callback/page.tsx +import { createServerClient } from '@supabase/ssr' +import { cookies } from 'next/headers' +import { redirect } from 'next/navigation' + +export const dynamic = 'force-dynamic' + +export default async function AuthCallbackPage({ + searchParams, +}: { + searchParams: { code: string; next?: string } +}) { + const cookieStore = cookies() + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + set(name: string, value: string, options: any) { + cookieStore.set({ name, value, ...options }) + }, + remove(name: string, options: any) { + cookieStore.delete({ name, ...options }) + }, + }, + } + ) + + const code = searchParams.code + + try { + if (code) { + const { error } = await supabase.auth.exchangeCodeForSession(code) + if (error) { + throw error + } + } + + // URL to redirect to after sign in process completes + return redirect(searchParams.next || '/dashboard') + } catch (error) { + console.error('Auth callback error:', error) + return redirect('/auth/auth-code-error') + } +} \ No newline at end of file diff --git a/app/auth/signin/signin-form.tsx b/app/auth/signin/signin-form.tsx index dc351e6..c75e6fd 100644 --- a/app/auth/signin/signin-form.tsx +++ b/app/auth/signin/signin-form.tsx @@ -44,12 +44,44 @@ const SignInForm = () => { defaultValues, }) + + // Add dev login function + const handleDevLogin = async () => { + const supabase = createClient() + try { + const { data, error } = await supabase.auth.signInWithPassword({ + email: 'dev@approvideo.org', // Replace with your dev email + password: 'devpass123' // Replace with your dev password + }) + + if (error) throw error + + toast.success('Dev login successful') + window.location.href = '/dashboard' // Force reload to ensure auth state + } catch (error) { + console.error('Dev login failed:', error) + toast.error('Dev login failed') + } + } + + return (
+ + + + ) diff --git a/app/auth/signup/signup-form.tsx b/app/auth/signup/signup-form.tsx index 181db8b..2b3f8f4 100644 --- a/app/auth/signup/signup-form.tsx +++ b/app/auth/signup/signup-form.tsx @@ -1,13 +1,12 @@ +// app/auth/signup/signup-form.tsx 'use client' import * as React from 'react' import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' - import { useForm, useFormContext } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' - import { toast } from 'sonner' import { Input } from '@/components/ui/input' import { Button } from '@/components/ui/button' @@ -20,16 +19,30 @@ import { FormLabel, FormMessage, } from '@/components/ui/form' - import { createClient } from '@/supabase/client' import { useAuth } from '@/hooks/use-auth' +// Enhanced password validation +const passwordSchema = z + .string() + .nonempty('Password is required') + .min(8, 'Password must be at least 8 characters') + .max(72, 'Password must not exceed 72 characters') + .regex(/[A-Z]/, 'Password must contain at least one uppercase letter') + .regex(/[a-z]/, 'Password must contain at least one lowercase letter') + .regex(/[0-9]/, 'Password must contain at least one number') + .regex(/[^A-Za-z0-9]/, 'Password must contain at least one special character') + const FormSchema = z .object({ - email: z.string().nonempty().max(255).email(), - // If the password is larger than 72 chars, it will be truncated to the first 72 chars. - newPassword: z.string().nonempty().min(6).max(72), - confirmNewPassword: z.string().nonempty().min(6).max(72), + email: z + .string() + .nonempty('Email is required') + .email('Invalid email format') + .max(255, 'Email must not exceed 255 characters') + .transform(val => val.toLowerCase().trim()), + newPassword: passwordSchema, + confirmNewPassword: passwordSchema, }) .refine((val) => val.newPassword === val.confirmNewPassword, { path: ['confirmNewPassword'], @@ -44,18 +57,63 @@ const defaultValues: Partial = { confirmNewPassword: '', } + + + + + +// Password strength indicator +const PasswordStrengthIndicator = ({ password }: { password: string }) => { + const strength = React.useMemo(() => { + if (!password) return 0 + let score = 0 + if (password.length >= 8) score++ + if (/[A-Z]/.test(password)) score++ + if (/[a-z]/.test(password)) score++ + if (/[0-9]/.test(password)) score++ + if (/[^A-Za-z0-9]/.test(password)) score++ + return score + }, [password]) + + return ( +
+
+ {[...Array(5)].map((_, i) => ( +
+ ))} +
+

+ {strength === 0 && 'Very weak'} + {strength === 1 && 'Weak'} + {strength === 2 && 'Fair'} + {strength === 3 && 'Good'} + {strength === 4 && 'Strong'} + {strength === 5 && 'Very strong'} +

+
+ ) +} + const SignUpForm = () => { const form = useForm({ resolver: zodResolver(FormSchema), - mode: 'onSubmit', + mode: 'onChange', // Enable real-time validation defaultValues, }) + const password = form.watch('newPassword') + return (
+ @@ -71,7 +129,7 @@ const EmailField = () => { ( + render={({ field, fieldState }) => ( {t('email')} @@ -81,9 +139,13 @@ const EmailField = () => { autoComplete="email" autoCorrect="off" placeholder="name@example.com" + className={fieldState.error ? 'border-red-500' : ''} {...field} /> + + You'll need to verify this email + )} @@ -94,24 +156,38 @@ const EmailField = () => { const NewPasswordField = () => { const { t } = useTranslation() const { control } = useFormContext() + const [showPassword, setShowPassword] = React.useState(false) return ( ( + render={({ field, fieldState }) => ( {t('password')} - - - +
+ + + + +
+ + Must include uppercase, lowercase, number and special character +
)} @@ -122,24 +198,35 @@ const NewPasswordField = () => { const ConfirmNewPasswordField = () => { const { t } = useTranslation() const { control } = useFormContext() + const [showPassword, setShowPassword] = React.useState(false) return ( ( + render={({ field, fieldState }) => ( {t('confirm_password')} - - - +
+ + + + +
)} @@ -150,42 +237,77 @@ const ConfirmNewPasswordField = () => { const SubmitButton = () => { const router = useRouter() const { t } = useTranslation() - const { handleSubmit, setError, getValues } = useFormContext() const { setSession, setUser } = useAuth() - const [isSubmitting, setIsSubmitting] = React.useState(false) + const { handleSubmit, setError, getValues, formState } = useFormContext() // Added formState here const onSubmit = async () => { try { setIsSubmitting(true) - const formValues = getValues() + // Log signup attempt (excluding password) + console.log('Attempting signup:', { + email: formValues?.email, + timestamp: new Date().toISOString() + }) + const supabase = createClient() - const signed = await supabase.auth.signUp({ + const { data, error } = await supabase.auth.signUp({ email: formValues?.email, password: formValues?.newPassword, + options: { + emailRedirectTo: `${window.location.origin}/auth/callback`, + data: { + email_confirmed: false, + signup_date: new Date().toISOString() + } + } }) - if (signed?.error) throw new Error(signed?.error?.message) - const unsigned = await supabase.auth.signOut() - if (unsigned?.error) throw new Error(unsigned?.error?.message) + if (error) { + console.error('Signup error:', { + code: error.status, + message: error.message, + timestamp: new Date().toISOString() + }) + throw error + } + + if (data?.user) { + console.log('Signup successful:', { + userId: data.user.id, + timestamp: new Date().toISOString() + }) - setSession(null) - setUser(null) + // Sign out after successful registration + const unsigned = await supabase.auth.signOut() + if (unsigned?.error) throw new Error(unsigned?.error?.message) - toast.success(t('you_have_successfully_registered_as_a_member')) + setSession(null) + setUser(null) - router.refresh() - router.replace('/auth/signin') + toast.success(t('you_have_successfully_registered_as_a_member')) + router.refresh() + router.replace('/auth/signin') + } } catch (e: unknown) { const err = (e as Error)?.message - if (err.startsWith('User already registered')) { + console.error('Signup error details:', { + error: err, + timestamp: new Date().toISOString() + }) + + if (err.includes('already registered')) { setError('email', { message: t('user_already_registered'), }) + } else if (err.includes('password')) { + setError('newPassword', { + message: err + }) } else { - toast.error(err) + toast.error(typeof err === 'string' ? err : 'Signup failed') } } finally { setIsSubmitting(false) @@ -196,12 +318,19 @@ const SubmitButton = () => { ) } -export { SignUpForm } +export { SignUpForm } \ No newline at end of file diff --git a/app/dashboard/admin/videos/page.tsx b/app/dashboard/admin/videos/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/app/dashboard/admin/videos/ubmit-form.tsx b/app/dashboard/admin/videos/ubmit-form.tsx new file mode 100644 index 0000000..e69de29 diff --git a/app/dashboard/admin/videos/video-table.tx b/app/dashboard/admin/videos/video-table.tx new file mode 100644 index 0000000..bc6448c --- /dev/null +++ b/app/dashboard/admin/videos/video-table.tx @@ -0,0 +1,130 @@ +// app/dashboard/admin/videos/video-table.tsx +'use client' + +import { useState } from 'react' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table' +import { Button } from '@/components/ui/button' +import { createClient } from '@/supabase/client' + +interface Video { + id: string + title: string + url: string + description: string + status: 'pending' | 'approved' | 'rejected' + created_at: string + updated_at: string +} + +export default function VideoTable() { + const [videos, setVideos] = useState([]) + const [loading, setLoading] = useState(false) + + const handleStatusChange = async (videoId: string, newStatus: 'approved' | 'rejected') => { + const supabase = createClient() + try { + const { error } = await supabase + .from('videos') + .update({ status: newStatus }) + .eq('id', videoId) + + if (error) throw error + // Refresh the videos list + fetchVideos() + } catch (error) { + console.error('Error updating video status:', error) + } + } + + const fetchVideos = async () => { + const supabase = createClient() + try { + setLoading(true) + const { data, error } = await supabase + .from('videos') + .select('*') + .order('created_at', { ascending: false }) + + if (error) throw error + setVideos(data || []) + } catch (error) { + console.error('Error fetching videos:', error) + } finally { + setLoading(false) + } + } + + return ( +
+
+

Video Submissions

+ +
+ + + + + Title + Description + Status + Submitted + Actions + + + + {videos.map((video) => ( + + {video.title} + {video.description} + + + {video.status} + + + + {new Date(video.created_at).toLocaleDateString()} + + +
+ + +
+
+
+ ))} +
+
+
+ ) +} \ No newline at end of file diff --git a/app/dashboard/components/app-bar.tsx b/app/dashboard/components/app-bar.tsx index 47dca8f..4f4be87 100644 --- a/app/dashboard/components/app-bar.tsx +++ b/app/dashboard/components/app-bar.tsx @@ -1,11 +1,10 @@ +// @/app/dashboard/components/app-bar.tsx 'use client' import * as React from 'react' - import { AccountMenu } from '@/components/account-menu' import { SiteBrand } from '@/components/site-brand' import { Notify } from '@/app/dashboard/components/notify' - import { cn } from '@/lib/utils' interface AppBarProps extends React.HTMLAttributes {} @@ -29,4 +28,4 @@ const AppBar = ({ children, className, ...props }: AppBarProps) => { ) } -export { AppBar, type AppBarProps } +export { AppBar, type AppBarProps } \ No newline at end of file diff --git a/app/supabase-test/check.tx b/app/supabase-test/check.tx new file mode 100644 index 0000000..705a4f2 --- /dev/null +++ b/app/supabase-test/check.tx @@ -0,0 +1,53 @@ +// app/supabase-test/check.tsx +import { cookies } from 'next/headers' +import { createServerClient } from '@supabase/ssr' + +export const dynamic = 'force-dynamic' + +export default async function CheckConfig() { + const cookieStore = cookies() + + try { + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + set(name: string, value: string, options: any) { + cookieStore.set({ name, value, ...options }) + }, + remove(name: string, options: any) { + cookieStore.delete({ name, ...options }) + }, + }, + } + ) + + // Test connection + const { data, error } = await supabase.from('_health').select('*').limit(1) + + return ( +
+

Supabase Configuration Check

+
+          {JSON.stringify({
+            supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
+            hasAnonKey: !!process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
+            connectionTest: error ? 'Failed' : 'Success',
+            error: error?.message || null
+          }, null, 2)}
+        
+
+ ) + } catch (error) { + return ( +
+

Configuration Error

+
{JSON.stringify(error, null, 2)}
+
+ ) + } +} \ No newline at end of file diff --git a/clean.pl b/clean.pl new file mode 100644 index 0000000..f101ddf --- /dev/null +++ b/clean.pl @@ -0,0 +1,129 @@ +#!/usr/bin/perl +use strict; +use warnings; +use File::Find; +use File::Spec; +use Data::Dumper; + +# Common patterns to ignore in Next.js projects +my @common_ignores = ( + # Next.js build output + '.next/', + 'out/', + 'build/', + + # Dependencies + 'node_modules/', + '.pnp/', + '.pnp.js', + + # Environment files + '.env*.local', + '.env.local', + '.env.development.local', + '.env.test.local', + '.env.production.local', + + # Debug logs + 'npm-debug.log*', + 'yarn-debug.log*', + 'yarn-error.log*', + + # Cache directories + '.cache/', + '.eslintcache', + + # Editor directories + '.idea/', + '.vscode/', + '*.swp', + '*.swo', + + # OS files + '.DS_Store', + 'Thumbs.db', + + # PWA files + 'public/sw.js', + 'public/workbox-*.js', + 'public/worker-*.js', + 'public/sw.js.map', + 'public/workbox-*.js.map', + 'public/worker-*.js.map' +); + +# Initialize arrays to store results +my @unused_files; +my @temp_files; +my @large_files; + +# Save the current directory +my $start_dir = '.'; + +# Function to check if a file is potentially unused +sub check_file { + my $file = $File::Find::name; + my $relative_path = File::Spec->abs2rel($file, $start_dir); + + # Skip node_modules and .next directories + return if $file =~ /node_modules|\.next/; + + # Check for temporary and backup files + if ($file =~ /\.(tmp|temp|bak|log|old)$/) { + push @temp_files, $relative_path; + return; + } + + # Check file size (files > 1MB) + my $size = -s $file; + if ($size && $size > 1_000_000) { + push @large_files, "$relative_path (" . int($size/1024/1024) . "MB)"; + } + + # Check for potentially unused files + if ($file =~ /\.(js|jsx|ts|tsx)$/) { + # Skip if it's in pages/ or components/ + return if $file =~ /\b(pages|components|app)\b/; + + # Check if file might be unused + my $is_imported = 0; + find(sub { + return if $_ !~ /\.(js|jsx|ts|tsx)$/; + return if $File::Find::name eq $file; + + open my $fh, '<', $_ or return; + while (<$fh>) { + if (/$relative_path/i || /\b$relative_path\b/i) { + $is_imported = 1; + last; + } + } + close $fh; + }, $start_dir); + + push @unused_files, $relative_path unless $is_imported; + } +} + +# Run the analysis +print "Analyzing project structure...\n\n"; +find(\&check_file, $start_dir); + +# Generate .gitignore content +my @gitignore_content = (@common_ignores, @unused_files, @temp_files); + +# Print results +print "Potentially unused files:\n"; +print "- $_\n" for @unused_files; +print "\nTemporary/backup files:\n"; +print "- $_\n" for @temp_files; +print "\nLarge files (>1MB):\n"; +print "- $_\n" for @large_files; + +# Write to .gitignore +print "\nGenerating .gitignore file...\n"; +open my $gi, '>', '.gitignore' or die "Cannot open .gitignore: $!"; +print $gi "$_\n" for @gitignore_content; +close $gi; + +print "\nDone! .gitignore has been updated.\n"; diff --git a/components/auth-test.tsx b/components/auth-test.tsx new file mode 100644 index 0000000..ea79af7 --- /dev/null +++ b/components/auth-test.tsx @@ -0,0 +1,187 @@ +// @/components/auth-test.tsx +'use client' + +import { useState, useEffect } from 'react' +import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' +import { useRouter, useSearchParams } from 'next/navigation' +import type { User, AuthError as SupabaseAuthError } from '@supabase/supabase-js' + +type AuthError = { + message: string + status?: number + __isAuthError?: boolean +} + +function formatError(error: unknown): AuthError { + if (error instanceof Error) { + return { + message: error.message, + __isAuthError: 'status' in error, + status: 'status' in error ? (error as any).status : undefined + } + } + return { + message: 'An unknown error occurred', + __isAuthError: false + } +} + +export default function AuthTest() { + const [status, setStatus] = useState<{ user: User | null } | null>(null) + const [error, setError] = useState(null) + const [loading, setLoading] = useState(false) + const router = useRouter() + const searchParams = useSearchParams() + const supabase = createClientComponentClient() + + useEffect(() => { + const initAuth = async () => { + try { + setLoading(true) + const { data: { session }, error: sessionError } = await supabase.auth.getSession() + + if (sessionError) { + throw sessionError + } + + if (session) { + setStatus({ user: session.user }) + } + + const { data: { subscription } } = supabase.auth.onAuthStateChange( + async (event, session) => { + console.log('Auth state changed:', event) + setStatus({ user: session?.user ?? null }) + } + ) + + return () => { + subscription.unsubscribe() + } + } catch (e) { + console.error('Auth initialization error:', e) + setError(formatError(e)) + } finally { + setLoading(false) + } + } + + initAuth() + }, [supabase.auth]) + + async function checkAuth() { + try { + setLoading(true) + setError(null) + const { data: { user }, error: authError } = await supabase.auth.getUser() + if (authError) throw authError + setStatus({ user }) + } catch (e) { + setError(formatError(e)) + console.error('Check auth error:', e) + } finally { + setLoading(false) + } + } + + async function testGoogleLogin() { + try { + setLoading(true) + setError(null) + const { error } = await supabase.auth.signInWithOAuth({ + provider: 'google', + options: { + redirectTo: `${window.location.origin}/auth/callback`, // Make sure this matches your callback route + queryParams: { + access_type: 'offline', + prompt: 'consent', + next: '/dashboard' // Where to go after successful auth + } + } + }) + if (error) throw error + } catch (e) { + setError(formatError(e)) + console.error( e) + } finally { + setLoading(false) + } + } + + async function signOut() { + try { + setLoading(true) + const { error } = await supabase.auth.signOut() + if (error) throw error + setStatus(null) + router.push('/') + } catch (e) { + setError(formatError(e)) + console.error('Sign out error:', e) + } finally { + setLoading(false) + } + } + + return ( +
+
+ + + +
+ +
+

+ Status: {loading ? 'Loading...' : status?.user ? 'Authenticated' : 'Not Authenticated'} +

+ {error && ( +

+ Error: {error.message} + {error.status && ` (Status: ${error.status})`} +

+ )} +
+ + {loading && ( +
+
+
+ )} + + {error && ( +
+
+            {JSON.stringify(error, null, 2)}
+          
+
+ )} + + {status && ( +
+
+            {JSON.stringify(status, null, 2)}
+          
+
+ )} +
+ ) +} \ No newline at end of file diff --git a/components/hero.tsx b/components/hero.tsx index 1777cdb..20342be 100644 --- a/components/hero.tsx +++ b/components/hero.tsx @@ -34,7 +34,7 @@ const Hero = () => { {t('github')} diff --git a/components/signin-with-google.tsx b/components/signin-with-google.tsx index 59f73f2..4c453c7 100644 --- a/components/signin-with-google.tsx +++ b/components/signin-with-google.tsx @@ -1,3 +1,4 @@ +// components/signin-with-google.tsx 'use client' import * as React from 'react' @@ -27,15 +28,16 @@ const SignInWithGoogle = ({ const next = (searchParams.get('next') as string) ?? '/dashboard' const supabase = createClient() - const signed = await supabase.auth.signInWithOAuth({ + const { error } = await supabase.auth.signInWithOAuth({ provider: 'google', options: { // A URL to send the user to after they are confirmed. - // Don't forget to change the URL in supabase's email template. + // Don't forget to change the URL in Supabase's email template. redirectTo: process.env.NEXT_PUBLIC_APP_URL + `/api/auth/callback?next=${next}`, // Google does not send out a refresh token by default, - // so you will need to pass parameters like these to signInWithOAuth() in order to extract the provider_refresh_token: + // so you will need to pass parameters like these to signInWithOAuth() + // in order to extract the provider_refresh_token: queryParams: { access_type: 'offline', prompt: 'consent', @@ -43,10 +45,13 @@ const SignInWithGoogle = ({ }, }) - if (signed?.error) throw new Error(signed?.error?.message) + if (error) { + toast.error(error.message) + } } catch (e: unknown) { toast.error((e as Error)?.message) } + } return ( @@ -57,4 +62,4 @@ const SignInWithGoogle = ({ ) } -export { SignInWithGoogle, type SignInWithGoogleProps } +export { SignInWithGoogle, type SignInWithGoogleProps } \ No newline at end of file diff --git a/components/site-logo.tsx b/components/site-logo.tsx index 0ac1f61..6deb41b 100644 --- a/components/site-logo.tsx +++ b/components/site-logo.tsx @@ -24,10 +24,12 @@ const SiteLogo = ({ fill="#66B814" d="M512 4.098C231.95 4.098 4.098 231.949 4.098 512c0 280.05 227.851 507.902 507.902 507.902 280.055 0 507.902-227.851 507.902-507.902C1019.902 231.95 792.055 4.098 512 4.098zm0 0" /> - + + + + + + ) } diff --git a/config/site.ts b/config/site.ts index a55ab20..b39a867 100644 --- a/config/site.ts +++ b/config/site.ts @@ -8,10 +8,10 @@ export interface SiteConfig { } export const siteConfig: SiteConfig = { - name: 'NextJS supabase dashboard', - title: 'NextJS supabase dashboard', + name: 'DIY Solutions', + title: 'A P P R O V I D E O', description: - 'This is a dashboard starter template for the NextJS 14 app router using supabase based on shadcn-ui.', + 'Manage the live site at approvideo.org', symbol: 'Activity', // LucideIcon } diff --git a/context/auth-provider.tsx b/context/auth-provider.tsx index 5857f44..e55c313 100644 --- a/context/auth-provider.tsx +++ b/context/auth-provider.tsx @@ -1,13 +1,12 @@ +// /context/auth-provider.tsx 'use client' import * as React from 'react' - import { Session, User } from '@supabase/supabase-js' import { createClient } from '@/supabase/client' /** * Listen to auth events - * * @link https://supabase.com/docs/reference/javascript/auth-onauthstatechange */ interface AuthContextProps { @@ -48,4 +47,4 @@ const AuthProvider = ({ children }: { children?: React.ReactNode }) => { ) } -export { AuthContext, type AuthContextProps, AuthProvider } +export { AuthContext, type AuthContextProps, AuthProvider } \ No newline at end of file diff --git a/hooks/use-auth.ts b/hooks/use-auth.ts index 9b08ca9..564d339 100644 --- a/hooks/use-auth.ts +++ b/hooks/use-auth.ts @@ -1,3 +1,5 @@ +// /hooks/use-auth.ts + 'use client' import * as React from 'react' diff --git a/managed_context/metadata.json b/managed_context/metadata.json new file mode 100644 index 0000000..2e0910c --- /dev/null +++ b/managed_context/metadata.json @@ -0,0 +1 @@ +{"current_schema_version":"0.0.1"} \ No newline at end of file diff --git a/middleware.ts b/middleware.ts index e5ec966..651fa4f 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,26 +1,38 @@ -import { NextResponse, type NextRequest } from 'next/server' -import { updateSession } from '@/supabase/middleware' -import { Deny, denies } from '@/config/middleware' +// @/middleware.ts +import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' -// Server Components only have read access to cookies. -// This Middleware example can be used to refresh expired sessions before loading Server Component routes. export async function middleware(request: NextRequest) { - const { response, authenticated } = await updateSession(request) + const res = NextResponse.next() + const supabase = createMiddlewareClient({ req: request, res }) - const found = denies?.find((deny: Deny) => - request.nextUrl.pathname.startsWith(deny.source) - ) + try { + // Refresh the session if it exists + const { data: { session }, error: sessionError } = await supabase.auth.getSession() + + if (sessionError) { + console.error('Session error in middleware:', sessionError) + } - if (found && found.authenticated === authenticated) { - return NextResponse.redirect(new URL(found.destination, request.url)) - } + // Handle protected routes + if (request.nextUrl.pathname.startsWith('/dashboard')) { + if (!session) { + return NextResponse.redirect(new URL('/auth/signin', request.url)) + } + } + + // Handle auth routes when already authenticated + if (request.nextUrl.pathname.startsWith('/auth/signin') && session) { + return NextResponse.redirect(new URL('/dashboard', request.url)) + } - const url = new URL(request.url) - response.headers.set('x-url', request.url) - response.headers.set('x-origin', url.origin) - response.headers.set('x-pathname', url.pathname) + return res - return response + } catch (error) { + console.error('Middleware error:', error) + return NextResponse.redirect(new URL('/auth/signin', request.url)) + } } export const config = { @@ -33,6 +45,7 @@ export const config = { * - favicon.ico (favicon file) */ '/', - '/((?!api|_next/static|_next/image|favicon.ico).*)', - ], -} + '/dashboard/:path*', + '/auth/:path*' + ] +} \ No newline at end of file diff --git a/next.config.js b/next.config.js index 50ac0b4..944f7e3 100644 --- a/next.config.js +++ b/next.config.js @@ -1,3 +1,5 @@ +// @/next.config.js + const withPWA = require('next-pwa')({ dest: 'public', }) @@ -7,8 +9,6 @@ const nextConfig = { reactStrictMode: process.env.NODE_ENV === 'production', swcMinify: true, eslint: { - // Warning: This allows production builds to successfully complete even if - // your project has ESLint errors. ignoreDuringBuilds: true, }, images: { @@ -34,28 +34,38 @@ const nextConfig = { }, ], }, - // Enabling CORS in a Next.js App - // https://vercel.com/guides/how-to-enable-cors async headers() { return [ { + // This matches all API routes source: '/api/:path*', + headers: [ + { key: 'Access-Control-Allow-Credentials', value: 'true' }, + { key: 'Access-Control-Allow-Origin', value: '*' }, // Consider restricting this to your domain + { key: 'Access-Control-Allow-Methods', value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT' }, + { key: 'Access-Control-Allow-Headers', value: 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Authorization' }, + ], + }, + { + // This matches all pages + source: '/:path*', headers: [ { key: 'Access-Control-Allow-Credentials', value: 'true' }, { key: 'Access-Control-Allow-Origin', value: '*' }, - { - key: 'Access-Control-Allow-Methods', - value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT', - }, - { - key: 'Access-Control-Allow-Headers', - value: - 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version', - }, + { key: 'Access-Control-Allow-Methods', value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT' }, + { key: 'Access-Control-Allow-Headers', value: 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Authorization' }, ], + } + ] + }, + async rewrites() { + return [ + { + source: '/api/:path*', + destination: '/api/:path*', }, ] }, } -module.exports = withPWA(nextConfig) +module.exports = withPWA(nextConfig) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 62e11ee..e7e28b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-visually-hidden": "^1.1.0", "@reduxjs/toolkit": "^2.2.6", + "@supabase/auth-helpers-nextjs": "^0.10.0", "@supabase/ssr": "^0.4.0", "@supabase/supabase-js": "^2.44.4", "@types/jsonwebtoken": "^9.0.6", @@ -4682,6 +4683,29 @@ "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", "dev": true }, + "node_modules/@supabase/auth-helpers-nextjs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.10.0.tgz", + "integrity": "sha512-2dfOGsM4yZt0oS4TPiE7bD4vf7EVz7NRz/IJrV6vLg0GP7sMUx8wndv2euLGq4BjN9lUCpu6DG/uCC8j+ylwPg==", + "dependencies": { + "@supabase/auth-helpers-shared": "0.7.0", + "set-cookie-parser": "^2.6.0" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.39.8" + } + }, + "node_modules/@supabase/auth-helpers-shared": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.7.0.tgz", + "integrity": "sha512-FBFf2ei2R7QC+B/5wWkthMha8Ca2bWHAndN+syfuEUUfufv4mLcAgBCcgNg5nJR8L0gZfyuaxgubtOc9aW3Cpg==", + "dependencies": { + "jose": "^4.14.4" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.39.8" + } + }, "node_modules/@supabase/auth-js": { "version": "2.64.4", "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.64.4.tgz", @@ -10060,6 +10084,14 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -12210,6 +12242,11 @@ "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==" }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", diff --git a/package.json b/package.json index ffc21e7..3d16f9a 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-visually-hidden": "^1.1.0", "@reduxjs/toolkit": "^2.2.6", + "@supabase/auth-helpers-nextjs": "^0.10.0", "@supabase/ssr": "^0.4.0", "@supabase/supabase-js": "^2.44.4", "@types/jsonwebtoken": "^9.0.6", diff --git a/structure.txt b/structure.txt new file mode 100644 index 0000000..a0ed6a3 --- /dev/null +++ b/structure.txt @@ -0,0 +1,805 @@ +. +├── app +│   ├── api +│   │   ├── auth +│   │   │   ├── callback +│   │   │   │   └── route.ts +│   │   │   └── confirm +│   │   │   └── route.ts +│   │   ├── cron +│   │   │   └── daily-reset-posts +│   │   │   └── route.ts +│   │   ├── ip +│   │   │   └── route.ts +│   │   ├── revalidate +│   │   │   └── route.ts +│   │   ├── v1 +│   │   │   ├── email +│   │   │   │   ├── list +│   │   │   │   │   └── route.ts +│   │   │   │   ├── route.ts +│   │   │   │   └── verify +│   │   │   │   └── route.ts +│   │   │   ├── favorite +│   │   │   │   └── route.ts +│   │   │   ├── notification +│   │   │   │   └── route.ts +│   │   │   ├── notify +│   │   │   │   └── route.ts +│   │   │   ├── post +│   │   │   │   ├── count +│   │   │   │   │   └── route.ts +│   │   │   │   ├── list +│   │   │   │   │   └── route.ts +│   │   │   │   ├── rank +│   │   │   │   │   └── route.ts +│   │   │   │   └── route.ts +│   │   │   ├── statistic +│   │   │   │   └── list +│   │   │   │   └── route.ts +│   │   │   ├── tag +│   │   │   │   ├── list +│   │   │   │   │   └── route.ts +│   │   │   │   └── route.ts +│   │   │   └── user +│   │   │   ├── list +│   │   │   │   └── route.ts +│   │   │   └── route.ts +│   │   └── verify +│   │   └── email +│   │   └── route.ts +│   ├── auth +│   │   ├── auth-code-error +│   │   │   └── page.tsx +│   │   ├── blocked +│   │   │   ├── layout.tsx +│   │   │   └── page.tsx +│   │   ├── callback +│   │   │   └── page.tsx +│   │   ├── forgot-password +│   │   │   ├── forgot-password-form.tsx +│   │   │   └── page.tsx +│   │   ├── page.tsx +│   │   ├── reset-password +│   │   │   ├── page.tsx +│   │   │   └── reset-password-form.tsx +│   │   ├── signin +│   │   │   ├── page.tsx +│   │   │   └── signin-form.tsx +│   │   └── signup +│   │   ├── page.tsx +│   │   ├── policy.tsx +│   │   └── signup-form.tsx +│   ├── auth-test +│   │   └── page.tsx +│   ├── dashboard +│   │   ├── admin +│   │   │   ├── layout.tsx +│   │   │   └── page.tsx +│   │   ├── appearance +│   │   │   ├── change-language-form.tsx +│   │   │   ├── change-theme-form.tsx +│   │   │   ├── layout.tsx +│   │   │   └── page.tsx +│   │   ├── components +│   │   │   ├── app-bar.tsx +│   │   │   ├── app-panel.tsx +│   │   │   ├── demo-site-warning-notification.tsx +│   │   │   ├── navigation.tsx +│   │   │   └── notify.tsx +│   │   ├── dashboard +│   │   │   ├── index.tsx +│   │   │   ├── latest-posts.tsx +│   │   │   └── post-ranks.tsx +│   │   ├── layout.tsx +│   │   ├── page.tsx +│   │   ├── posts +│   │   │   ├── components +│   │   │   │   ├── add-dummy-post.tsx +│   │   │   │   ├── add-post.tsx +│   │   │   │   ├── bulk-actions +│   │   │   │   │   ├── bulk-actions-provider.tsx +│   │   │   │   │   ├── bulk-actions.tsx +│   │   │   │   │   └── index.ts +│   │   │   │   ├── head-link.tsx +│   │   │   │   ├── quick-links +│   │   │   │   │   ├── index.ts +│   │   │   │   │   ├── quick-delete.tsx +│   │   │   │   │   ├── quick-draft.tsx +│   │   │   │   │   ├── quick-edit.tsx +│   │   │   │   │   ├── quick-private.tsx +│   │   │   │   │   ├── quick-public.tsx +│   │   │   │   │   ├── quick-publish.tsx +│   │   │   │   │   ├── quick-restore.tsx +│   │   │   │   │   ├── quick-trash.tsx +│   │   │   │   │   └── quick-view.tsx +│   │   │   │   └── search-form.tsx +│   │   │   ├── edit +│   │   │   │   ├── components +│   │   │   │   │   ├── back-link.tsx +│   │   │   │   │   ├── ckeditor5 +│   │   │   │   │   │   ├── editor.tsx +│   │   │   │   │   │   ├── style.css +│   │   │   │   │   │   └── supabase-upload-adapter.ts +│   │   │   │   │   ├── fields +│   │   │   │   │   │   ├── field-meta.tsx +│   │   │   │   │   │   ├── field-title.tsx +│   │   │   │   │   │   ├── field-user-id.tsx +│   │   │   │   │   │   └── index.ts +│   │   │   │   │   └── metaboxes +│   │   │   │   │   ├── index.ts +│   │   │   │   │   ├── metabox-description.tsx +│   │   │   │   │   ├── metabox-future-date.tsx +│   │   │   │   │   ├── metabox-keywords.tsx +│   │   │   │   │   ├── metabox-permalink.tsx +│   │   │   │   │   ├── metabox-publish.tsx +│   │   │   │   │   ├── metabox-restriction.tsx +│   │   │   │   │   ├── metabox-revisions.tsx +│   │   │   │   │   ├── metabox-slug.tsx +│   │   │   │   │   ├── metabox-tags.tsx +│   │   │   │   │   └── metabox-thumbnail.tsx +│   │   │   │   ├── context +│   │   │   │   │   └── post-form-provider.tsx +│   │   │   │   ├── page.tsx +│   │   │   │   └── post-form.tsx +│   │   │   ├── layout.tsx +│   │   │   ├── page.tsx +│   │   │   └── post-list.tsx +│   │   ├── settings +│   │   │   ├── account +│   │   │   │   ├── change-username-form.tsx +│   │   │   │   ├── deactivate-user-form.tsx +│   │   │   │   ├── delete-user-form.tsx +│   │   │   │   └── page.tsx +│   │   │   ├── emails +│   │   │   │   ├── components +│   │   │   │   │   ├── add-email.tsx +│   │   │   │   │   ├── delete-email.tsx +│   │   │   │   │   ├── edit-primary-email.tsx +│   │   │   │   │   └── resend-verify-email.tsx +│   │   │   │   ├── email-list.tsx +│   │   │   │   └── page.tsx +│   │   │   ├── layout.tsx +│   │   │   ├── notifications +│   │   │   │   ├── notifications-form.tsx +│   │   │   │   └── page.tsx +│   │   │   ├── page.tsx +│   │   │   ├── security +│   │   │   │   ├── change-password-form.tsx +│   │   │   │   ├── manage-2fa-form.tsx +│   │   │   │   └── page.tsx +│   │   │   └── sessions +│   │   │   ├── page.tsx +│   │   │   └── sessions-form.tsx +│   │   ├── tags +│   │   │   ├── components +│   │   │   │   ├── add-tag.tsx +│   │   │   │   ├── bulk-actions +│   │   │   │   │   ├── bulk-actions-provider.tsx +│   │   │   │   │   ├── bulk-actions.tsx +│   │   │   │   │   └── index.ts +│   │   │   │   ├── quick-links +│   │   │   │   │   ├── index.ts +│   │   │   │   │   ├── quick-delete.tsx +│   │   │   │   │   ├── quick-edit.tsx +│   │   │   │   │   └── quick-view.tsx +│   │   │   │   └── search-form.tsx +│   │   │   ├── edit +│   │   │   │   ├── components +│   │   │   │   │   ├── back-link.tsx +│   │   │   │   │   ├── fields +│   │   │   │   │   │   ├── field-meta.tsx +│   │   │   │   │   │   ├── field-name.tsx +│   │   │   │   │   │   ├── field-post-tags.tsx +│   │   │   │   │   │   ├── field-user-id.tsx +│   │   │   │   │   │   └── index.ts +│   │   │   │   │   └── metaboxes +│   │   │   │   │   ├── index.ts +│   │   │   │   │   ├── metabox-description.tsx +│   │   │   │   │   ├── metabox-publish.tsx +│   │   │   │   │   └── metabox-slug.tsx +│   │   │   │   ├── context +│   │   │   │   │   └── tag-form-provider.tsx +│   │   │   │   ├── page.tsx +│   │   │   │   └── tag-form.tsx +│   │   │   ├── layout.tsx +│   │   │   ├── page.tsx +│   │   │   └── tag-list.tsx +│   │   └── users +│   │   ├── layout.tsx +│   │   ├── page.tsx +│   │   └── profile +│   │   ├── page.tsx +│   │   └── profile-form.tsx +│   ├── favicon.ico +│   ├── globals.css +│   ├── layout.tsx +│   ├── not-found.tsx +│   ├── page.tsx +│   ├── policy +│   │   ├── privacy +│   │   │   └── page.tsx +│   │   └── terms +│   │   └── page.tsx +│   ├── posts +│   │   ├── page.tsx +│   │   └── sitemap.ts +│   ├── robots.ts +│   ├── search +│   │   └── page.tsx +│   ├── sitemap.ts +│   └── [username] +│   ├── aside.tsx +│   ├── components +│   │   └── tab-link.tsx +│   ├── favorites +│   │   ├── page.tsx +│   │   └── post-list.tsx +│   ├── layout.tsx +│   ├── page.tsx +│   ├── post-list.tsx +│   ├── [slug] +│   │   ├── favorite-button.tsx +│   │   ├── page.tsx +│   │   ├── post-views.tsx +│   │   └── related-posts.tsx +│   └── statistics.tsx +├── clean.pl +├── components +│   ├── account-menu.tsx +│   ├── auth-test.tsx +│   ├── banners +│   │   ├── index.ts +│   │   └── upgrade-pro-banner.tsx +│   ├── button-link.tsx +│   ├── copyright.tsx +│   ├── country-flag-button.tsx +│   ├── description.tsx +│   ├── error.tsx +│   ├── footer.tsx +│   ├── header.tsx +│   ├── hentry +│   │   ├── entry-author.tsx +│   │   ├── entry-published.tsx +│   │   ├── entry-summary.tsx +│   │   ├── entry-tags.tsx +│   │   ├── entry-title.tsx +│   │   ├── entry-updated.tsx +│   │   └── index.ts +│   ├── hero.tsx +│   ├── language-combobox.tsx +│   ├── latest-posts +│   │   ├── index.tsx +│   │   └── latest-posts.tsx +│   ├── mobile-navigation.tsx +│   ├── navigation.tsx +│   ├── paging +│   │   ├── index.tsx +│   │   ├── paging-provider.tsx +│   │   └── paging.tsx +│   ├── search-form-dialog.tsx +│   ├── search-form.tsx +│   ├── signin-with-github.tsx +│   ├── signin-with-google.tsx +│   ├── signin-with.tsx +│   ├── signout-button.tsx +│   ├── site-brand.tsx +│   ├── site-logo.tsx +│   ├── tailwind-indicator.tsx +│   ├── text-link.tsx +│   ├── theme-toggle.tsx +│   ├── time-picker.tsx +│   ├── title.tsx +│   ├── ui +│   │   ├── accordion.tsx +│   │   ├── alert-dialog.tsx +│   │   ├── alert.tsx +│   │   ├── aspect-ratio.tsx +│   │   ├── avatar.tsx +│   │   ├── badge.tsx +│   │   ├── breadcrumb.tsx +│   │   ├── button.tsx +│   │   ├── calendar.tsx +│   │   ├── card.tsx +│   │   ├── carousel.tsx +│   │   ├── chart.tsx +│   │   ├── checkbox.tsx +│   │   ├── collapsible.tsx +│   │   ├── command.tsx +│   │   ├── context-menu.tsx +│   │   ├── dialog.tsx +│   │   ├── drawer.tsx +│   │   ├── dropdown-menu.tsx +│   │   ├── form.tsx +│   │   ├── hover-card.tsx +│   │   ├── input-otp.tsx +│   │   ├── input.tsx +│   │   ├── label.tsx +│   │   ├── menubar.tsx +│   │   ├── navigation-menu.tsx +│   │   ├── pagination.tsx +│   │   ├── popover.tsx +│   │   ├── progress.tsx +│   │   ├── radio-group.tsx +│   │   ├── resizable.tsx +│   │   ├── scroll-area.tsx +│   │   ├── select.tsx +│   │   ├── separator.tsx +│   │   ├── sheet.tsx +│   │   ├── skeleton.tsx +│   │   ├── slider.tsx +│   │   ├── sonner.tsx +│   │   ├── switch.tsx +│   │   ├── table.tsx +│   │   ├── tabs.tsx +│   │   ├── textarea.tsx +│   │   ├── toaster.tsx +│   │   ├── toast.tsx +│   │   ├── toggle-group.tsx +│   │   ├── toggle.tsx +│   │   ├── tooltip.tsx +│   │   └── use-toast.ts +│   └── ui-custom +│   ├── accordion.tsx +│   ├── command.tsx +│   ├── pagination.tsx +│   └── time-picker-input +│   ├── index.tsx +│   └── time-picker-utils.tsx +├── components.json +├── config +│   ├── dashboard.ts +│   ├── middleware.ts +│   └── site.ts +├── context +│   ├── app-provider.tsx +│   ├── auth-provider.tsx +│   ├── i18n-provider.tsx +│   ├── swr-provider.tsx +│   └── theme-provider.tsx +├── docs +│   ├── CONFIGURATION.md +│   ├── DEPLOYING.md +│   ├── EXAMPLES.md +│   ├── INSTALLATION.md +│   └── LINTER.md +├── hooks +│   ├── headers +│   │   ├── index.ts +│   │   └── url.ts +│   ├── i18next +│   │   ├── get-translation.ts +│   │   ├── index.ts +│   │   └── use-trans.tsx +│   ├── url +│   │   ├── index.ts +│   │   └── use-query-string.ts +│   └── use-auth.ts +├── i18next.config.ts +├── lib +│   ├── country-flag-icons.tsx +│   ├── dayjs.ts +│   ├── emblor.ts +│   ├── i18next.ts +│   ├── jsonwebtoken.ts +│   ├── lucide-icon.tsx +│   ├── nodemailer.ts +│   ├── redux +│   │   ├── hooks.ts +│   │   ├── persist-provider.tsx +│   │   ├── redux-provider.tsx +│   │   ├── storage.ts +│   │   ├── store-provider.tsx +│   │   └── store.ts +│   ├── slugify.ts +│   └── utils +│   ├── cache.ts +│   ├── cookie.ts +│   ├── dummy-text.ts +│   ├── error.ts +│   ├── fetcher.ts +│   ├── functions.ts +│   ├── http-status-codes.ts +│   ├── index.ts +│   ├── tailwind.ts +│   └── url.ts +├── LICENSE +├── LICENSE.md +├── managed_context +│   └── metadata.json +├── middleware.ts +├── next.config.js +├── next-env.d.ts +├── package.json +├── package-lock.json +├── postcss.config.js +├── public +│   ├── assets +│   │   ├── icons +│   │   │   ├── icon-192x192.png +│   │   │   ├── icon-256x256.png +│   │   │   ├── icon-384x384.png +│   │   │   ├── icon-512x512.png +│   │   │   └── icon.svg +│   │   └── images +│   │   └── main +│   │   ├── photo-1481277542470-605612bd2d61.jpg +│   │   ├── photo-1516455207990-7a41ce80f7ee.jpg +│   │   ├── photo-1517487881594-2787fef5ebf7.jpg +│   │   ├── photo-1519710164239-da123dc03ef4.jpg +│   │   ├── photo-1523413651479-597eb2da0ad6.jpg +│   │   ├── photo-1525097487452-6278ff080c31.jpg +│   │   ├── photo-1530731141654-5993c3016c77.jpg +│   │   ├── photo-1549388604-817d15aa0110.jpg +│   │   ├── photo-1563298723-dcfebaa392e3.jpg +│   │   ├── photo-1574180045827-681f8a1a9622.jpg +│   │   ├── photo-1588436706487-9d55d73a39e3.jpg +│   │   └── photo-1597262975002-c5c3b14bbd62.jpg +│   ├── data +│   │   ├── countries +│   │   │   ├── countries.json +│   │   │   ├── languages-all.json +│   │   │   └── languages.json +│   │   └── country-flag-icons +│   │   ├── AC.svg +│   │   ├── AD.svg +│   │   ├── AE.svg +│   │   ├── AF.svg +│   │   ├── AG.svg +│   │   ├── AI.svg +│   │   ├── AL.svg +│   │   ├── AM.svg +│   │   ├── AO.svg +│   │   ├── AQ.svg +│   │   ├── AR.svg +│   │   ├── AS.svg +│   │   ├── AT.svg +│   │   ├── AU.svg +│   │   ├── AW.svg +│   │   ├── AX.svg +│   │   ├── AZ.svg +│   │   ├── BA.svg +│   │   ├── BB.svg +│   │   ├── BD.svg +│   │   ├── BE.svg +│   │   ├── BF.svg +│   │   ├── BG.svg +│   │   ├── BH.svg +│   │   ├── BI.svg +│   │   ├── BJ.svg +│   │   ├── BL.svg +│   │   ├── BM.svg +│   │   ├── BN.svg +│   │   ├── BO.svg +│   │   ├── BQ.svg +│   │   ├── BR.svg +│   │   ├── BS.svg +│   │   ├── BT.svg +│   │   ├── BV.svg +│   │   ├── BW.svg +│   │   ├── BY.svg +│   │   ├── BZ.svg +│   │   ├── CA.svg +│   │   ├── CC.svg +│   │   ├── CD.svg +│   │   ├── CF.svg +│   │   ├── CG.svg +│   │   ├── CH.svg +│   │   ├── CI.svg +│   │   ├── CK.svg +│   │   ├── CL.svg +│   │   ├── CM.svg +│   │   ├── CN.svg +│   │   ├── CO.svg +│   │   ├── CR.svg +│   │   ├── CU.svg +│   │   ├── CV.svg +│   │   ├── CW.svg +│   │   ├── CX.svg +│   │   ├── CY.svg +│   │   ├── CZ.svg +│   │   ├── DE.svg +│   │   ├── DJ.svg +│   │   ├── DK.svg +│   │   ├── DM.svg +│   │   ├── DO.svg +│   │   ├── DZ.svg +│   │   ├── EC.svg +│   │   ├── EE.svg +│   │   ├── EG.svg +│   │   ├── EH.svg +│   │   ├── ER.svg +│   │   ├── ES.svg +│   │   ├── ET.svg +│   │   ├── EU.svg +│   │   ├── FI.svg +│   │   ├── FJ.svg +│   │   ├── FK.svg +│   │   ├── flags.css +│   │   ├── FM.svg +│   │   ├── FO.svg +│   │   ├── FR.svg +│   │   ├── GA.svg +│   │   ├── GB.svg +│   │   ├── GD.svg +│   │   ├── GE-AB.svg +│   │   ├── GE-OS.svg +│   │   ├── GE.svg +│   │   ├── GF.svg +│   │   ├── GG.svg +│   │   ├── GH.svg +│   │   ├── GI.svg +│   │   ├── GL.svg +│   │   ├── GM.svg +│   │   ├── GN.svg +│   │   ├── GP.svg +│   │   ├── GQ.svg +│   │   ├── GR.svg +│   │   ├── GS.svg +│   │   ├── GT.svg +│   │   ├── GU.svg +│   │   ├── GW.svg +│   │   ├── GY.svg +│   │   ├── HK.svg +│   │   ├── HM.svg +│   │   ├── HN.svg +│   │   ├── HR.svg +│   │   ├── HT.svg +│   │   ├── HU.svg +│   │   ├── IC.svg +│   │   ├── ID.svg +│   │   ├── IE.svg +│   │   ├── IL.svg +│   │   ├── IM.svg +│   │   ├── index.html +│   │   ├── IN.svg +│   │   ├── IO.svg +│   │   ├── IQ.svg +│   │   ├── IR.svg +│   │   ├── IS.svg +│   │   ├── IT.svg +│   │   ├── JE.svg +│   │   ├── JM.svg +│   │   ├── JO.svg +│   │   ├── JP.svg +│   │   ├── KE.svg +│   │   ├── KG.svg +│   │   ├── KH.svg +│   │   ├── KI.svg +│   │   ├── KM.svg +│   │   ├── KN.svg +│   │   ├── KP.svg +│   │   ├── KR.svg +│   │   ├── KW.svg +│   │   ├── KY.svg +│   │   ├── KZ.svg +│   │   ├── LA.svg +│   │   ├── LB.svg +│   │   ├── LC.svg +│   │   ├── LI.svg +│   │   ├── LK.svg +│   │   ├── LR.svg +│   │   ├── LS.svg +│   │   ├── LT.svg +│   │   ├── LU.svg +│   │   ├── LV.svg +│   │   ├── LY.svg +│   │   ├── MA.svg +│   │   ├── MC.svg +│   │   ├── MD.svg +│   │   ├── ME.svg +│   │   ├── MF.svg +│   │   ├── MG.svg +│   │   ├── MH.svg +│   │   ├── MK.svg +│   │   ├── ML.svg +│   │   ├── MM.svg +│   │   ├── MN.svg +│   │   ├── MO.svg +│   │   ├── MP.svg +│   │   ├── MQ.svg +│   │   ├── MR.svg +│   │   ├── MS.svg +│   │   ├── MT.svg +│   │   ├── MU.svg +│   │   ├── MV.svg +│   │   ├── MW.svg +│   │   ├── MX.svg +│   │   ├── MY.svg +│   │   ├── MZ.svg +│   │   ├── NA.svg +│   │   ├── NC.svg +│   │   ├── NE.svg +│   │   ├── NF.svg +│   │   ├── NG.svg +│   │   ├── NI.svg +│   │   ├── NL.svg +│   │   ├── NO.svg +│   │   ├── NP.svg +│   │   ├── NR.svg +│   │   ├── NU.svg +│   │   ├── NZ.svg +│   │   ├── OM.svg +│   │   ├── PA.svg +│   │   ├── PE.svg +│   │   ├── PF.svg +│   │   ├── PG.svg +│   │   ├── PH.svg +│   │   ├── PK.svg +│   │   ├── PL.svg +│   │   ├── PM.svg +│   │   ├── PN.svg +│   │   ├── PR.svg +│   │   ├── PS.svg +│   │   ├── PT.svg +│   │   ├── PW.svg +│   │   ├── PY.svg +│   │   ├── QA.svg +│   │   ├── RE.svg +│   │   ├── RO.svg +│   │   ├── RS.svg +│   │   ├── RU.svg +│   │   ├── RW.svg +│   │   ├── SA.svg +│   │   ├── SB.svg +│   │   ├── SC.svg +│   │   ├── SD.svg +│   │   ├── SE.svg +│   │   ├── SG.svg +│   │   ├── SH.svg +│   │   ├── SI.svg +│   │   ├── SJ.svg +│   │   ├── SK.svg +│   │   ├── SL.svg +│   │   ├── SM.svg +│   │   ├── SN.svg +│   │   ├── SO.svg +│   │   ├── SR.svg +│   │   ├── SS.svg +│   │   ├── ST.svg +│   │   ├── SV.svg +│   │   ├── SX.svg +│   │   ├── SY.svg +│   │   ├── SZ.svg +│   │   ├── TA.svg +│   │   ├── TC.svg +│   │   ├── TD.svg +│   │   ├── TF.svg +│   │   ├── TG.svg +│   │   ├── TH.svg +│   │   ├── TJ.svg +│   │   ├── TK.svg +│   │   ├── TL.svg +│   │   ├── TM.svg +│   │   ├── TN.svg +│   │   ├── TO.svg +│   │   ├── TR.svg +│   │   ├── TT.svg +│   │   ├── TV.svg +│   │   ├── TW.svg +│   │   ├── TZ.svg +│   │   ├── UA.svg +│   │   ├── UG.svg +│   │   ├── UM.svg +│   │   ├── US.svg +│   │   ├── UY.svg +│   │   ├── UZ.svg +│   │   ├── VA.svg +│   │   ├── VC.svg +│   │   ├── VE.svg +│   │   ├── VG.svg +│   │   ├── VI.svg +│   │   ├── VN.svg +│   │   ├── VU.svg +│   │   ├── WF.svg +│   │   ├── WS.svg +│   │   ├── XK.svg +│   │   ├── YE.svg +│   │   ├── YT.svg +│   │   ├── ZA.svg +│   │   ├── ZM.svg +│   │   └── ZW.svg +│   ├── locales +│   │   ├── en +│   │   │   ├── components.json +│   │   │   ├── httpstatuscode.json +│   │   │   ├── translation.json +│   │   │   ├── zod-custom.json +│   │   │   └── zod.json +│   │   └── ko +│   │   ├── components.json +│   │   ├── httpstatuscode.json +│   │   ├── translation.json +│   │   ├── zod-custom.json +│   │   └── zod.json +│   ├── manifest.json +│   ├── next.svg +│   ├── sw.js +│   ├── vercel.svg +│   └── workbox-4754cb34.js +├── queries +│   ├── client +│   │   ├── emails.ts +│   │   ├── favorites.ts +│   │   ├── notifications.ts +│   │   ├── posts.ts +│   │   ├── statistics.ts +│   │   ├── tags.ts +│   │   └── users.ts +│   └── server +│   ├── auth.ts +│   ├── posts.ts +│   └── users.ts +├── README.md +├── screenshot.png +├── screenshots +│   ├── 01.main.png +│   ├── 02.signin.png +│   ├── 03.signup.png +│   ├── 04.forgot-password.png +│   ├── 05.reset-password.png +│   ├── 06.profile.png +│   ├── 07.favorites.png +│   ├── 08.posts.png +│   ├── 09.post.png +│   ├── 10.search.png +│   ├── 20.dashboard.png +│   ├── 30.posts.png +│   ├── 31.post.png +│   ├── 32.tags.png +│   ├── 33.tag.png +│   ├── 40.appearance.png +│   ├── 50.profile.png +│   ├── 60.account.png +│   ├── 61.notifications.png +│   ├── 62.emails.png +│   └── 63.security.png +├── store +│   ├── reducers +│   │   └── app-reducer.ts +│   └── root-reducer.ts +├── structure.txt +├── supabase +│   ├── client.ts +│   ├── middleware.ts +│   ├── schemas +│   │   ├── auth +│   │   │   └── users.sql +│   │   ├── cat.sh +│   │   ├── concatenated_output.sql +│   │   ├── cron +│   │   │   ├── job_scheduling.sql +│   │   │   └── pg_cron.sql +│   │   ├── public +│   │   │   ├── emails.sql +│   │   │   ├── favorites.sql +│   │   │   ├── notifications.sql +│   │   │   ├── postmeta.sql +│   │   │   ├── posts.sql +│   │   │   ├── post_tags.sql +│   │   │   ├── role_permissions.sql +│   │   │   ├── statistics.sql +│   │   │   ├── tagmeta.sql +│   │   │   ├── tags.sql +│   │   │   ├── usermeta.sql +│   │   │   ├── users.sql +│   │   │   └── votes.sql +│   │   └── storage +│   │   └── buckets.sql +│   ├── seed.sql +│   └── server.ts +├── tailwind.config.js +├── test_suite_analysis +│   └── metadata.json +├── tsconfig.json +├── types +│   ├── api.ts +│   ├── ckeditor5-react +│   │   └── index.d.ts +│   ├── database.ts +│   ├── index.d.ts +│   ├── supabase.ts +│   └── token.ts +└── _vercel.json + +124 directories, 678 files diff --git a/supabase/client.ts b/supabase/client.ts index 574280b..87d7b9a 100644 --- a/supabase/client.ts +++ b/supabase/client.ts @@ -1,14 +1,27 @@ -import { createBrowserClient } from '@supabase/ssr' -import { type Database } from '@/types/supabase' - -/** - * Setting up Server-Side Auth for Next.js - * - * @link https://supabase.com/docs/guides/auth/server-side/nextjs - */ -export function createClient() { - return createBrowserClient( +// supabase/client.ts +'use client' + +import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' +import { createClient as createRawClient } from '@supabase/supabase-js' +import type { Database } from '@/types/database' + +// Client Component client +export const createClient = () => { + return createClientComponentClient() +} + + +// Raw client (for direct API access) +export const createDirectClient = () => { + return createRawClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + auth: { + persistSession: false, + } + } ) } + + diff --git a/supabase/server.ts b/supabase/server.ts index 099c443..cd899bb 100644 --- a/supabase/server.ts +++ b/supabase/server.ts @@ -1,69 +1,11 @@ +// supabase/server.ts +import { createServerComponentClient } from '@supabase/auth-helpers-nextjs' import { cookies } from 'next/headers' -import { createServerClient, type CookieOptions } from '@supabase/ssr' -import { createClient as createAuthClient } from '@supabase/supabase-js' -import { type Database } from '@/types/supabase' +import type { Database } from '@/types/database' -/** - * Setting up Server-Side Auth for Next.js - * - * @link https://supabase.com/docs/guides/auth/server-side/nextjs - * @link https://supabase.com/docs/reference/javascript/auth-api - */ -export function createClient() { +export const createServerClient = () => { const cookieStore = cookies() - - const supabase = createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - { - cookies: { - get(name: string) { - return cookieStore.get(name)?.value - }, - set(name: string, value: string, options: CookieOptions) { - try { - cookieStore.set({ name, value, ...options }) - } catch (error) { - // The `set` method was called from a Server Component. - // This can be ignored if you have middleware refreshing - // user sessions. - } - }, - remove(name: string, options: CookieOptions) { - try { - cookieStore.set({ name, value: '', ...options }) - } catch (error) { - // The `delete` method was called from a Server Component. - // This can be ignored if you have middleware refreshing - // user sessions. - } - }, - }, - } - ) - - return supabase -} - -/** - * Setting up Server-Side Auth Admin for Next.js - * - * @link https://supabase.com/docs/reference/javascript/admin-api - */ -export function createAdminClient() { - const supabase = createAuthClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY!, - { - auth: { - autoRefreshToken: false, - persistSession: false, - }, - } - ) - - // Access auth admin api - const adminAuthClient = supabase.auth.admin - - return adminAuthClient -} + return createServerComponentClient({ + cookies: () => cookieStore, + }) +} \ No newline at end of file diff --git a/test_suite_analysis/metadata.json b/test_suite_analysis/metadata.json new file mode 100644 index 0000000..2e0910c --- /dev/null +++ b/test_suite_analysis/metadata.json @@ -0,0 +1 @@ +{"current_schema_version":"0.0.1"} \ No newline at end of file