1
1
'use client' ;
2
2
3
3
import { Button } from "@/components/ui/button" ;
4
- import { Form , FormControl , FormField , FormItem , FormLabel , FormMessage } from "@/components/ui/form" ;
5
- import { Input } from "@/components/ui/input" ;
6
- import { useForm } from "react-hook-form" ;
7
- import { zodResolver } from "@hookform/resolvers/zod" ;
8
- import { z } from "zod" ;
9
4
import logoDark from "@/public/sb_logo_dark_large.png" ;
10
5
import logoLight from "@/public/sb_logo_light_large.png" ;
11
- import githubLogo from "@/public/github.svg" ;
12
6
import googleLogo from "@/public/google.svg" ;
13
7
import Image from "next/image" ;
14
8
import { signIn } from "next-auth/react" ;
15
- import { useCallback , useMemo } from "react" ;
16
- import { verifyCredentialsRequestSchema } from "@/lib/schemas" ;
9
+ import { Fragment , useCallback , useMemo } from "react" ;
10
+ import { Card } from "@/components/ui/card" ;
11
+ import { cn , getCodeHostIcon } from "@/lib/utils" ;
12
+ import { MagicLinkForm } from "./magicLinkForm" ;
13
+ import { CredentialsForm } from "./credentialsForm" ;
17
14
18
15
interface LoginFormProps {
19
16
callbackUrl ?: string ;
20
17
error ?: string ;
21
- }
22
-
23
- const magicLinkSchema = z . object ( {
24
- email : z . string ( ) . email ( ) ,
25
- } )
26
-
27
- export const LoginForm = ( { callbackUrl, error } : LoginFormProps ) => {
28
- const emailPasswordForm = useForm < z . infer < typeof verifyCredentialsRequestSchema > > ( {
29
- resolver : zodResolver ( verifyCredentialsRequestSchema ) ,
30
- defaultValues : {
31
- email : "" ,
32
- password : "" ,
33
- } ,
34
- } ) ;
35
-
36
- const magicLinkForm = useForm < z . infer < typeof magicLinkSchema > > ( {
37
- resolver : zodResolver ( magicLinkSchema ) ,
38
- defaultValues : {
39
- email : "" ,
40
- } ,
41
- } ) ;
42
-
43
- const onSignInWithEmailPassword = ( values : z . infer < typeof verifyCredentialsRequestSchema > ) => {
44
- signIn ( "credentials" , {
45
- email : values . email ,
46
- password : values . password ,
47
- redirectTo : callbackUrl ?? "/"
48
- } ) ;
49
- }
50
-
51
- const onSignInWithMagicLink = ( values : z . infer < typeof magicLinkSchema > ) => {
52
- signIn ( "nodemailer" , { email : values . email , redirectTo : callbackUrl ?? "/" } ) ;
18
+ enabledMethods : {
19
+ github : boolean ;
20
+ google : boolean ;
21
+ magicLink : boolean ;
22
+ credentials : boolean ;
53
23
}
24
+ }
54
25
26
+ export const LoginForm = ( { callbackUrl, error, enabledMethods } : LoginFormProps ) => {
55
27
const onSignInWithOauth = useCallback ( ( provider : string ) => {
56
28
signIn ( provider , { redirectTo : callbackUrl ?? "/" } ) ;
57
29
} , [ callbackUrl ] ) ;
@@ -71,117 +43,107 @@ export const LoginForm = ({ callbackUrl, error }: LoginFormProps) => {
71
43
} , [ error ] ) ;
72
44
73
45
return (
74
- < div className = "flex flex-col items-center border p-16 rounded-lg gap-6 w-[500px]" >
75
- { error && (
76
- < div className = "text-sm text-destructive text-center text-wrap border p-2 rounded-md border-destructive" >
77
- { errorMessage }
46
+ < div className = "flex flex-col items-center justify-center" >
47
+ < div className = "mb-6 flex flex-col items-center" >
48
+ < div >
49
+ < Image
50
+ src = { logoDark }
51
+ className = "h-16 w-auto hidden dark:block"
52
+ alt = { "Sourcebot logo" }
53
+ priority = { true }
54
+ />
55
+ < Image
56
+ src = { logoLight }
57
+ className = "h-16 w-auto block dark:hidden"
58
+ alt = { "Sourcebot logo" }
59
+ priority = { true }
60
+ />
78
61
</ div >
79
- ) }
80
- < div >
81
- < Image
82
- src = { logoDark }
83
- className = "h-16 w-auto hidden dark:block"
84
- alt = { "Sourcebot logo" }
85
- priority = { true }
86
- />
87
- < Image
88
- src = { logoLight }
89
- className = "h-16 w-auto block dark:hidden"
90
- alt = { "Sourcebot logo" }
91
- priority = { true }
92
- />
62
+ < h2 className = "text-lg font-bold" > Sign in to your account</ h2 >
93
63
</ div >
94
- < ProviderButton
95
- name = "GitHub"
96
- logo = { githubLogo }
97
- onClick = { ( ) => {
98
- onSignInWithOauth ( "github" )
99
- } }
100
- />
101
- < ProviderButton
102
- name = "Google"
103
- logo = { googleLogo }
104
- onClick = { ( ) => {
105
- onSignInWithOauth ( "google" )
106
- } }
107
- />
108
- < div className = "flex items-center w-full gap-4" >
109
- < div className = "h-[1px] flex-1 bg-border" />
110
- < span className = "text-muted-foreground text-sm" > or</ span >
111
- < div className = "h-[1px] flex-1 bg-border" />
112
- </ div >
113
- < div className = "flex flex-col w-60" >
114
- < Form { ...magicLinkForm } >
115
- < form onSubmit = { magicLinkForm . handleSubmit ( onSignInWithMagicLink ) } >
116
- < FormField
117
- control = { magicLinkForm . control }
118
- name = "email"
119
- render = { ( { field } ) => (
120
- < FormItem className = "mb-4" >
121
- < FormLabel > Email</ FormLabel >
122
- < FormControl >
123
- < Input placeholder = "[email protected] " { ...
field } />
124
- </ FormControl >
125
- < FormMessage />
126
- </ FormItem >
127
- ) }
128
- />
129
- < Button type = "submit" className = "w-full" >
130
- Sign in
131
- </ Button >
132
- </ form >
133
- </ Form >
134
- < Form { ...emailPasswordForm } >
135
- < form onSubmit = { emailPasswordForm . handleSubmit ( onSignInWithEmailPassword ) } >
136
- < FormField
137
- control = { emailPasswordForm . control }
138
- name = "email"
139
- render = { ( { field } ) => (
140
- < FormItem className = "mb-4" >
141
- < FormLabel > Email</ FormLabel >
142
- < FormControl >
143
- < Input placeholder = "[email protected] " { ...
field } />
144
- </ FormControl >
145
- < FormMessage />
146
- </ FormItem >
147
- ) }
148
- />
149
- < FormField
150
- control = { emailPasswordForm . control }
151
- name = "password"
152
- render = { ( { field } ) => (
153
- < FormItem className = "mb-8" >
154
- < FormLabel > Password</ FormLabel >
155
- < FormControl >
156
- < Input type = "password" { ...field } />
157
- </ FormControl >
158
- < FormMessage />
159
- </ FormItem >
160
- ) }
161
- />
162
- < Button type = "submit" className = "w-full" >
163
- Sign in
164
- </ Button >
165
- </ form >
166
- </ Form >
167
- </ div >
168
- </ div >
64
+ < Card className = "flex flex-col items-center border p-12 rounded-lg gap-6 w-[500px] bg-background" >
65
+ { error && (
66
+ < div className = "text-sm text-destructive text-center text-wrap border p-2 rounded-md border-destructive" >
67
+ { errorMessage }
68
+ </ div >
69
+ ) }
70
+ < DividerSet
71
+ children = { [
72
+ ...( enabledMethods . github || enabledMethods . google ? [
73
+ < >
74
+ { enabledMethods . github && (
75
+ < ProviderButton
76
+ name = "GitHub"
77
+ logo = { getCodeHostIcon ( "github" ) ! }
78
+ onClick = { ( ) => {
79
+ onSignInWithOauth ( "github" )
80
+ } }
81
+ />
82
+ ) }
83
+ { enabledMethods . google && (
84
+ < ProviderButton
85
+ name = "Google"
86
+ logo = { { src : googleLogo } }
87
+ onClick = { ( ) => {
88
+ onSignInWithOauth ( "google" )
89
+ } }
90
+ />
91
+ ) }
92
+ </ >
93
+ ] : [ ] ) ,
94
+ ...( enabledMethods . magicLink ? [
95
+ < MagicLinkForm callbackUrl = { callbackUrl } />
96
+ ] : [ ] ) ,
97
+ ...( enabledMethods . credentials ? [
98
+ < CredentialsForm callbackUrl = { callbackUrl } />
99
+ ] : [ ] )
100
+ ] }
101
+ />
102
+ </ Card >
103
+ </ div >
169
104
)
170
105
}
171
106
172
107
const ProviderButton = ( {
173
108
name,
174
109
logo,
175
110
onClick,
111
+ className,
176
112
} : {
177
113
name : string ;
178
- logo : string ;
114
+ logo : { src : string , className ?: string } ;
179
115
onClick : ( ) => void ;
116
+ className ?: string ;
180
117
} ) => {
181
118
return (
182
- < Button onClick = { onClick } >
183
- { logo && < Image src = { logo } alt = { name } className = "w-5 h-5 invert dark:invert-0 mr-2" /> }
119
+ < Button
120
+ onClick = { onClick }
121
+ className = { cn ( "w-full" , className ) }
122
+ variant = "outline"
123
+ >
124
+ { logo && < Image src = { logo . src } alt = { name } className = { cn ( "w-5 h-5 mr-2" , logo . className ) } /> }
184
125
Sign in with { name }
185
126
</ Button >
186
127
)
128
+ }
129
+
130
+ const DividerSet = ( { children } : { children : React . ReactNode [ ] } ) => {
131
+ return children . map ( ( child , index ) => {
132
+ return (
133
+ < Fragment key = { index } >
134
+ { child }
135
+ { index < children . length - 1 && < Divider /> }
136
+ </ Fragment >
137
+ )
138
+ } )
139
+ }
140
+
141
+ const Divider = ( { className } : { className ?: string } ) => {
142
+ return (
143
+ < div className = { cn ( "flex items-center w-full gap-4" , className ) } >
144
+ < div className = "h-[1px] flex-1 bg-border" />
145
+ < span className = "text-muted-foreground text-sm" > or</ span >
146
+ < div className = "h-[1px] flex-1 bg-border" />
147
+ </ div >
148
+ )
187
149
}
0 commit comments