@@ -10,7 +10,7 @@ import { LOGIN_API } from '@/lib/constants'
10
10
import { type ValidationErrors } from '@/types'
11
11
import { type PageProps } from '@/types/inertia'
12
12
import { Head , router , useForm } from '@inertiajs/react'
13
- import { EyeIcon , EyeOffIcon , Github , LoaderCircle , ScanFaceIcon } from 'lucide-react'
13
+ import { EyeIcon , EyeOffIcon , LoaderCircle } from 'lucide-react'
14
14
import { useState } from 'react'
15
15
16
16
type LoginForm = {
@@ -37,12 +37,12 @@ const Login = ({ status, canResetPassword, auth, flash }: LoginProps) => {
37
37
const handleSubmit = ( e : React . FormEvent ) => {
38
38
e . preventDefault ( )
39
39
40
- // Direct Inertia form submission - cleanest approach
40
+ // Direct Inertia form submission
41
41
post ( LOGIN_API , {
42
42
onFinish : ( ) => reset ( 'password' ) ,
43
43
onError : ( _errors ) => {
44
44
// show a toast or send error to Sentry or log it to Firebase.
45
- // Whatever you prefer
45
+ // whatever you prefer
46
46
} ,
47
47
} )
48
48
}
@@ -55,114 +55,126 @@ const Login = ({ status, canResetPassword, auth, flash }: LoginProps) => {
55
55
< AuthLayout title = "Welcome back" description = "Log in to your account" >
56
56
< Head title = "Log in" />
57
57
58
- < form className = "flex flex-col gap-6" onSubmit = { handleSubmit } >
58
+ < form onSubmit = { handleSubmit } >
59
59
< div className = "grid gap-6" >
60
- < div className = "grid gap-2" >
61
- < Label htmlFor = "email" > Email address</ Label >
62
- < Input
63
- id = "email"
64
- type = "email"
65
- required
66
- autoFocus
67
- tabIndex = { 1 }
68
- autoComplete = "email"
69
- value = { data . email }
70
- onChange = { ( e ) => setData ( 'email' , e . target . value ) }
71
- placeholder = "name@example.com"
72
- />
73
- < InputError message = { errors ?. email } />
74
- </ div >
75
- </ div >
60
+ < div className = "grid gap-6" >
61
+ < div className = "grid gap-3" >
62
+ < Label htmlFor = "email" > Email</ Label >
63
+ < Input
64
+ id = "email"
65
+ type = "email"
66
+ required
67
+ autoFocus
68
+ tabIndex = { 1 }
69
+ autoComplete = "email"
70
+ value = { data . email }
71
+ onChange = { ( e ) => setData ( 'email' , e . target . value ) }
72
+ placeholder = "name@example.com"
73
+ />
74
+ < InputError message = { errors ?. email } />
75
+ </ div >
76
+ < div className = "grid gap-3" >
77
+ < div className = "flex items-center" >
78
+ < Label htmlFor = "password" > Password</ Label >
79
+ < a href = "#" className = "ml-auto text-sm underline-offset-4 hover:underline" >
80
+ Forgot your password?
81
+ </ a >
82
+ </ div >
83
+ < div className = "relative" >
84
+ < Input
85
+ id = "password"
86
+ type = { showPassword ? 'text' : 'password' }
87
+ required
88
+ tabIndex = { 2 }
89
+ autoComplete = "current-password"
90
+ value = { data . password }
91
+ onChange = { ( e ) => setData ( 'password' , e . target . value ) }
92
+ />
93
+ < Button
94
+ type = "button"
95
+ variant = "ghost"
96
+ size = "sm"
97
+ disabled = { ! data . password }
98
+ className = "absolute top-0 right-0 h-full px-3 py-2 hover:cursor-pointer hover:bg-transparent"
99
+ onClick = { ( ) => setShowPassword ( ! showPassword ) }
100
+ aria-label = { showPassword ? 'Hide password' : 'Show password' }
101
+ >
102
+ { showPassword ? (
103
+ < EyeOffIcon className = "h-4 w-4" />
104
+ ) : (
105
+ < EyeIcon className = "h-4 w-4" />
106
+ ) }
107
+ </ Button >
108
+ </ div >
109
+ < InputError message = { errors ?. password } />
110
+ </ div >
111
+
112
+ < div className = "flex items-center justify-between space-x-3" >
113
+ < div className = "flex flex-row items-center space-y-0 space-x-2" >
114
+ < Checkbox
115
+ id = "remember"
116
+ name = "remember"
117
+ checked = { data . remember }
118
+ onClick = { ( ) => setData ( 'remember' , ! data . remember ) }
119
+ tabIndex = { 3 }
120
+ />
121
+ < Label className = "text-sm font-normal" htmlFor = "remember" >
122
+ Remember me
123
+ </ Label >
124
+ </ div >
125
+ { canResetPassword && (
126
+ < TextLink href = { 'password.request' } className = "ml-auto text-sm" tabIndex = { 5 } >
127
+ Forgot password?
128
+ </ TextLink >
129
+ ) }
130
+ </ div >
76
131
77
- < div className = "grid gap-2" >
78
- < div className = "flex items-center" >
79
- < Label htmlFor = "password" > Password</ Label >
80
- </ div >
81
- < div className = "relative" >
82
- < Input
83
- id = "password"
84
- type = { showPassword ? 'text' : 'password' }
85
- required
86
- tabIndex = { 2 }
87
- autoComplete = "current-password"
88
- value = { data . password }
89
- onChange = { ( e ) => setData ( 'password' , e . target . value ) }
90
- placeholder = "Password"
91
- />
92
132
< Button
93
- type = "button"
94
- variant = "ghost"
95
- size = "sm"
96
- disabled = { ! data . password }
97
- className = "absolute top-0 right-0 h-full px-3 py-2 hover:cursor-pointer hover:bg-transparent"
98
- onClick = { ( ) => setShowPassword ( ! showPassword ) }
99
- aria-label = { showPassword ? 'Hide password' : 'Show password' }
133
+ type = "submit"
134
+ className = "mt-4 h-12 w-full rounded-full text-base"
135
+ tabIndex = { 4 }
136
+ disabled = { processing }
100
137
>
101
- { showPassword ? < EyeOffIcon className = "h-4 w-4" /> : < EyeIcon className = "h-4 w-4" /> }
138
+ { processing && < LoaderCircle className = "h-4 w-4 animate-spin" /> }
139
+ Log in
102
140
</ Button >
103
141
</ div >
104
- < InputError message = { errors ?. password } />
105
- </ div >
106
142
107
- < div className = "flex items-center justify-between space-x-3" >
108
- < div className = "flex flex-row items-center space-y-0 space-x-2" >
109
- < Checkbox
110
- id = "remember"
111
- name = "remember"
112
- checked = { data . remember }
113
- onClick = { ( ) => setData ( 'remember' , ! data . remember ) }
114
- tabIndex = { 3 }
115
- />
116
- < Label className = "text-sm font-normal" htmlFor = "remember" >
117
- Remember me
118
- </ Label >
143
+ < div className = "relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border" >
144
+ < span className = "relative z-10 bg-card px-2 text-muted-foreground" >
145
+ Or continue with
146
+ </ span >
119
147
</ div >
120
- { canResetPassword && (
121
- < TextLink href = { 'password.request' } className = "ml-auto text-sm" tabIndex = { 5 } >
122
- Forgot password?
123
- </ TextLink >
124
- ) }
125
- </ div >
126
148
127
- < Button
128
- type = "submit"
129
- className = "mt-4 h-12 w-full rounded-full text-base"
130
- tabIndex = { 4 }
131
- disabled = { processing }
132
- >
133
- { processing && < LoaderCircle className = "h-4 w-4 animate-spin" /> }
134
- Log in
135
- </ Button >
136
- </ form >
149
+ < div className = "flex gap-4" >
150
+ < Button variant = "secondary" size = "icon" className = "h-12 flex-1 rounded-full" >
151
+ < svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" >
152
+ < path
153
+ d = "M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
154
+ fill = "currentColor"
155
+ />
156
+ </ svg >
157
+ < span className = "sr-only" > Login with Google</ span >
158
+ </ Button >
159
+ < Button variant = "secondary" size = "icon" className = "h-12 flex-1 rounded-full" >
160
+ < svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" >
161
+ < path
162
+ d = "M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
163
+ fill = "currentColor"
164
+ />
165
+ </ svg >
166
+ < span className = "sr-only" > Login with Apple</ span >
167
+ </ Button >
168
+ </ div >
137
169
138
- < div className = "relative " >
139
- < div className = "absolute inset-0 flex items-center" >
140
- < span className = "w-full border-t" / >
141
- </ div >
142
- < div className = "relative flex justify-center text-xs uppercase" >
143
- < span className = "bg-background px-2 text-zinc-500" > Or continue with </ span >
170
+ < div className = "text-center text-sm " >
171
+ Don't have an account? { ' ' }
172
+ < a href = "/register" className = "underline underline-offset-4" >
173
+ Sign up
174
+ </ a >
175
+ </ div >
144
176
</ div >
145
- </ div >
146
-
147
- < div className = "flex gap-4" >
148
- < Button variant = "outline" size = "icon" className = "h-12 flex-1 rounded-full" >
149
- < Github className = "h-6 w-6" />
150
- < span className = "sr-only" > GitHub</ span >
151
- </ Button >
152
- < Button variant = "outline" size = "icon" className = "h-12 flex-1 rounded-full" >
153
- < ScanFaceIcon className = "h-6 w-6" />
154
- < span className = "sr-only" > Face ID</ span >
155
- </ Button >
156
- </ div >
157
-
158
- < div className = "text-center" >
159
- < p className = "text-sm text-foreground/80" >
160
- < span > Don't have an account?</ span >
161
- < TextLink className = "ml-1" href = { '/register' } >
162
- Sign up
163
- </ TextLink >
164
- </ p >
165
- </ div >
177
+ </ form >
166
178
167
179
{ status && (
168
180
< div className = "mb-4 text-center text-sm font-medium text-green-600" > { status } </ div >
0 commit comments