diff --git a/bun.lockb b/bun.lockb index fdccb78..c3abeae 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 4738558..4e3dfcd 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@supabase-cache-helpers/postgrest-react-query": "^1.13.4", "@supabase-cache-helpers/storage-react-query": "^1.3.5", "@supabase/ssr": "^0.6.1", - "@supabase/supabase-js": "^2.51.0", + "@supabase/supabase-js": "^2.52.0", "@t3-oss/env-nextjs": "^0.12.0", "@tanstack/react-query": "^5.83.0", "@tanstack/react-table": "^8.21.3", @@ -65,7 +65,7 @@ "import-in-the-middle": "^1.14.2", "input-otp": "^1.4.2", "lucide-react": "^0.522.0", - "next": "^15.4.1", + "next": "^15.4.2", "next-plausible": "^3.12.4", "next-themes": "^0.4.6", "postgres": "^3.4.7", @@ -86,22 +86,22 @@ "@tailwindcss/postcss": "^4.1.11", "@types/cors": "^2.8.19", "@types/express": "^5.0.3", - "@types/node": "^20.19.8", + "@types/node": "^20.19.9", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "drizzle-kit": "^0.30.6", "eslint": "^9.31.0", - "eslint-config-next": "^15.4.1", - "eslint-config-prettier": "^10.1.5", + "eslint-config-next": "^15.4.2", + "eslint-config-prettier": "^10.1.8", "eslint-plugin-drizzle": "^0.2.3", - "eslint-plugin-prettier": "^5.5.1", + "eslint-plugin-prettier": "^5.5.3", "postcss": "^8.5.6", "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", "tailwindcss": "^4.1.11", "tw-animate-css": "^1.3.5", "typescript": "^5.8.3", - "typescript-eslint": "^8.37.0" + "typescript-eslint": "^8.38.0" }, "ct3aMetadata": { "initVersion": "7.39.3" diff --git a/src/app/(auth)/auth/success/page.tsx b/src/app/(auth)/auth/success/page.tsx index a18dad1..b361ae3 100644 --- a/src/app/(auth)/auth/success/page.tsx +++ b/src/app/(auth)/auth/success/page.tsx @@ -15,8 +15,9 @@ const AuthSuccessPage = () => { // Small delay to ensure state is updated setTimeout(() => router.push('/'), 100); }; - handleAuthSuccess() - .catch(error => console.error(`Error handling auth success: ${error}`)); + handleAuthSuccess().catch((error) => + console.error(`Error handling auth success: ${error}`), + ); }, [refreshUser, router]); return ( diff --git a/src/app/(auth)/forgot-password/page.tsx b/src/app/(auth)/forgot-password/page.tsx index 35d1646..9dcbc7a 100644 --- a/src/app/(auth)/forgot-password/page.tsx +++ b/src/app/(auth)/forgot-password/page.tsx @@ -4,7 +4,7 @@ import { ForgotPasswordCard } from '@/components/default/auth/cards/client'; const ForgotPasswordPage = () => { return (
- +
); }; diff --git a/src/app/(auth)/profile/page.tsx b/src/app/(auth)/profile/page.tsx index c80e708..3c73822 100644 --- a/src/app/(auth)/profile/page.tsx +++ b/src/app/(auth)/profile/page.tsx @@ -1,9 +1,6 @@ 'use client'; const ProfilePage = () => { - return ( -
-
- ); + return
; }; export default ProfilePage; diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index b3fe17f..c4e10ef 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -20,9 +20,11 @@ type GlobalErrorProps = { }; const GlobalError = ({ error, reset = undefined }: GlobalErrorProps) => { - useEffect(() => { Sentry.captureException(error) }, [error]); + useEffect(() => { + Sentry.captureException(error); + }, [error]); return ( - { template: '%s | Next Template', default: 'Next Template', }, - description: 'Gib\'s Next Template', + description: "Gib's Next Template", applicationName: 'Next Template', keywords: 'Next.js, Supabase, Tailwind, Tanstack, React, Query, T3, Gib', authors: [{ name: 'Gib', url: 'https://gbrown.org' }], @@ -217,7 +217,9 @@ const RootLayout = async ({ children, }: Readonly<{ children: React.ReactNode }>) => { const client = await SupabaseServer(); - const { data: { user } } = await getCurrentUser(client); + const { + data: { user }, + } = await getCurrentUser(client); return (
-
- {children} -
+
{children}
diff --git a/src/app/page.tsx b/src/app/page.tsx index b354cf8..80efd7c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,7 +3,7 @@ import { SignInCard } from '@/components/default/auth/cards/client'; const HomePage = () => { return (
- +
); }; diff --git a/src/components/default/auth/buttons/client/index.tsx b/src/components/default/auth/buttons/client/index.tsx index 2adf938..4d2631b 100644 --- a/src/components/default/auth/buttons/client/index.tsx +++ b/src/components/default/auth/buttons/client/index.tsx @@ -1,3 +1,9 @@ -export { SignInWithApple, type SignInWithAppleProps } from './sign-in-with-apple'; -export { SignInWithMicrosoft, type SignInWithMicrosoftProps } from './sign-in-with-microsoft'; +export { + SignInWithApple, + type SignInWithAppleProps, +} from './sign-in-with-apple'; +export { + SignInWithMicrosoft, + type SignInWithMicrosoftProps, +} from './sign-in-with-microsoft'; export { SignInLinkButton } from './sign-in-link'; diff --git a/src/components/default/auth/buttons/client/sign-in-with-apple.tsx b/src/components/default/auth/buttons/client/sign-in-with-apple.tsx index b99dff7..1dc0c97 100644 --- a/src/components/default/auth/buttons/client/sign-in-with-apple.tsx +++ b/src/components/default/auth/buttons/client/sign-in-with-apple.tsx @@ -25,11 +25,11 @@ export const SignInWithApple = ({ formProps, textProps, iconProps, -} : SignInWithAppleProps) => { +}: SignInWithAppleProps) => { const router = useRouter(); const { loading, refreshUser } = useAuth(); const [statusMessage, setStatusMessage] = useState(''); - const [ isLoading, setIsLoading ] = useState(false); + const [isLoading, setIsLoading] = useState(false); const supabase = SupabaseClient()!; const handleSignInWithApple = async (e: React.FormEvent) => { @@ -63,12 +63,18 @@ export const SignInWithApple = ({ className={cn('w-full', submitButtonProps?.className)} >
- -

+ +

Sign In with Apple

- + {statusMessage && } ); diff --git a/src/components/default/auth/buttons/client/sign-in-with-microsoft.tsx b/src/components/default/auth/buttons/client/sign-in-with-microsoft.tsx index 7fab135..1929e9e 100644 --- a/src/components/default/auth/buttons/client/sign-in-with-microsoft.tsx +++ b/src/components/default/auth/buttons/client/sign-in-with-microsoft.tsx @@ -25,11 +25,11 @@ export const SignInWithMicrosoft = ({ formProps, textProps, iconProps, -} : SignInWithMicrosoftProps) => { +}: SignInWithMicrosoftProps) => { const router = useRouter(); const { loading, refreshUser } = useAuth(); const [statusMessage, setStatusMessage] = useState(''); - const [ isLoading, setIsLoading ] = useState(false); + const [isLoading, setIsLoading] = useState(false); const supabase = SupabaseClient()!; const handleSignInWithMicrosoft = async (e: React.FormEvent) => { @@ -63,12 +63,18 @@ export const SignInWithMicrosoft = ({ className={cn('w-full', submitButtonProps?.className)} >
- -

+ +

Sign In with Microsoft

- + {statusMessage && } ); diff --git a/src/components/default/auth/buttons/client/sign-out.tsx b/src/components/default/auth/buttons/client/sign-out.tsx index 802b525..8cf3082 100644 --- a/src/components/default/auth/buttons/client/sign-out.tsx +++ b/src/components/default/auth/buttons/client/sign-out.tsx @@ -1,12 +1,15 @@ 'use client'; -import { SubmitButton, type SubmitButtonProps } from '@/components/default/forms'; +import { + SubmitButton, + type SubmitButtonProps, +} from '@/components/default/forms'; import { useRouter } from 'next/navigation'; import { useAuth } from '@/lib/hooks/context'; import { signOut } from '@/lib/queries'; import { SupabaseClient } from '@/utils/supabase'; import { cn } from '@/lib/utils'; -type SignOutProps = Omit +type SignOutProps = Omit; export const SignOut = ({ className, @@ -38,7 +41,7 @@ export const SignOut = ({ className={cn( 'text-[1.0rem] font-semibold \ hover:bg-red-700/60 dark:hover:bg-red-300/80', - className + className, )} > Sign Out diff --git a/src/components/default/auth/buttons/server/sign-in-with-apple.tsx b/src/components/default/auth/buttons/server/sign-in-with-apple.tsx index 8fcb06c..76d5fdd 100644 --- a/src/components/default/auth/buttons/server/sign-in-with-apple.tsx +++ b/src/components/default/auth/buttons/server/sign-in-with-apple.tsx @@ -21,7 +21,7 @@ export const SignInWithApple = async ({ formProps, textProps, iconProps, -} : SignInWithAppleProps) => { +}: SignInWithAppleProps) => { const supabase = await SupabaseServer(); const handleSignInWithApple = async () => { @@ -29,7 +29,9 @@ export const SignInWithApple = async ({ if (!supabase) throw new Error('Supabase client not found'); const result = await signInWithApple(supabase); if (result.error) - throw new Error(`Error signing in with Microsoft: ${result.error.message}`); + throw new Error( + `Error signing in with Microsoft: ${result.error.message}`, + ); else if (result.data.url) window.location.href = result.data.url; } catch (error) { console.error(error); @@ -48,8 +50,14 @@ export const SignInWithApple = async ({ className={cn('w-full', submitButtonProps?.className)} >
- -

+ +

Sign In with Apple

diff --git a/src/components/default/auth/buttons/server/sign-in-with-microsoft.tsx b/src/components/default/auth/buttons/server/sign-in-with-microsoft.tsx index a3a013e..90b0c40 100644 --- a/src/components/default/auth/buttons/server/sign-in-with-microsoft.tsx +++ b/src/components/default/auth/buttons/server/sign-in-with-microsoft.tsx @@ -21,7 +21,7 @@ export const SignInWithMicrosoft = async ({ formProps, textProps, iconProps, -} : SignInWithMicrosoftProps) => { +}: SignInWithMicrosoftProps) => { const supabase = await SupabaseServer(); const handleSignInWithMicrosoft = async () => { @@ -29,7 +29,9 @@ export const SignInWithMicrosoft = async ({ if (!supabase) throw new Error('Supabase client not found'); const result = await signInWithMicrosoft(supabase); if (result.error) - throw new Error(`Error signing in with Microsoft: ${result.error.message}`); + throw new Error( + `Error signing in with Microsoft: ${result.error.message}`, + ); else if (result.data.url) window.location.href = result.data.url; } catch (error) { console.error(error); @@ -48,8 +50,14 @@ export const SignInWithMicrosoft = async ({ className={cn('w-full', submitButtonProps?.className)} >
- -

+ +

Sign In with Microsoft

diff --git a/src/components/default/auth/buttons/server/sign-out.tsx b/src/components/default/auth/buttons/server/sign-out.tsx index 0802e1e..430aa2c 100644 --- a/src/components/default/auth/buttons/server/sign-out.tsx +++ b/src/components/default/auth/buttons/server/sign-out.tsx @@ -1,12 +1,18 @@ 'use server'; import 'server-only'; import { redirect } from 'next/navigation'; -import { SubmitButton, type SubmitButtonProps } from '@/components/default/forms'; +import { + SubmitButton, + type SubmitButtonProps, +} from '@/components/default/forms'; import { signOut } from '@/lib/queries'; import { SupabaseServer } from '@/utils/supabase'; import { cn } from '@/lib/utils'; -type SignOutProps = Omit +type SignOutProps = Omit< + SubmitButtonProps, + 'disabled' | 'onClick' | 'formAction' +>; export const SignOut = async ({ className, @@ -35,7 +41,7 @@ export const SignOut = async ({ className={cn( 'text-[1.0rem] font-semibold \ hover:bg-red-700/60 dark:hover:bg-red-300/80', - className + className, )} > Sign Out diff --git a/src/components/default/auth/cards/client/forgot-password.tsx b/src/components/default/auth/cards/client/forgot-password.tsx index 3c8e4df..de335c7 100644 --- a/src/components/default/auth/cards/client/forgot-password.tsx +++ b/src/components/default/auth/cards/client/forgot-password.tsx @@ -27,7 +27,7 @@ import { cn } from '@/lib/utils'; const forgotPasswordFormSchema = z.object({ email: z.string().email({ - message: 'Please enter a valid email address.' + message: 'Please enter a valid email address.', }), }); @@ -65,10 +65,12 @@ export const ForgotPasswordCard = ({ }); useEffect(() => { - if (isAuthenticated) router.push('/') + if (isAuthenticated) router.push('/'); }, [isAuthenticated, router]); - const handleForgotPassword = async (values: z.infer) => { + const handleForgotPassword = async ( + values: z.infer, + ) => { try { setStatusMessage(''); const formData = new FormData(); @@ -100,7 +102,10 @@ export const ForgotPasswordCard = ({ Don't have an account?{' '} )} /> - + Reset Password {statusMessage && diff --git a/src/components/default/auth/cards/client/sign-in.tsx b/src/components/default/auth/cards/client/sign-in.tsx index 8a60317..02f130b 100755 --- a/src/components/default/auth/cards/client/sign-in.tsx +++ b/src/components/default/auth/cards/client/sign-in.tsx @@ -36,10 +36,10 @@ import { cn } from '@/lib/utils'; const signInFormSchema = z.object({ email: z.string().email({ - message: 'Please enter a valid email address.' + message: 'Please enter a valid email address.', }), password: z.string().min(8, { - message: 'Password must be at least 8 characters.' + message: 'Password must be at least 8 characters.', }), }); @@ -71,8 +71,10 @@ type SignInCardProps = { cardProps?: ComponentProps; formProps?: Omit, 'onSubmit'>; formLabelProps?: ComponentProps; - submitButtonProps?: Omit, - 'pendingText' | 'disabled'>; + submitButtonProps?: Omit< + ComponentProps, + 'pendingText' | 'disabled' + >; signInWithAppleProps?: SignInWithAppleProps; signInWithMicrosoftProps?: SignInWithMicrosoftProps; }; @@ -89,7 +91,6 @@ export const SignInCard = ({ signInWithAppleProps, signInWithMicrosoftProps, }: SignInCardProps) => { - const router = useRouter(); const { isAuthenticated, loading, refreshUser } = useAuth(); const [statusMessage, setStatusMessage] = useState(''); @@ -154,10 +155,7 @@ export const SignInCard = ({ {...containerProps} className={cn('p-4 bg-card/25 min-h-[720px]', containerProps?.className)} > - + Sign In @@ -196,7 +194,10 @@ export const SignInCard = ({
Password - - Forgot Password? - + Forgot Password? ) : ( - ))} + ))} Sign In @@ -278,10 +275,11 @@ export const SignInCard = ({ @@ -430,14 +432,14 @@ export const SignInCard = ({ ) : ( - ))} + ))} Sign Up @@ -451,10 +453,11 @@ export const SignInCard = ({ ); }; - diff --git a/src/components/default/auth/forms/client/profile/avatar-upload.tsx b/src/components/default/auth/forms/client/profile/avatar-upload.tsx index 11d535b..a80b8de 100755 --- a/src/components/default/auth/forms/client/profile/avatar-upload.tsx +++ b/src/components/default/auth/forms/client/profile/avatar-upload.tsx @@ -2,11 +2,7 @@ import { useFileUpload } from '@/lib/hooks'; import { useAuth } from '@/lib/hooks/context'; import { SupabaseClient } from '@/utils/supabase'; -import { - BasedAvatar, - Card, - CardContent, -} from '@/components/ui'; +import { BasedAvatar, Card, CardContent } from '@/components/ui'; import { Loader2, Pencil, Upload } from 'lucide-react'; import type { ComponentProps, ChangeEvent } from 'react'; import { toast } from 'sonner'; @@ -34,11 +30,10 @@ export const AvatarUpload = ({ }: AvatarUploadProps) => { const { profile, isAuthenticated } = useAuth(); const client = SupabaseClient()!; - const { - isUploading, - fileInputRef, - uploadAvatarMutation - } = useFileUpload(client, 'avatars'); + const { isUploading, fileInputRef, uploadAvatarMutation } = useFileUpload( + client, + 'avatars', + ); const handleAvatarClick = () => { if (!isAuthenticated) { @@ -54,11 +49,12 @@ export const AvatarUpload = ({ if (!file) throw new Error('No file selected!'); if (!client) throw new Error('Supabase client not found!'); if (!isAuthenticated) throw new Error('User is not authenticated!'); - if (!file.type.startsWith('image/')) throw new Error('File is not an image!'); + if (!file.type.startsWith('image/')) + throw new Error('File is not an image!'); if (file.size > 8 * 1024 * 1024) throw new Error('File is too large!'); - const avatarPath = profile?.avatar_url ?? - `${profile?.id}.${file.name.split('.').pop()}`; + const avatarPath = + profile?.avatar_url ?? `${profile?.id}.${file.name.split('.').pop()}`; const avatarUrl = await uploadAvatarMutation.mutateAsync({ file, @@ -70,26 +66,25 @@ export const AvatarUpload = ({ replace: avatarPath, }); if (avatarUrl) await onAvatarUploaded(avatarUrl); - } catch (error) { toast.error(`Error: ${error as string}`); } }; return ( - +
@@ -124,7 +121,8 @@ export const AvatarUpload = ({ {...iconProps} className={cn( 'text-white opacity-100 group-hover:opacity-0\ - transition-opacity', iconProps?.className + transition-opacity', + iconProps?.className, )} /> diff --git a/src/components/default/auth/forms/client/profile/profile-form.tsx b/src/components/default/auth/forms/client/profile/profile-form.tsx index 14d0cdc..cb9c7cb 100644 --- a/src/components/default/auth/forms/client/profile/profile-form.tsx +++ b/src/components/default/auth/forms/client/profile/profile-form.tsx @@ -28,9 +28,7 @@ type ProfileFormProps = { onSubmit: (values: z.infer) => Promise; }; -export const ProfileForm = ({ - onSubmit, -}: ProfileFormProps) => { +export const ProfileForm = ({ onSubmit }: ProfileFormProps) => { const { profile, loading } = useAuth(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -48,14 +46,10 @@ export const ProfileForm = ({ }); }, [profile, form]); - const handleSubmit = async (values: z.infer) => { - await onSubmit(values); - }; - return ( - + Promise>; + onSubmit: (values: z.infer) => Promise>; message?: string; + cardHeaderProps?: ComponentProps; + cardTitleProps?: ComponentProps; + cardDescriptionProps?: ComponentProps; + cardContentProps?: ComponentProps; + formProps?: Omit, 'onSubmit'>; + formLabelProps?: ComponentProps; + inputProps?: Omit, 'type'>; + formDescriptionProps?: ComponentProps; + formMessageProps?: ComponentProps; + statusMessageProps?: Omit, 'message'>; + submitButtonProps?: Omit, 'disabled'>; }; export const ResetPasswordForm = ({ onSubmit, message, + cardHeaderProps, + cardTitleProps, + cardDescriptionProps, + cardContentProps, + formProps, + formLabelProps, + inputProps, + formDescriptionProps, + formMessageProps, + statusMessageProps, + submitButtonProps, }: ResetPasswordFormProps) => { const { loading } = useAuth(); const [statusMessage, setStatusMessage] = useState(message ?? ''); @@ -41,14 +78,96 @@ export const ResetPasswordForm = ({ const handleSubmit = async (values: z.infer) => { try { - const formData = new FormData(); - formData.append('password', values.password); - formData.append('confirmPassword', values.confirmPassword); - await onSubmit(formData); - + const { error } = await onSubmit(values); + if (error) throw new Error(error.message); + setStatusMessage('Password reset successfully.'); } catch (error) { - + setStatusMessage(`Error: ${error as string}`); } - } + }; + return ( + <> + + + Change Password + + + Update your password to keep your account secure + + + + + + ( + + New Password + + + + + Enter your new password. Must be at least 8 characters. + + + + )} + /> + ( + + Confirm Password + + + + + Please re-enter your new password to confirm. + + + + )} + /> + {statusMessage && + (statusMessage.includes('Error') || + statusMessage.includes('error') || + statusMessage.includes('failed') || + statusMessage.includes('invalid') ? ( + + ) : ( + + ))} +
+ + Update Password + +
+ + +
+ + ); }; diff --git a/src/components/default/forms/status-message.tsx b/src/components/default/forms/status-message.tsx index 52cabf3..72a39ae 100644 --- a/src/components/default/forms/status-message.tsx +++ b/src/components/default/forms/status-message.tsx @@ -1,10 +1,7 @@ import { type ComponentProps } from 'react'; import { cn } from '@/lib/utils'; -type Message = -| { success: string} -| { error: string} -| { message: string} +type Message = { success: string } | { error: string } | { message: string }; type StatusMessageProps = { message: Message; @@ -30,9 +27,9 @@ export const StatusMessage = ({
{message.success}
@@ -45,13 +42,7 @@ export const StatusMessage = ({ {message.error} )} - {'message' in message && ( -
- {message.message} -
- )} + {'message' in message &&
{message.message}
} ); }; diff --git a/src/components/default/forms/submit-button.tsx b/src/components/default/forms/submit-button.tsx index 7fdd83c..1df9cdc 100644 --- a/src/components/default/forms/submit-button.tsx +++ b/src/components/default/forms/submit-button.tsx @@ -44,7 +44,7 @@ export const SubmitButton = ({

) : ( - children + children )} ); diff --git a/src/components/default/layout/header/index.tsx b/src/components/default/layout/header/index.tsx index c20fdd3..fd03cb6 100644 --- a/src/components/default/layout/header/index.tsx +++ b/src/components/default/layout/header/index.tsx @@ -1,7 +1,11 @@ 'use client'; import Image from 'next/image'; import Link from 'next/link'; -import { ThemeToggle, type ThemeToggleProps, useAuth } from '@/lib/hooks/context'; +import { + ThemeToggle, + type ThemeToggleProps, + useAuth, +} from '@/lib/hooks/context'; import { cn } from '@/lib/utils'; import { AvatarDropdown } from './avatar-dropdown'; import { type ComponentProps } from 'react'; @@ -11,10 +15,7 @@ type Props = { themeToggleProps?: ThemeToggleProps; }; -const Header = ({ - headerProps, - themeToggleProps, -}: Props) => { +const Header = ({ headerProps, themeToggleProps }: Props) => { const { isAuthenticated } = useAuth(); const Controls = () => ( @@ -28,7 +29,7 @@ const Header = ({ ...themeToggleProps?.buttonProps, }} /> - {isAuthenticated && ( )} + {isAuthenticated && } ); @@ -41,7 +42,6 @@ const Header = ({ )} >
- {/* Left spacer for perfect centering */}
@@ -76,10 +76,8 @@ const Header = ({
-
); - }; export default Header; diff --git a/src/components/ui/based-avatar.tsx b/src/components/ui/based-avatar.tsx index 581489e..ec0fcee 100644 --- a/src/components/ui/based-avatar.tsx +++ b/src/components/ui/based-avatar.tsx @@ -34,7 +34,11 @@ const BasedAvatar = ({ {...props} > {src ? ( - + ) : ( + )} )} diff --git a/src/lib/hooks/context/use-auth.tsx b/src/lib/hooks/context/use-auth.tsx index 86a3dd2..23ac36e 100755 --- a/src/lib/hooks/context/use-auth.tsx +++ b/src/lib/hooks/context/use-auth.tsx @@ -1,10 +1,5 @@ 'use client'; -import React, { - createContext, - useContext, - useEffect, - useState -} from 'react'; +import React, { createContext, useContext, useEffect, useState } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery as useSupabaseQuery, @@ -40,13 +35,12 @@ export const AuthContextProvider = ({ const [user, setUser] = useState(initialUser ?? null); // User query with initial data - const { - data: userData, - isLoading: userLoading, - } = useQuery({ + const { data: userData, isLoading: userLoading } = useQuery({ queryKey: ['auth', 'user'], queryFn: async () => { - const { data: { user } } = await supabase.auth.getUser(); + const { + data: { user }, + } = await supabase.auth.getUser(); return user; }, initialData: initialUser, @@ -54,10 +48,7 @@ export const AuthContextProvider = ({ }); // Profile query using Supabase Cache Helpers - const { - data: profileData, - isLoading: profileLoading, - } = useSupabaseQuery( + const { data: profileData, isLoading: profileLoading } = useSupabaseQuery( supabase .from('profiles') .select('*') @@ -65,7 +56,7 @@ export const AuthContextProvider = ({ .single(), { enabled: !!userData?.id, - } + }, ); // Update profile mutation @@ -75,21 +66,26 @@ export const AuthContextProvider = ({ '*', { onSuccess: () => toast.success('Profile updated successfully!'), - onError: (error) => toast.error(`Failed to update profile: ${error.message}`), - } + onError: (error) => + toast.error(`Failed to update profile: ${error.message}`), + }, ); // Auth state listener useEffect(() => { - const { data: { subscription } } = supabase.auth.onAuthStateChange( - async (event, session) => { - setUser(session?.user ?? null); + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange(async (event, session) => { + setUser(session?.user ?? null); - if (event === 'SIGNED_IN' || event === 'SIGNED_OUT' || event === 'TOKEN_REFRESHED') { - await queryClient.invalidateQueries({ queryKey: ['auth'] }); - } + if ( + event === 'SIGNED_IN' || + event === 'SIGNED_OUT' || + event === 'TOKEN_REFRESHED' + ) { + await queryClient.invalidateQueries({ queryKey: ['auth'] }); } - ); + }); return () => subscription.unsubscribe(); }, [supabase.auth, queryClient]); @@ -116,11 +112,7 @@ export const AuthContextProvider = ({ refreshUser, }; - return ( - - {children} - - ); + return {children}; }; export const useAuth = () => { diff --git a/src/lib/hooks/context/use-query.tsx b/src/lib/hooks/context/use-query.tsx index e25fc1e..64b24c4 100644 --- a/src/lib/hooks/context/use-query.tsx +++ b/src/lib/hooks/context/use-query.tsx @@ -14,17 +14,18 @@ const enum QueryErrorCodes { FETCH_AVATAR_FAILED = 'FETCH_AVATAR_FAILED', UPDATE_PROFILE_FAILED = 'UPDATE_PROFILE_FAILED', UPLOAD_PHOTO_FAILED = 'UPLOAD_PHOTO_FAILED', -}; +} const queryCacheOnError = (error: unknown, query: any) => { - const errorMessage = error instanceof Error ? error.message : error as string; + const errorMessage = + error instanceof Error ? error.message : (error as string); switch (query.meta?.errCode) { case QueryErrorCodes.FETCH_USER_FAILED: break; case QueryErrorCodes.FETCH_PROFILE_FAILED: break; case QueryErrorCodes.FETCH_AVATAR_FAILED: - console.warn('Failed to fetch avatar. User may not have one!') + console.warn('Failed to fetch avatar. User may not have one!'); break; default: console.error('Query error:', error); @@ -38,13 +39,14 @@ const mutationCacheOnError = ( context: unknown, mutation: any, ) => { - const errorMessage = error instanceof Error ? error.message : error as string; + const errorMessage = + error instanceof Error ? error.message : (error as string); switch (mutation.meta?.errCode) { case QueryErrorCodes.UPDATE_PROFILE_FAILED: - toast.error(`Failed to update user profile: ${errorMessage}`) + toast.error(`Failed to update user profile: ${errorMessage}`); break; case QueryErrorCodes.UPLOAD_PHOTO_FAILED: - toast.error(`Failed to upload photo: ${errorMessage}`) + toast.error(`Failed to upload photo: ${errorMessage}`); break; default: console.error('Mutation error:', error); @@ -52,7 +54,6 @@ const mutationCacheOnError = ( } }; - const QueryClientProvider = ({ children }: { children: React.ReactNode }) => { const [queryClient] = useState( () => @@ -72,9 +73,13 @@ const QueryClientProvider = ({ children }: { children: React.ReactNode }) => { gcTime: Infinity, }, }, - }) - ) - return {children} + }), + ); + return ( + + {children} + + ); }; export { QueryClientProvider, QueryErrorCodes }; diff --git a/src/lib/hooks/context/use-theme.tsx b/src/lib/hooks/context/use-theme.tsx index ea37d15..ebd12ea 100644 --- a/src/lib/hooks/context/use-theme.tsx +++ b/src/lib/hooks/context/use-theme.tsx @@ -1,9 +1,5 @@ 'use client'; -import { - useEffect, - useState, - type ComponentProps, -} from 'react'; +import { useEffect, useState, type ComponentProps } from 'react'; import { ThemeProvider as NextThemesProvider } from 'next-themes'; import { Moon, Sun } from 'lucide-react'; import { useTheme } from 'next-themes'; @@ -14,10 +10,11 @@ const ThemeProvider = ({ children, ...props }: ComponentProps) => { - const [mounted, setMounted] = useState(false); - useEffect(() => { setMounted(true) }, []); + useEffect(() => { + setMounted(true); + }, []); if (!mounted) return null; return {children}; @@ -28,15 +25,13 @@ type ThemeToggleProps = { buttonProps?: Omit, 'onClick'>; }; -const ThemeToggle = ({ - size = 1, - buttonProps, -}: ThemeToggleProps) => { - +const ThemeToggle = ({ size = 1, buttonProps }: ThemeToggleProps) => { const { setTheme, resolvedTheme } = useTheme(); const [mounted, setMounted] = useState(false); - useEffect(() => { setMounted(true) }, []); + useEffect(() => { + setMounted(true); + }, []); if (!mounted) { return ( diff --git a/src/lib/hooks/use-file-upload.ts b/src/lib/hooks/use-file-upload.ts index 9543f03..cd69fa8 100644 --- a/src/lib/hooks/use-file-upload.ts +++ b/src/lib/hooks/use-file-upload.ts @@ -11,11 +11,13 @@ type UploadToStorageProps = { client: SBClientWithDatabase; file: File; bucket: string; - resize?: false | { - maxWidth?: number; - maxHeight?: number; - quality?: number; - }; + resize?: + | false + | { + maxWidth?: number; + maxHeight?: number; + quality?: number; + }; replace?: false | string; }; @@ -26,66 +28,70 @@ const useFileUpload = (client: SBClientWithDatabase, bucket: string) => { const queryClient = useQueryClient(); // Initialize the upload hook at the top level - const { mutateAsync: upload } = useUpload( - client.storage.from(bucket), - { - buildFileName: ({ fileName, path }) => path ?? fileName, - } + const { mutateAsync: upload } = useUpload(client.storage.from(bucket), { + buildFileName: ({ fileName, path }) => path ?? fileName, + }); + + const uploadToStorage = useCallback( + async ({ + file, + resize = false, + replace = false, + }: Omit) => { + try { + if (!isAuthenticated) + throw new Error('Error: User is not authenticated!'); + + setIsUploading(true); + + let fileToUpload = file; + if (resize && file.type.startsWith('image/')) + fileToUpload = await resizeImage({ file, options: resize }); + + const fileName = + replace || + `${Date.now()}-${profile?.id}.${file.name.split('.').pop()}`; + + // Create a file object with the custom path + const fileWithPath = Object.assign(fileToUpload, { + webkitRelativePath: fileName, + }); + + const uploadResult = await upload({ files: [fileWithPath] }); + + if (!uploadResult || uploadResult.length === 0) { + throw new Error('Upload failed: No result returned'); + } + + const uploadedFile = uploadResult[0]; + if (!uploadedFile || uploadedFile.error) { + throw new Error( + `Error uploading file: ${uploadedFile?.error.message ?? 'No uploaded file'}`, + ); + } + + // Get signed URL for the uploaded file + const { data: urlData, error: urlError } = await getSignedUrl({ + client, + bucket, + path: uploadedFile.data.path, + }); + + if (urlError) { + throw new Error(`Error getting signed URL: ${urlError.message}`); + } + + return { urlData, error: null }; + } catch (error) { + return { data: null, error }; + } finally { + setIsUploading(false); + if (fileInputRef.current) fileInputRef.current.value = ''; + } + }, + [client, bucket, upload, isAuthenticated, profile?.id], ); - const uploadToStorage = useCallback(async ({ - file, - resize = false, - replace = false, - }: Omit) => { - try { - if (!isAuthenticated) - throw new Error('Error: User is not authenticated!'); - - setIsUploading(true); - - let fileToUpload = file; - if (resize && file.type.startsWith('image/')) - fileToUpload = await resizeImage({ file, options: resize }); - - const fileName = replace || `${Date.now()}-${profile?.id}.${file.name.split('.').pop()}`; - - // Create a file object with the custom path - const fileWithPath = Object.assign(fileToUpload, { - webkitRelativePath: fileName, - }); - - const uploadResult = await upload({ files: [fileWithPath]}); - - if (!uploadResult || uploadResult.length === 0) { - throw new Error('Upload failed: No result returned'); - } - - const uploadedFile = uploadResult[0]; - if (!uploadedFile || uploadedFile.error) { - throw new Error(`Error uploading file: ${uploadedFile?.error.message ?? 'No uploaded file'}`); - } - - // Get signed URL for the uploaded file - const { data: urlData, error: urlError } = await getSignedUrl({ - client, - bucket, - path: uploadedFile.data.path, - }); - - if (urlError) { - throw new Error(`Error getting signed URL: ${urlError.message}`); - } - - return { urlData, error: null }; - } catch (error) { - return { data: null, error }; - } finally { - setIsUploading(false); - if (fileInputRef.current) fileInputRef.current.value = ''; - } - }, [client, bucket, upload, isAuthenticated, profile?.id]); - const uploadMutation = useMutation({ mutationFn: uploadToStorage, onSuccess: (result) => { @@ -96,38 +102,51 @@ const useFileUpload = (client: SBClientWithDatabase, bucket: string) => { } }, onError: (error) => { - toast.error(`Upload failed: ${error instanceof Error ? error.message : error}`); + toast.error( + `Upload failed: ${error instanceof Error ? error.message : error}`, + ); }, meta: { errCode: QueryErrorCodes.UPLOAD_PHOTO_FAILED }, }); const uploadAvatarMutation = useMutation({ - mutationFn: async (props: Omit) => { + mutationFn: async ( + props: Omit, + ) => { const { data, error } = await uploadToStorage(props); if (error) throw new Error(`Error uploading avatar: ${error as string}`); return data; }, onSuccess: (avatarUrl) => { - queryClient.invalidateQueries({ queryKey: ['auth'] }) - .catch((error) => console.error('Error invalidating auth query:', error)); + queryClient + .invalidateQueries({ queryKey: ['auth'] }) + .catch((error) => + console.error('Error invalidating auth query:', error), + ); queryClient.setQueryData(['auth, user'], (oldUser: User) => oldUser); if (profile?.id) { - queryClient.setQueryData(['profiles', profile.id], (oldProfile: Profile) => ({ - ...oldProfile, - avatar_url: avatarUrl, - updated_at: new Date().toISOString(), - })); + queryClient.setQueryData( + ['profiles', profile.id], + (oldProfile: Profile) => ({ + ...oldProfile, + avatar_url: avatarUrl, + updated_at: new Date().toISOString(), + }), + ); } toast.success('Avatar uploaded successfully!'); }, onError: (error) => { - toast.error(`Avatar upload failed: ${error instanceof Error ? error.message : error}`); + toast.error( + `Avatar upload failed: ${error instanceof Error ? error.message : error}`, + ); }, meta: { errCode: QueryErrorCodes.UPLOAD_PHOTO_FAILED }, }); return { - isUploading: isUploading || uploadMutation.isPending || uploadAvatarMutation.isPending, + isUploading: + isUploading || uploadMutation.isPending || uploadAvatarMutation.isPending, fileInputRef, uploadToStorage, uploadMutation, diff --git a/src/lib/queries/auth.ts b/src/lib/queries/auth.ts index b56d9a4..29cb8b7 100644 --- a/src/lib/queries/auth.ts +++ b/src/lib/queries/auth.ts @@ -1,4 +1,8 @@ -import { type Profile, type SBClientWithDatabase, type UserRecord } from '@/utils/supabase'; +import { + type Profile, + type SBClientWithDatabase, + type UserRecord, +} from '@/utils/supabase'; const signUp = (client: SBClientWithDatabase, formData: FormData) => { const full_name = formData.get('name') as string; @@ -14,8 +18,8 @@ const signUp = (client: SBClientWithDatabase, formData: FormData) => { full_name, email, provider: 'email', - } - } + }, + }, }); }; @@ -62,24 +66,21 @@ const resetPassword = (client: SBClientWithDatabase, formData: FormData) => { const signOut = (client: SBClientWithDatabase) => { return client.auth.signOut(); -} +}; const getCurrentUser = (client: SBClientWithDatabase) => { return client.auth.getUser(); }; const getProfile = (client: SBClientWithDatabase, userId: string) => { - return client - .from(`profiles`) - .select(`*`) - .eq(`id`, userId) - .single(); + return client.from(`profiles`).select(`*`).eq(`id`, userId).single(); }; const getUserWithStatus = (client: SBClientWithDatabase, userId: string) => { return client .from(`profiles`) - .select(` + .select( + ` id, updated_at, email, @@ -91,7 +92,8 @@ const getUserWithStatus = (client: SBClientWithDatabase, userId: string) => { created_at, updated_by:profiles!updated_by_id(*) ) - `) + `, + ) .eq(`id`, userId) .throwOnError() .single(); @@ -122,5 +124,5 @@ export { signInWithMicrosoft, signOut, signUp, - updateProfile + updateProfile, }; diff --git a/src/lib/queries/index.ts b/src/lib/queries/index.ts index 077a9f6..e620821 100644 --- a/src/lib/queries/index.ts +++ b/src/lib/queries/index.ts @@ -9,7 +9,7 @@ export { signInWithMicrosoft, signOut, signUp, - updateProfile + updateProfile, } from './auth'; export { deleteFiles, @@ -20,5 +20,5 @@ export { listFiles, resizeImage, uploadFile, - updateFile + updateFile, } from './storage'; diff --git a/src/lib/queries/storage.ts b/src/lib/queries/storage.ts index 658f575..15f636a 100755 --- a/src/lib/queries/storage.ts +++ b/src/lib/queries/storage.ts @@ -36,10 +36,7 @@ type ResizeImageProps = { }; }; -const getAvatarUrl = ( - client: SBClientWithDatabase, - path: string, -) => { +const getAvatarUrl = (client: SBClientWithDatabase, path: string) => { return getPublicUrl({ client, bucket: 'avatars', @@ -48,7 +45,7 @@ const getAvatarUrl = ( width: 128, height: 128, quality: 0.8, - } + }, }).data.publicUrl; }; @@ -61,12 +58,12 @@ const getPublicUrl = ({ }: GetStorageProps) => { return client.storage .from(bucket) - .getPublicUrl(path, { download, transform}); + .getPublicUrl(path, { download, transform }); }; const getSignedAvatarUrl = ( client: SBClientWithDatabase, - avatarUrl: string + avatarUrl: string, ) => { return getSignedUrl({ client, @@ -90,7 +87,7 @@ const getSignedUrl = ({ }: GetStorageProps) => { return client.storage .from(bucket) - .createSignedUrl(path, seconds, { download, transform}); + .createSignedUrl(path, seconds, { download, transform }); }; const uploadFile = ({ @@ -100,9 +97,7 @@ const uploadFile = ({ file, options = {}, }: UploadStorageProps) => { - return client.storage - .from(bucket) - .upload(path, file, options); + return client.storage.from(bucket).upload(path, file, options); }; const updateFile = ({ @@ -114,9 +109,7 @@ const updateFile = ({ upsert: true, }, }: UploadStorageProps) => { - return client.storage - .from(bucket) - .update(path, file, options); + return client.storage.from(bucket).update(path, file, options); }; const deleteFiles = ({ @@ -127,7 +120,7 @@ const deleteFiles = ({ client: SBClientWithDatabase; bucket: string; path: string[]; - }) => { +}) => { return client.storage.from(bucket).remove(path); }; @@ -141,9 +134,9 @@ const listFiles = ({ bucket: string; path?: string; options?: { - limit?: number; - offset?: number; - sortBy?: { column: string, order: 'asc' | 'desc' }; + limit?: number; + offset?: number; + sortBy?: { column: string; order: 'asc' | 'desc' }; }; }) => { return client.storage.from(bucket).list(path, options); @@ -190,7 +183,7 @@ const resizeImage = async ({ resolve(resizedFile); }, 'image/jpeg', - (options.quality && options.quality < 1 && options.quality > 0) + options.quality && options.quality < 1 && options.quality > 0 ? options.quality : 0.8, ); @@ -208,5 +201,5 @@ export { listFiles, resizeImage, uploadFile, - updateFile + updateFile, }; diff --git a/src/middleware.ts b/src/middleware.ts index a2c1306..97d496f 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,11 +1,21 @@ -import { type NextRequest } from 'next/server'; +import { type NextRequest, NextResponse } from 'next/server'; import { updateSession } from '@/utils/supabase/middleware'; import { banSuspiciousIPs } from '@/utils/ban-suspicious-ips'; export const middleware = async (request: NextRequest) => { const banResponse = banSuspiciousIPs(request); if (banResponse) return banResponse; - return await updateSession(request); + const response = await updateSession(request); + const newResponse = NextResponse.next({ + request: { headers: new Headers(request.headers) }, + }); + + if (response.headers) { + response.headers.forEach((value, key) => { + newResponse.headers.set(key, value); + }); + } + return response; }; export const config = { diff --git a/src/utils/ban-suspicious-ips.ts b/src/utils/ban-suspicious-ips.ts index 7216c4d..6e60c32 100644 --- a/src/utils/ban-suspicious-ips.ts +++ b/src/utils/ban-suspicious-ips.ts @@ -1,7 +1,8 @@ import { type NextRequest, NextResponse } from 'next/server'; -// In-memory store for tracking IPs (use Redis in production) +// In-memory stores for tracking IPs (use Redis in production) const ipAttempts = new Map(); +const ip404Attempts = new Map(); const bannedIPs = new Set(); // Ban Arctic Wolf Explicitly @@ -9,6 +10,7 @@ bannedIPs.add('::ffff:10.0.1.49'); // Suspicious patterns that indicate malicious activity const MALICIOUS_PATTERNS = [ + // Your existing patterns /web-inf/i, /\.jsp/i, /\.php/i, @@ -28,6 +30,37 @@ const MALICIOUS_PATTERNS = [ /\.%00/i, /\.\./, /lcgi/i, + + // New patterns from your logs + /\/appliance\//i, + /bomgar/i, + /netburner-logo/i, + /\/ui\/images\//i, + /logon_merge/i, + /logon_t\.gif/i, + /login_top\.gif/i, + /theme1\/images/i, + /\.well-known\/acme-challenge\/.*\.jpg$/i, + /\.well-known\/pki-validation\/.*\.jpg$/i, + + // Path traversal and system file access patterns + /\/etc\/passwd/i, + /\/etc%2fpasswd/i, + /\/etc%5cpasswd/i, + /\/\/+etc/i, + /\\\\+.*etc/i, + /%2f%2f/i, + /%5c%5c/i, + /\/\/+/, + /\\\\+/, + /%00/i, + /%23/i, + + // Encoded path traversal attempts + /%2e%2e/i, + /%252e/i, + /%c0%ae/i, + /%c1%9c/i, ]; // Suspicious HTTP methods @@ -37,6 +70,10 @@ const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute const MAX_ATTEMPTS = 10; // Max suspicious requests per window const BAN_DURATION = 30 * 60 * 1000; // 30 minutes +// 404 rate limiting settings +const RATE_404_WINDOW = 2 * 60 * 1000; // 2 minutes +const MAX_404_ATTEMPTS = 10; // Max 404s before ban + const getClientIP = (request: NextRequest): string => { const forwarded = request.headers.get('x-forwarded-for'); const realIP = request.headers.get('x-real-ip'); @@ -64,17 +101,51 @@ const updateIPAttempts = (ip: string): boolean => { ipAttempts.set(ip, { count: 1, lastAttempt: now }); return false; } + attempts.count++; attempts.lastAttempt = now; + if (attempts.count > MAX_ATTEMPTS) { bannedIPs.add(ip); ipAttempts.delete(ip); - // Auto-unban after duration (in production, use a proper scheduler) + setTimeout(() => { bannedIPs.delete(ip); }, BAN_DURATION); + return true; } + + return false; +}; + +const update404Attempts = (ip: string): boolean => { + const now = Date.now(); + const attempts = ip404Attempts.get(ip); + + if (!attempts || now - attempts.lastAttempt > RATE_404_WINDOW) { + ip404Attempts.set(ip, { count: 1, lastAttempt: now }); + return false; + } + + attempts.count++; + attempts.lastAttempt = now; + + if (attempts.count > MAX_404_ATTEMPTS) { + bannedIPs.add(ip); + ip404Attempts.delete(ip); + + console.log( + `🔨 IP ${ip} banned for excessive 404 requests (${attempts.count} in ${RATE_404_WINDOW / 1000}s)`, + ); + + setTimeout(() => { + bannedIPs.delete(ip); + }, BAN_DURATION); + + return true; + } + return false; }; @@ -83,20 +154,48 @@ export const banSuspiciousIPs = (request: NextRequest): NextResponse | null => { const method = request.method; const ip = getClientIP(request); - if (bannedIPs.has(ip)) return new NextResponse('Access denied.', { status: 403 }); + // Check if IP is already banned + if (bannedIPs.has(ip)) { + return new NextResponse('Access denied.', { status: 403 }); + } const isSuspiciousPath = isPathSuspicious(pathname); const isSuspiciousMethod = isMethodSuspicious(method); + // Handle suspicious activity if (isSuspiciousPath || isSuspiciousMethod) { const shouldBan = updateIPAttempts(ip); + if (shouldBan) { console.log(`🔨 IP ${ip} has been banned for suspicious activity`); return new NextResponse('Access denied - IP banned. Please fuck off.', { status: 403, }); } + return new NextResponse('Not Found', { status: 404 }); } + + return null; +}; + +// Call this function when you detect a 404 response +export const handle404Response = ( + request: NextRequest, +): NextResponse | null => { + const ip = getClientIP(request); + + if (bannedIPs.has(ip)) { + return new NextResponse('Access denied.', { status: 403 }); + } + + const shouldBan = update404Attempts(ip); + + if (shouldBan) { + return new NextResponse('Access denied - IP banned for excessive 404s.', { + status: 403, + }); + } + return null; }; diff --git a/src/utils/supabase/client.ts b/src/utils/supabase/client.ts index f01dc5e..6349075 100644 --- a/src/utils/supabase/client.ts +++ b/src/utils/supabase/client.ts @@ -5,7 +5,7 @@ import type { Database, SBClientWithDatabase } from '@/utils/supabase'; let client: SBClientWithDatabase | undefined; const getSupbaseClient = (): SBClientWithDatabase | undefined => { - if (client) return client; + if (client) return client; client = createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, diff --git a/src/utils/supabase/database.types.ts b/src/utils/supabase/database.types.ts index 3f41d60..634ac02 100644 --- a/src/utils/supabase/database.types.ts +++ b/src/utils/supabase/database.types.ts @@ -4,197 +4,196 @@ export type Json = | boolean | null | { [key: string]: Json | undefined } - | Json[] + | Json[]; export type Database = { public: { Tables: { profiles: { Row: { - avatar_url: string | null - current_status_id: string | null - email: string | null - full_name: string | null - id: string - provider: string | null - updated_at: string | null - } + avatar_url: string | null; + current_status_id: string | null; + email: string | null; + full_name: string | null; + id: string; + provider: string | null; + updated_at: string | null; + }; Insert: { - avatar_url?: string | null - current_status_id?: string | null - email?: string | null - full_name?: string | null - id: string - provider?: string | null - updated_at?: string | null - } + avatar_url?: string | null; + current_status_id?: string | null; + email?: string | null; + full_name?: string | null; + id: string; + provider?: string | null; + updated_at?: string | null; + }; Update: { - avatar_url?: string | null - current_status_id?: string | null - email?: string | null - full_name?: string | null - id?: string - provider?: string | null - updated_at?: string | null - } + avatar_url?: string | null; + current_status_id?: string | null; + email?: string | null; + full_name?: string | null; + id?: string; + provider?: string | null; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "profiles_current_status_id_fkey" - columns: ["current_status_id"] - isOneToOne: false - referencedRelation: "statuses" - referencedColumns: ["id"] + foreignKeyName: 'profiles_current_status_id_fkey'; + columns: ['current_status_id']; + isOneToOne: false; + referencedRelation: 'statuses'; + referencedColumns: ['id']; }, - ] - } + ]; + }; statuses: { Row: { - created_at: string - id: string - status: string - updated_by_id: string | null - user_id: string - } + created_at: string; + id: string; + status: string; + updated_by_id: string | null; + user_id: string; + }; Insert: { - created_at?: string - id?: string - status: string - updated_by_id?: string | null - user_id: string - } + created_at?: string; + id?: string; + status: string; + updated_by_id?: string | null; + user_id: string; + }; Update: { - created_at?: string - id?: string - status?: string - updated_by_id?: string | null - user_id?: string - } - Relationships: [] - } - } + created_at?: string; + id?: string; + status?: string; + updated_by_id?: string | null; + user_id?: string; + }; + Relationships: []; + }; + }; Views: { - [_ in never]: never - } + [_ in never]: never; + }; Functions: { - [_ in never]: never - } + [_ in never]: never; + }; Enums: { - [_ in never]: never - } + [_ in never]: never; + }; CompositeTypes: { - [_ in never]: never - } - } -} + [_ in never]: never; + }; + }; +}; -type DefaultSchema = Database[Extract] +type DefaultSchema = Database[Extract]; export type Tables< DefaultSchemaTableNameOrOptions extends - | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | keyof (DefaultSchema['Tables'] & DefaultSchema['Views']) | { schema: keyof Database }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof Database + schema: keyof Database; } - ? keyof (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & - Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + ? keyof (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] & + Database[DefaultSchemaTableNameOrOptions['schema']]['Views']) : never = never, > = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } - ? (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & - Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { - Row: infer R + ? (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] & + Database[DefaultSchemaTableNameOrOptions['schema']]['Views'])[TableName] extends { + Row: infer R; } ? R : never - : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & - DefaultSchema["Views"]) - ? (DefaultSchema["Tables"] & - DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { - Row: infer R + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema['Tables'] & + DefaultSchema['Views']) + ? (DefaultSchema['Tables'] & + DefaultSchema['Views'])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R; } ? R : never - : never + : never; export type TablesInsert< DefaultSchemaTableNameOrOptions extends - | keyof DefaultSchema["Tables"] + | keyof DefaultSchema['Tables'] | { schema: keyof Database }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof Database + schema: keyof Database; } - ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + ? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] : never = never, > = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } - ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Insert: infer I + ? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends { + Insert: infer I; } ? I : never - : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] - ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { - Insert: infer I + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] + ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I; } ? I : never - : never + : never; export type TablesUpdate< DefaultSchemaTableNameOrOptions extends - | keyof DefaultSchema["Tables"] + | keyof DefaultSchema['Tables'] | { schema: keyof Database }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof Database + schema: keyof Database; } - ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + ? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] : never = never, > = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } - ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Update: infer U + ? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends { + Update: infer U; } ? U : never - : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] - ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { - Update: infer U + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] + ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends { + Update: infer U; } ? U : never - : never + : never; export type Enums< DefaultSchemaEnumNameOrOptions extends - | keyof DefaultSchema["Enums"] + | keyof DefaultSchema['Enums'] | { schema: keyof Database }, EnumName extends DefaultSchemaEnumNameOrOptions extends { - schema: keyof Database + schema: keyof Database; } - ? keyof Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + ? keyof Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums'] : never = never, > = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database } - ? Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] - : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] - ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] - : never + ? Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums'][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums'] + ? DefaultSchema['Enums'][DefaultSchemaEnumNameOrOptions] + : never; export type CompositeTypes< PublicCompositeTypeNameOrOptions extends - | keyof DefaultSchema["CompositeTypes"] + | keyof DefaultSchema['CompositeTypes'] | { schema: keyof Database }, CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { - schema: keyof Database + schema: keyof Database; } - ? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + ? keyof Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'] : never = never, > = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } - ? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] - : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] - ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] - : never + ? Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema['CompositeTypes'] + ? DefaultSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions] + : never; export const Constants = { public: { Enums: {}, }, -} as const - +} as const; diff --git a/src/utils/supabase/types.ts b/src/utils/supabase/types.ts index 2dc08cc..b3da0ee 100644 --- a/src/utils/supabase/types.ts +++ b/src/utils/supabase/types.ts @@ -1,5 +1,5 @@ import type { Database } from '@/utils/supabase/database.types'; -import type { SupabaseClient as SBClient } from '@supabase/supabase-js' +import type { SupabaseClient as SBClient } from '@supabase/supabase-js'; export type SBClientWithDatabase = SBClient; @@ -11,17 +11,17 @@ export type Result = { }; export type UserRecord = { - id: string, - updated_at: string | null, - email: string | null, - full_name: string | null, - avatar_url: string | null, - provider: string | null, + id: string; + updated_at: string | null; + email: string | null; + full_name: string | null; + avatar_url: string | null; + provider: string | null; status: { - status: string, - created_at: string, - updated_by: Profile | null, - } + status: string; + created_at: string; + updated_by: Profile | null; + }; }; export type AsyncReturnType Promise> =