Skip to content
Open
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
8 changes: 8 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ app:
- changed-files:
- any-glob-to-any-file: 'apps/**'

analytics:
- changed-files:
- any-glob-to-any-file: 'packages/analytics/**'

auth:
- changed-files:
- any-glob-to-any-file: 'packages/auth/**'
Expand All @@ -10,6 +14,10 @@ database:
- changed-files:
- any-glob-to-any-file: 'packages/database/**'

email:
- changed-files:
- any-glob-to-any-file: 'packages/email/**'

next-config:
- changed-files:
- any-glob-to-any-file: 'packages/next-config/**'
Expand Down
2 changes: 2 additions & 0 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ BETTER_AUTH_SECRET=""
DATABASE_URL=""
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
RESEND_FROM=""
RESEND_TOKEN=""
BETTERSTACK_API_KEY=""
BETTERSTACK_URL=""

Expand Down
3 changes: 1 addition & 2 deletions apps/web/app/(unauthenticated)/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,12 @@ export const Header = () => {
<nav className="flex flex-1 flex-row items-center justify-end">
<div className="flex flex-initial flex-row items-center justify-end gap-2">
<Button
size="sm"
asChild
className="bg-transparent text-muted-foreground hover:bg-transparent hover:text-muted-foreground"
>
<Link href="/contact">Contact</Link>
</Button>
<Button variant="outline" size="sm" asChild>
<Button variant="outline" asChild>
{pathname.includes('login') ? (
<Link href="/signup">Sign Up</Link>
) : (
Expand Down
102 changes: 102 additions & 0 deletions apps/web/app/(unauthenticated)/forgot-password/forgot-password.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import { forgetPassword } from '@repo/auth/client'
import { Alert } from '@repo/ui/components/ui/alert'
import { Button } from '@repo/ui/components/ui/button'
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage
} from '@repo/ui/components/ui/form'
import { Input } from '@repo/ui/components/ui/input'
import { AlertCircle, PartyPopper } from 'lucide-react'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import type { z } from 'zod'
import { formSchema } from './schema'

export const ForgotPassword = () => {
const [status, setStatus] = useState<boolean>(false)
const [generalError, setGeneralError] = useState<string | null>(null)
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: ''
}
})

const onSubmit = async ({ email }: z.infer<typeof formSchema>) => {
try {
const { data, error } = await forgetPassword({
email,
redirectTo: '/reset-password'
})

if (error) {
setGeneralError(error.message || 'Failed to send reset password email.')
}

if (data && !error) {
setStatus(true)
}
} catch {
setGeneralError(
'Could not connect to the authentication service. Please try again later or contact support if the issue persists.'
)
}
}

return (
<div className="flex w-full flex-1 flex-col items-center justify-center gap-6 p-6">
<div className="mx-auto mb-4 max-w-md text-center">
<h1 className="font-semibold text-3xl">Forgot Password?</h1>
</div>
<div className="mx-auto w-full max-w-80 space-y-4">
{generalError && (
<Alert variant="destructive" className="flex bg-destructive/15">
<AlertCircle className="inline-block size-4" />
{generalError}
</Alert>
)}
{status && (
<Alert className="flex bg-success/15 text-success">
<PartyPopper className="inline-block size-4" />
Check your email for a link to reset your password.
</Alert>
)}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input disabled={status} placeholder="Email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
variant={form.formState.isValid ? 'default' : 'outline'}
size="lg"
className="mt-4 w-full"
disabled={
form.formState.isSubmitting || !form.formState.isValid || status
}
>
{form.formState.isSubmitting ? 'Sending...' : 'Send Reset Link'}
</Button>
</form>
</Form>
</div>
</div>
)
}
9 changes: 9 additions & 0 deletions apps/web/app/(unauthenticated)/forgot-password/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import dynamic from 'next/dynamic'

const ForgotPassword = dynamic(() =>
import('./forgot-password').then(mod => mod.ForgotPassword)
)

export default function ForgotPasswordPage() {
return <ForgotPassword />
}
5 changes: 5 additions & 0 deletions apps/web/app/(unauthenticated)/forgot-password/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { z } from 'zod'

export const formSchema = z.object({
email: z.string().email()
})
4 changes: 3 additions & 1 deletion apps/web/app/(unauthenticated)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export default async function AuthLayout({ children }: AuthLayoutProps) {
return (
<>
<Header />
{children}
<main className="flex min-h-[calc(100vh-64px)] flex-col items-stretch">
{children}
</main>
<Footer />
</>
)
Expand Down
120 changes: 67 additions & 53 deletions apps/web/app/(unauthenticated)/login/email/login-email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,66 +55,80 @@ export const LogInEmail = () => {
}

return (
<div className="relative flex h-full min-h-[calc(100vh-64px)] w-full shrink grow flex-col content-center items-center justify-center gap-6 p-6">
<div className="mx-auto mb-4 max-w-md text-center">
<h1 className="font-semibold text-3xl">Log In to Blackhead</h1>
<>
<div className="flex w-full flex-1 flex-col items-center justify-center gap-6 p-6">
<div className="mx-auto mb-4 max-w-md text-center">
<h1 className="font-semibold text-3xl">Log In to Blackhead</h1>
</div>
<div className="mx-auto w-full max-w-80 space-y-4">
{generalError && (
<Alert variant="destructive" className="flex bg-destructive/15">
<AlertCircle className="inline-block size-4" />
{generalError}
</Alert>
)}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="Email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<PasswordInput {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
variant={form.formState.isValid ? 'default' : 'outline'}
size="lg"
className="mt-4 w-full"
disabled={
form.formState.isSubmitting || !form.formState.isValid
}
>
{form.formState.isSubmitting ? 'Signing up...' : 'Continue'}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect loading text for login form.

The button displays "Signing up..." during submission, but this is a login form, not a signup form.

-                {form.formState.isSubmitting ? 'Signing up...' : 'Continue'}
+                {form.formState.isSubmitting ? 'Logging in...' : 'Continue'}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{form.formState.isSubmitting ? 'Signing up...' : 'Continue'}
{form.formState.isSubmitting ? 'Logging in...' : 'Continue'}

</Button>
</form>
</Form>
</div>
<div className="flex items-center justify-center">
<Button
variant="link"
asChild
className="text-blue-500 text-sm hover:text-blue-600"
>
<Link href="/login">← Other Login Options</Link>
</Button>
</div>
</div>
<div className="mx-auto w-full max-w-80 space-y-4">
{generalError && (
<Alert variant="destructive" className="flex bg-destructive/15">
<AlertCircle className="inline-block size-4" />
{generalError}
</Alert>
)}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="Email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<PasswordInput {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
variant={form.formState.isValid ? 'default' : 'outline'}
size="lg"
className="mt-4 w-full"
disabled={form.formState.isSubmitting || !form.formState.isValid}
>
{form.formState.isSubmitting ? 'Signing up...' : 'Continue'}
</Button>
</form>
</Form>
</div>
<div className="flex items-center justify-center">

<div className="flex w-full items-center justify-center gap-6 border-t p-7">
<Button
variant="link"
asChild
className="text-blue-500 text-sm hover:text-blue-600"
>
<Link href="/login">← Other Login Options</Link>
<Link href="/forgot-password">Forgot password?</Link>
</Button>
</div>
</div>
</>
)
}
2 changes: 1 addition & 1 deletion apps/web/app/(unauthenticated)/login/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { toast } from 'sonner'

export const LogIn = () => {
return (
<div className="relative flex h-full min-h-[calc(100vh-64px)] w-full shrink grow flex-col content-center items-center justify-center gap-6 p-6">
<div className="flex w-full flex-1 flex-col items-center justify-center gap-6 p-6">
<div className="mx-auto mb-4 max-w-md text-center">
<h1 className="font-semibold text-3xl">Log in to Blackhead</h1>
</div>
Expand Down
9 changes: 9 additions & 0 deletions apps/web/app/(unauthenticated)/reset-password/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import dynamic from 'next/dynamic'

const ResetPassword = dynamic(() =>
import('./reset-password').then(mod => mod.ResetPassword)
)

export default function ResetPasswordPage() {
return <ResetPassword />
}
Loading
Loading