diff --git a/src/app/(auth-pages)/forgot-password/page.tsx b/src/app/(auth-pages)/forgot-password/page.tsx index d7d5eed..6a3f5a1 100644 --- a/src/app/(auth-pages)/forgot-password/page.tsx +++ b/src/app/(auth-pages)/forgot-password/page.tsx @@ -1,35 +1,123 @@ +'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/actions'; -import { FormMessage, type Message, SubmitButton } from '@/components/default'; -import { Input, Label } from '@/components/ui'; +import { useRouter } from 'next/navigation'; +import { useAuth } from '@/components/context/auth'; +import { useEffect, useState } from 'react'; +import { StatusMessage, SubmitButton } from '@/components/default'; + +const formSchema = z.object({ + email: z.string().email({ + message: 'Please enter a valid email address.', + }), +}) + +const ForgotPassword = () => { + const router = useRouter(); + const { isAuthenticated, isLoading, refreshUserData } = useAuth(); + const [statusMessage, setStatusMessage] = useState(''); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: '', + }, + }); + + // Redirect if already authenticated + useEffect(() => { + if (isAuthenticated) { + router.push('/'); + } + }, [isAuthenticated, router]); + + + const handleForgotPassword = async (values: z.infer) => { + try { + setStatusMessage(''); + const formData = new FormData(); + formData.append('email', values.email); + const result = await forgotPassword(formData); + if (result?.success) { + await refreshUserData(); + setStatusMessage(result?.data ?? 'Check your email for a link to reset your password.') + form.reset(); + router.push(''); + } else { + setStatusMessage(`Error: ${result.error}`) + } + } catch (error) { + setStatusMessage( + `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}` + ); + } + }; -const ForgotPassword = async (props: { searchParams: Promise }) => { - const searchParams = await props.searchParams; return ( - <> -
-
-

Reset Password

-

- Already have an account?{' '} - - Sign in + + + Reset Password + + Don't have an account?{' '} + + Sign up -

-
-
- - - - Reset Password - - -
-
- + + + +
+ + ( + + Email + + + + + + )} + /> + + Reset Password + + {statusMessage && ( + statusMessage.includes('Error') || + statusMessage.includes('error') || + statusMessage.includes('failed') || + statusMessage.includes('invalid') + ? + : + )} + + +
+ ); }; export default ForgotPassword; diff --git a/src/app/(auth-pages)/profile/page.tsx b/src/app/(auth-pages)/profile/page.tsx index be7610c..39a6147 100644 --- a/src/app/(auth-pages)/profile/page.tsx +++ b/src/app/(auth-pages)/profile/page.tsx @@ -2,7 +2,7 @@ import { useAuth } from '@/components/context/auth'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; -import { AvatarUpload, ProfileForm, ResetPasswordForm } from '@/components/default/profile'; +import { AvatarUpload, ProfileForm, ResetPasswordForm, SignOut } from '@/components/default/profile'; import { Card, CardHeader, @@ -100,6 +100,8 @@ const ProfilePage = () => { + + )} diff --git a/src/app/(auth-pages)/sign-in/page.tsx b/src/app/(auth-pages)/sign-in/page.tsx index 1cb2954..ab1e508 100644 --- a/src/app/(auth-pages)/sign-in/page.tsx +++ b/src/app/(auth-pages)/sign-in/page.tsx @@ -4,7 +4,6 @@ import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { - Button, Card, CardContent, CardDescription, @@ -12,20 +11,18 @@ import { CardTitle, Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, FormMessage, Input, - Label, } from '@/components/ui'; import Link from 'next/link'; import { signIn } from '@/lib/actions'; -import { SubmitButton } from '@/components/default'; import { useRouter } from 'next/navigation'; import { useAuth } from '@/components/context/auth'; import { useEffect, useState } from 'react'; +import { StatusMessage, SubmitButton } from '@/components/default'; const formSchema = z.object({ email: z.string().email({ @@ -58,6 +55,7 @@ const Login = () => { const handleSignIn = async (values: z.infer) => { try { + setStatusMessage(''); const formData = new FormData(); formData.append('email', values.email); formData.append('password', values.password); @@ -77,7 +75,7 @@ const Login = () => { }; return ( - + Sign In @@ -131,15 +129,12 @@ const Login = () => { )} /> {statusMessage && ( -
- {statusMessage} -
+ statusMessage.includes('Error') || + statusMessage.includes('error') || + statusMessage.includes('failed') || + statusMessage.includes('invalid') + ? + : )} }) => { - const searchParams = await props.searchParams; - const user = await getUser(); - if (user.success) redirect('/profile'); - if ('message' in searchParams) { - return ( -
- -
- ); - } else { - return ( -
-

Sign up

-

+const formSchema = z + .object({ + name: z.string().min(2, { + message: 'Name must be at least 2 characters.', + }), + email: z.string().email({ + message: 'Please enter a valid email address.', + }), + password: z.string().min(8, { + message: 'Password must be at least 8 characters.', + }), + confirmPassword: z.string().min(8, { + message: 'Password must be at least 8 characters.', + }), + }) + .refine((data) => data.password === data.confirmPassword, { + message: 'Passwords do not match!', + path: ['confirmPassword'], + }); + +const SignUp = () => { + + const router = useRouter(); + const { isAuthenticated, isLoading, refreshUserData } = useAuth(); + const [statusMessage, setStatusMessage] = useState(''); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: '', + email: '', + password: '', + confirmPassword: '', + }, + mode: 'onChange', + }); + + // Redirect if already authenticated + useEffect(() => { + if (isAuthenticated) { + router.push('/'); + } + }, [isAuthenticated, router]); + + const handleSignUp = async (values: z.infer) => { + try { + setStatusMessage(''); + const formData = new FormData(); + formData.append('name', values.name); + formData.append('email', values.email); + formData.append('password', values.password); + const result = await signUp(formData); + if (result?.success) { + await refreshUserData(); + setStatusMessage( + result.data ?? + 'Thanks for signing up! Please check your email for a verification link.' + ); + form.reset(); + router.push(''); + } else { + setStatusMessage(`Error: ${result.error}`) + } + } catch (error) { + setStatusMessage( + `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}` + ); + } + }; + + return ( + + + + Sign Up + + Already have an account?{' '} Sign in -

-
- - - - - - - - Sign up - - -
-
- ); - } + +
+ +
+ + ( + + Name + + + + + )} + /> + ( + + Email + + + + + + )} + /> + ( + + Password + + + + + + )} + /> + ( + + Confirm Password + + + + + + )} + /> + {statusMessage && ( + statusMessage.includes('Error') || + statusMessage.includes('error') || + statusMessage.includes('failed') || + statusMessage.includes('invalid') + ? + : + )} + + Sign up + + + +
+
+ ); }; export default SignUp; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f3c6a1e..80d8fc0 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -195,7 +195,10 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
-
+
{children}
diff --git a/src/components/default/footer/index.tsx b/src/components/default/footer/index.tsx index 84a02ef..7379740 100644 --- a/src/components/default/footer/index.tsx +++ b/src/components/default/footer/index.tsx @@ -1,6 +1,6 @@ 'use server'; -const FooterTest = () => { +const Footer = () => { return (

@@ -17,4 +17,4 @@ const FooterTest = () => {

); }; -export default FooterTest; +export default Footer; diff --git a/src/components/default/form-message.tsx b/src/components/default/form-message.tsx deleted file mode 100644 index 42b9a7b..0000000 --- a/src/components/default/form-message.tsx +++ /dev/null @@ -1,24 +0,0 @@ -export type Message = - | { success: string } - | { error: string } - | { message: string }; - -export const FormMessage = ({ message }: { message: Message }) => { - return ( -
- {'success' in message && ( -
- {message.success} -
- )} - {'error' in message && ( -
- {message.error} -
- )} - {'message' in message && ( -
{message.message}
- )} -
- ); -}; diff --git a/src/components/default/index.tsx b/src/components/default/index.tsx index 3a200fb..dfb263d 100644 --- a/src/components/default/index.tsx +++ b/src/components/default/index.tsx @@ -1,4 +1,2 @@ -import { FormMessage, type Message } from '@/components/default/form-message'; -import { SubmitButton } from '@/components/default/submit-button'; - -export { FormMessage, type Message, SubmitButton }; +export { StatusMessage, type Message } from '@/components/default/status-message'; +export { SubmitButton } from '@/components/default/submit-button'; diff --git a/src/components/default/profile/ProfileForm.tsx b/src/components/default/profile/ProfileForm.tsx index dab38ab..aaa7de3 100644 --- a/src/components/default/profile/ProfileForm.tsx +++ b/src/components/default/profile/ProfileForm.tsx @@ -2,7 +2,6 @@ import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { - Button, CardContent, Form, FormControl, @@ -13,9 +12,9 @@ import { FormMessage, Input, } from '@/components/ui'; -import { Loader2 } from 'lucide-react'; import { useEffect } from 'react'; import { useAuth } from '@/components/context/auth'; +import { SubmitButton } from '@/components/default'; const formSchema = z.object({ full_name: z.string().min(5, { @@ -90,16 +89,12 @@ export const ProfileForm = ({onSubmit}: ProfileFormProps) => { />
- + + Save Changes +
diff --git a/src/components/default/profile/ResetPasswordForm.tsx b/src/components/default/profile/ResetPasswordForm.tsx index a138088..340fb6d 100644 --- a/src/components/default/profile/ResetPasswordForm.tsx +++ b/src/components/default/profile/ResetPasswordForm.tsx @@ -18,7 +18,7 @@ import { import { SubmitButton } from '@/components/default'; import { useState } from 'react'; import { type Result } from '@/lib/actions'; -import { FormMessage as Pw } from '@/components/default'; +import { StatusMessage } from '@/components/default'; const formSchema = z .object({ @@ -85,65 +85,64 @@ export const ResetPasswordForm = ({ - -
- - ( - - New Password - - - - - Enter your new password. Must be at least 8 characters. - - - + + + ( + + New Password + + + + + Enter your new password. Must be at least 8 characters. + + + + )} + /> + ( + + Confirm Password + + + + + Please re-enter your new password to confirm. + + + + )} + /> + {statusMessage && ( +
+ {statusMessage} +
)} - /> - ( - - Confirm Password - - - - - Please re-enter your new password to confirm. - - - - )} - /> - {statusMessage && ( -
- {statusMessage} +
+ + Update Password +
- )} -
- - Update Password - -
- - + +
); diff --git a/src/components/default/profile/SignOut.tsx b/src/components/default/profile/SignOut.tsx new file mode 100644 index 0000000..b6b6c54 --- /dev/null +++ b/src/components/default/profile/SignOut.tsx @@ -0,0 +1,32 @@ +'use client'; + +import { CardContent, CardHeader, CardTitle } from '@/components/ui'; +import { SubmitButton } from '@/components/default'; +import { useRouter } from 'next/navigation'; +import { useAuth } from '@/components/context/auth'; +import { signOut } from '@/lib/actions'; + +export const SignOut = () => { + const { isLoading, refreshUserData } = useAuth(); + const router = useRouter(); + + const handleSignOut = async () => { + const result = await signOut(); + if (result?.success) { + await refreshUserData(); + router.push('/sign-in'); + } + }; + return ( +
+ + + Sign Out + + +
+ ); +}; diff --git a/src/components/default/profile/index.tsx b/src/components/default/profile/index.tsx index e598655..a231fb7 100644 --- a/src/components/default/profile/index.tsx +++ b/src/components/default/profile/index.tsx @@ -1,3 +1,4 @@ export * from './AvatarUpload'; export * from './ProfileForm'; export * from './ResetPasswordForm'; +export * from './SignOut'; diff --git a/src/components/default/status-message.tsx b/src/components/default/status-message.tsx new file mode 100644 index 0000000..96e72ec --- /dev/null +++ b/src/components/default/status-message.tsx @@ -0,0 +1,27 @@ +export type Message = + | { success: string } + | { error: string } + | { message: string }; + +export const StatusMessage = ({ message }: { message: Message }) => { + return ( +
+ {'success' in message && ( +
+ {message.success} +
+ )} + {'error' in message && ( +
+ {message.error} +
+ )} + {'message' in message && ( +
{message.message}
+ )} +
+ ); +}; diff --git a/src/components/default/submit-button.tsx b/src/components/default/submit-button.tsx index 22538c3..cf124b5 100644 --- a/src/components/default/submit-button.tsx +++ b/src/components/default/submit-button.tsx @@ -19,7 +19,13 @@ export const SubmitButton = ({ const { pending } = useFormStatus(); return ( -