Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ yarn-error.log*

# env files (can opt-in for committing if needed)
.env*
!.env.example



# vercel
.vercel
Expand Down
20 changes: 15 additions & 5 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,33 @@

# Conventional commit message format
# Format: <type>(<scope>): <description>
# Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert
# Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert, merge
# Also allows Git's standard merge commit format: "Merge branch '...' of ... into ..."

commit_regex='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,50}'
# Regex for conventional commits
conventional_regex='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert|merge)(\(.+\))?: .{1,50}'

# Regex for Git merge commits
merge_regex='^Merge branch .* of .* into .*'

# Combined regex - either conventional or merge format
commit_regex="($conventional_regex|$merge_regex)"

if ! grep -qE "$commit_regex" "$1"; then
echo "❌ Invalid commit message format."
echo "✅ Please use conventional commit format:"
echo " <type>(<scope>): <description>"
echo "✅ Please use either:"
echo " 1. Conventional commit format: <type>(<scope>): <description>"
echo " 2. Git's standard merge format: Merge branch '...' of ... into ..."
echo ""
echo "📝 Examples:"
echo " feat: add new wallet connection feature"
echo " fix(wallet): resolve persistence issue"
echo " docs: update README with setup instructions"
echo " style: format code with prettier"
echo " refactor(components): improve wallet button"
echo " Merge branch 'main' of https://github.com/user/repo into feature/branch"
echo ""
echo "🔧 Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
echo "🔧 Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert, merge"
exit 1
fi

Expand Down
28 changes: 24 additions & 4 deletions app/api/auth/register/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ import { NextResponse } from 'next/server';
import { z } from 'zod';

const userSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
firstName: z.string().min(2, 'First name must be at least 2 characters'),
lastName: z.string().min(2, 'Last name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});

export async function POST(req: Request) {
try {
const body = await req.json();
const { name, email, password } = userSchema.parse(body);
const { firstName, lastName, email, password } = userSchema.parse(body);
const registerData = {
email,
password,
firstName: name,
lastName: name,
firstName,
lastName,
username: email.split('@')[0],
};
const response = await register(registerData);
Expand All @@ -25,6 +26,25 @@ export async function POST(req: Request) {
if (error instanceof z.ZodError) {
return NextResponse.json({ message: error.message }, { status: 400 });
}

if (
typeof error === 'object' &&
error !== null &&
'message' in error &&
'status' in error
) {
const serverError = error;
const errorMessage =
serverError?.message || 'An unexpected error occurred';
const statusCode =
typeof serverError?.status === 'number' ? serverError.status : 400;

return NextResponse.json(
{ message: errorMessage },
{ status: statusCode }
);
}

const errorMessage =
error instanceof Error ? error.message : 'An unexpected error occurred';
return NextResponse.json({ message: errorMessage }, { status: 500 });
Expand Down
189 changes: 108 additions & 81 deletions app/auth/forgot-password/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,126 +2,153 @@

import { useState } from 'react';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Mail, ArrowLeft } from 'lucide-react';
import Image from 'next/image';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Loader2, Mail, ArrowLeft } from 'lucide-react';
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { BoundlessButton } from '@/components/buttons';

const forgotPasswordSchema = z.object({
email: z.string().email({
message: 'Please enter a valid email address',
}),
});

type ForgotPasswordFormData = z.infer<typeof forgotPasswordSchema>;

export default function ForgotPasswordPage() {
const [email, setEmail] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const [successMessage, setSuccessMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');

const form = useForm<ForgotPasswordFormData>({
resolver: zodResolver(forgotPasswordSchema),
defaultValues: {
email: '',
},
});

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const handleSubmit = async (data: ForgotPasswordFormData) => {
setIsLoading(true);
setError('');
setSuccess('');
setSuccessMessage('');
setErrorMessage('');

try {
const response = await fetch('/api/auth/forgot-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
body: JSON.stringify({ email: data.email }),
});

const data = await response.json();
const res = await response.json();

if (response.ok) {
setSuccess(
setSuccessMessage(
'Password reset instructions have been sent to your email address.'
);
setEmail('');
form.reset();
} else {
setError(data.message || 'Failed to send reset email');
setErrorMessage(res.message || 'Failed to send reset email');
}
} catch {
setError('An unexpected error occurred');
setErrorMessage('An unexpected error occurred');
} finally {
setIsLoading(false);
}
};

return (
<div className='min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8'>
<div className='min-h-screen flex items-center justify-center bg-background py-12 px-4 sm:px-6 lg:px-16'>
<div className='absolute top-16 left-16'>
<Image
src='/auth/logo.svg'
alt='logo'
width={123}
height={22}
className='object-cover'
/>
</div>
<div className='max-w-md w-full space-y-8'>
<div className='text-center'>
<h2 className='mt-6 text-3xl font-extrabold text-gray-900'>
Forgot your password?
<h2 className='mt-6 text-[40px] font-medium text-white'>
Reset Password
</h2>
<p className='mt-2 text-sm text-gray-600'>
Enter your email address and we'll send you a link to reset your
password.
<p className='mt-2 text-[#D9D9D9]'>
Enter the email address you used when you joined, and we'll send you
instructions to reset your password.
</p>
</div>

<Card>
<CardHeader>
<CardTitle>Reset Password</CardTitle>
<CardDescription>
We'll send you an email with instructions to reset your password
</CardDescription>
</CardHeader>
<CardContent className='space-y-4'>
{error && (
<Alert variant='destructive'>
<AlertDescription>
{typeof error === 'string' ? error : 'An error occurred'}
</AlertDescription>
</Alert>
)}
{successMessage && (
<div className='text-sm text-green-500 '>{successMessage}</div>
)}

{success && (
<Alert>
<AlertDescription>{success}</AlertDescription>
</Alert>
)}
{errorMessage && (
<div className='text-sm text-red-500'>{errorMessage}</div>
)}

<form onSubmit={handleSubmit} className='space-y-4'>
<div className='space-y-2'>
<Label htmlFor='email'>Email</Label>
<div className='relative'>
<Mail className='absolute left-3 top-3 h-4 w-4 text-gray-400' />
<Input
id='email'
type='email'
value={email}
onChange={e => setEmail(e.target.value)}
placeholder='Enter your email'
className='pl-10'
required
/>
</div>
</div>
<Form {...form}>
<form
onSubmit={form.handleSubmit(handleSubmit)}
className='space-y-4'
>
<FormField
control={form.control}
name='email'
render={({ field }) => (
<FormItem>
<FormLabel className='text-white text-xs font-medium'>
Email
</FormLabel>
<FormControl>
<div className='relative'>
<Mail className='absolute left-3 top-3 h-4 w-4 text-[#B5B5B5]' />
<Input
{...field}
type='email'
placeholder='Enter your email'
className='text-white placeholder:text-[#B5B5B5] border-[#2B2B2B] bg-[#1C1C1C] focus-visible:ring-0 focus-visible:ring-offset-0 caret-white w-full pl-10'
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<Button type='submit' className='w-full' disabled={isLoading}>
{isLoading && <Loader2 className='mr-2 h-4 w-4 animate-spin' />}
Send Reset Link
</Button>
</form>
<BoundlessButton
type='submit'
className='w-full'
disabled={isLoading}
fullWidth
loading={isLoading}
>
Send Reset Link
</BoundlessButton>
</form>
</Form>

<div className='text-center'>
<Link
href='/auth/signin'
className='inline-flex items-center text-sm text-blue-600 hover:text-blue-500'
>
<ArrowLeft className='mr-1 h-4 w-4' />
Back to sign in
</Link>
</div>
</CardContent>
</Card>
<div className='text-center'>
<Link
href='/auth/signin'
className='inline-flex items-center text-sm text-primary hover:text-primary/80'
>
<ArrowLeft className='mr-1 h-4 w-4' />
Back to sign in
</Link>
</div>
</div>
</div>
);
Expand Down
Loading
Loading