diff --git a/bun.lockb b/bun.lockb
index ab5f3b7..331110f 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 74d8edd..be95821 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
"@sentry/nextjs": "^9.34.0",
"@supabase-cache-helpers/postgrest-react-query": "^1.13.4",
"@supabase/ssr": "^0.6.1",
- "@supabase/supabase-js": "^2.50.2",
+ "@supabase/supabase-js": "^2.50.3",
"@t3-oss/env-nextjs": "^0.12.0",
"@tanstack/react-query": "^5.81.5",
"@tanstack/react-table": "^8.21.3",
@@ -75,10 +75,10 @@
"react-icons": "^5.5.0",
"react-resizable-panels": "^3.0.3",
"recharts": "^3.0.2",
- "sonner": "^2.0.5",
+ "sonner": "^2.0.6",
"tailwind-merge": "^3.3.1",
"vaul": "^1.1.2",
- "zod": "^3.25.67"
+ "zod": "^3.25.71"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
@@ -98,7 +98,7 @@
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.13",
"tailwindcss": "^4.1.11",
- "tw-animate-css": "^1.3.4",
+ "tw-animate-css": "^1.3.5",
"typescript": "^5.8.3",
"typescript-eslint": "^8.35.1"
},
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 6ec30b1..a7dc76a 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,4 +1,5 @@
import { SignInCard } from '@/components/default/auth/cards/client/sign-in';
+import { ForgotPasswordCard } from '@/components/default/auth/cards/client/forgot-password';
import { ThemeToggle } from '@/lib/hooks/context';
export default function HomePage() {
@@ -9,6 +10,7 @@ export default function HomePage() {
Create T3 App
+
diff --git a/src/components/default/auth/buttons/client/sign-out.tsx b/src/components/default/auth/buttons/client/sign-out.tsx
new file mode 100644
index 0000000..2ee273f
--- /dev/null
+++ b/src/components/default/auth/buttons/client/sign-out.tsx
@@ -0,0 +1,48 @@
+'use client';
+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 { useSupabaseClient } from '@/utils/supabase';
+import { cn } from '@/lib/utils';
+
+type SignOutProps = Omit
+
+export const SignOut = ({
+ className,
+ pendingText = 'Signing out...',
+ ...props
+}: SignOutProps) => {
+
+ const supabase = useSupabaseClient();
+ const { loading, refreshUser } = useAuth();
+ const router = useRouter();
+
+ const handleSignOut = async () => {
+ try {
+ if (!supabase) throw new Error('Supabase client not found');
+ const result = await signOut(supabase);
+ if (result.error) throw new Error(result.error.message);
+ await refreshUser();
+ router.push('/sign-in');
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ return (
+
+ Sign Out
+
+ );
+};
diff --git a/src/components/default/auth/buttons/server/sign-out.tsx b/src/components/default/auth/buttons/server/sign-out.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/default/auth/cards/client/forgot-password.tsx b/src/components/default/auth/cards/client/forgot-password.tsx
index e69de29..5775411 100644
--- a/src/components/default/auth/cards/client/forgot-password.tsx
+++ b/src/components/default/auth/cards/client/forgot-password.tsx
@@ -0,0 +1,177 @@
+'use client';
+import { z } from 'zod';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Input,
+} from '@/components/ui';
+import Link from 'next/link';
+import { forgotPassword } from '@/lib/queries';
+import { useAuth } from '@/lib/hooks/context';
+import { useEffect, useState, type ComponentProps } from 'react';
+import { useRouter } from 'next/navigation';
+import { useSupabaseClient } from '@/utils/supabase';
+import { StatusMessage, SubmitButton } from '@/components/default/forms';
+import { cn } from '@/lib/utils';
+
+const forgotPasswordFormSchema = z.object({
+ email: z.string().email({
+ message: 'Please enter a valid email address.'
+ }),
+});
+
+type ForgotPasswordCardProps = {
+ cardClassName?: ComponentProps['className'];
+ cardProps?: Omit, 'className'>;
+ cardTitleClassName?: ComponentProps['className'];
+ cardTitleProps?: Omit, 'className'>;
+ cardDescriptionClassName?: ComponentProps['className'];
+ cardDescriptionProps?: Omit, 'className'>;
+ signUpLinkClassName?: ComponentProps['className'];
+ signUpLinkProps?: Omit, 'className' | 'href'>;
+ formClassName?: ComponentProps<'form'>['className'];
+ formProps?: Omit, 'className' | 'onSubmit'>;
+ formLabelClassName?: ComponentProps['className'];
+ formLabelProps?: Omit, 'className'>;
+ buttonProps?: ComponentProps;
+};
+
+export const ForgotPasswordCard = ({
+ cardClassName,
+ cardProps,
+ cardTitleClassName,
+ cardTitleProps,
+ cardDescriptionClassName,
+ cardDescriptionProps,
+ signUpLinkClassName,
+ signUpLinkProps,
+ formClassName,
+ formProps,
+ formLabelClassName,
+ formLabelProps,
+ buttonProps = {
+ pendingText: 'Sending Reset Link...',
+ },
+}: ForgotPasswordCardProps) => {
+ const router = useRouter();
+ const { isAuthenticated, loading, refreshUser } = useAuth();
+ const [statusMessage, setStatusMessage] = useState('');
+ const supabase = useSupabaseClient();
+
+ const form = useForm>({
+ resolver: zodResolver(forgotPasswordFormSchema),
+ defaultValues: {
+ email: '',
+ },
+ });
+
+ useEffect(() => {
+ if (isAuthenticated) router.push('/')
+ }, [isAuthenticated, router]);
+
+ const handleForgotPassword = async (values: z.infer) => {
+ try {
+ setStatusMessage('');
+ const formData = new FormData();
+ formData.append('email', values.email);
+ if (!supabase) throw new Error('Supabase client not found');
+ const result = await forgotPassword(supabase, formData);
+ if (result.error) throw new Error(result.error.message);
+ await refreshUser();
+ setStatusMessage('Check your email for a link to reset your password.');
+ form.reset();
+ router.push('');
+ } catch (error) {
+ setStatusMessage(
+ `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}`,
+ );
+ }
+ };
+
+ return (
+
+
+
+ Forgot Password
+
+
+ Don't have an account?{' '}
+
+ Sign up!
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/default/auth/cards/client/profile.tsx b/src/components/default/auth/cards/client/profile.tsx
index e69de29..c9495b3 100644
--- a/src/components/default/auth/cards/client/profile.tsx
+++ b/src/components/default/auth/cards/client/profile.tsx
@@ -0,0 +1,4 @@
+'use client';
+import { useAuth } from '@/lib/hooks/context';
+import { useEffect } from 'react';
+import { useRouter } from 'next/navigation';
diff --git a/src/components/default/auth/cards/client/sign-in.tsx b/src/components/default/auth/cards/client/sign-in.tsx
index 9569c39..57e0208 100755
--- a/src/components/default/auth/cards/client/sign-in.tsx
+++ b/src/components/default/auth/cards/client/sign-in.tsx
@@ -1,5 +1,4 @@
'use client';
-
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
diff --git a/src/components/default/auth/forms/profile/avatar-upload.tsx b/src/components/default/auth/forms/profile/avatar-upload.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/default/auth/forms/profile/profile-form.tsx b/src/components/default/auth/forms/profile/profile-form.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/default/auth/forms/profile/reset-password-form.tsx b/src/components/default/auth/forms/profile/reset-password-form.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/default/forms/submit-button.tsx b/src/components/default/forms/submit-button.tsx
index 94e3d90..8cdd63f 100644
--- a/src/components/default/forms/submit-button.tsx
+++ b/src/components/default/forms/submit-button.tsx
@@ -7,9 +7,8 @@ import { cn } from '@/lib/utils';
export type SubmitButtonProps = Omit<
ComponentProps,
- 'type' | 'aria-disabled' | 'className',
+ 'type' | 'aria-disabled'
> & {
- className?: ComponentProps['className'];
pendingText?: string;
pendingTextClassName?: ComponentProps<'p'>['className'];
pendingTextProps?: Omit, 'className'>;