1- import { getAuth } from 'firebase/auth'
2- import { Button , Card } from 'flowbite-react'
1+ import { getAuth , signInWithEmailAndPassword } from 'firebase/auth'
2+ import { Button , Card , Label , TextInput } from 'flowbite-react'
33import Image from 'next/image'
44import Link from 'next/link'
55import { useRouter } from 'next/router'
6- import React from 'react'
6+ import React , { useState } from 'react'
77import {
88 useSignInWithGithub ,
99 useSignInWithGoogle ,
@@ -21,6 +21,11 @@ const AuthUI: React.FC<{}> = ({}) => {
2121 const { t } = useNextTranslation ( )
2222 const router = useRouter ( )
2323 const auth = getAuth ( app )
24+ // Email/password form state
25+ const [ email , setEmail ] = useState ( '' )
26+ const [ password , setPassword ] = useState ( '' )
27+ const [ emailSignInLoading , setEmailSignInLoading ] = useState ( false )
28+ const [ showEmailForm , setShowEmailForm ] = useState ( false )
2429 const [
2530 signInWithGoogle ,
2631 _googleUser ,
@@ -42,6 +47,50 @@ const AuthUI: React.FC<{}> = ({}) => {
4247 if ( loggedIn ) router . push ( fromUrl ?? '/nodes' )
4348 } , [ loggedIn , router , fromUrl ] )
4449
50+ // Email/password sign-in handler
51+ const handleEmailSignIn = async ( e : React . FormEvent ) => {
52+ e . preventDefault ( )
53+
54+ // Basic validation
55+ if ( ! email ) {
56+ toast . error ( t ( 'Email is required' ) )
57+ return
58+ }
59+ if ( ! password ) {
60+ toast . error ( t ( 'Password is required' ) )
61+ return
62+ }
63+ if ( password . length < 6 ) {
64+ toast . error ( t ( 'Password must be at least 6 characters' ) )
65+ return
66+ }
67+
68+ setEmailSignInLoading ( true )
69+ try {
70+ await signInWithEmailAndPassword ( auth , email , password )
71+ analytic . track ( 'Sign In' , { provider : 'Email' } )
72+ toast . success ( t ( 'Sign In successful' ) )
73+ } catch ( error : any ) {
74+ console . error ( 'Email sign-in error:' , error )
75+ if ( error . code === 'auth/user-not-found' ) {
76+ toast . error ( t ( 'No account found with this email' ) )
77+ } else if ( error . code === 'auth/wrong-password' ) {
78+ toast . error ( t ( 'Incorrect password' ) )
79+ } else if ( error . code === 'auth/invalid-email' ) {
80+ toast . error ( t ( 'Invalid email format' ) )
81+ } else if ( error . code === 'auth/too-many-requests' ) {
82+ toast . error ( t ( 'Too many failed attempts. Please try again later' ) )
83+ } else {
84+ toast . error (
85+ error . message ||
86+ t ( 'An unexpected error occurred. Please try again later.' )
87+ )
88+ }
89+ } finally {
90+ setEmailSignInLoading ( false )
91+ }
92+ }
93+
4594 // handle errors
4695 React . useEffect ( ( ) => {
4796 if ( googleSignInError ) {
@@ -92,86 +141,156 @@ const AuthUI: React.FC<{}> = ({}) => {
92141 { t ( 'Sign In' ) }
93142 </ h1 >
94143
95- < div className = "mt-10 space-y-3 sm:space-x-4 sm:space-y-0" >
96- < Button
97- color = "gray"
98- href = "#"
99- className = "font-bold "
100- onClick = { ( ) => {
101- analytic . track ( 'Sign In' , {
102- provider : 'Google' ,
103- } )
104- signInWithGoogle ( )
105- } }
106- >
107- < svg
108- className = "w-5 h-5 mr-2"
109- viewBox = "0 0 21 20"
110- fill = "#ffff"
111- xmlns = "http://www.w3.org/2000/svg"
144+ { ! showEmailForm ? (
145+ < >
146+ < div className = "mt-10 space-y-3 sm:space-x-4 sm:space-y-0" >
147+ < Button
148+ color = "gray"
149+ href = "#"
150+ className = "font-bold "
151+ onClick = { ( ) => {
152+ analytic . track ( 'Sign In' , {
153+ provider : 'Google' ,
154+ } )
155+ signInWithGoogle ( )
156+ } }
157+ >
158+ < svg
159+ className = "w-5 h-5 mr-2"
160+ viewBox = "0 0 21 20"
161+ fill = "#ffff"
162+ xmlns = "http://www.w3.org/2000/svg"
163+ >
164+ < g clipPath = "url(#clip0_13183_10121)" >
165+ < path
166+ d = "M20.3081 10.2303C20.3081 9.55056 20.253 8.86711 20.1354 8.19836H10.7031V12.0492H16.1046C15.8804 13.2911 15.1602 14.3898 14.1057 15.0879V17.5866H17.3282C19.2205 15.8449 20.3081 13.2728 20.3081 10.2303Z"
167+ fill = "#3F83F8"
168+ />
169+ < path
170+ d = "M10.7019 20.0006C13.3989 20.0006 15.6734 19.1151 17.3306 17.5865L14.1081 15.0879C13.2115 15.6979 12.0541 16.0433 10.7056 16.0433C8.09669 16.0433 5.88468 14.2832 5.091 11.9169H1.76562V14.4927C3.46322 17.8695 6.92087 20.0006 10.7019 20.0006V20.0006Z"
171+ fill = "#34A853"
172+ />
173+ < path
174+ d = "M5.08857 11.9169C4.66969 10.6749 4.66969 9.33008 5.08857 8.08811V5.51233H1.76688C0.348541 8.33798 0.348541 11.667 1.76688 14.4927L5.08857 11.9169V11.9169Z"
175+ fill = "#FBBC04"
176+ />
177+ < path
178+ d = "M10.7019 3.95805C12.1276 3.936 13.5055 4.47247 14.538 5.45722L17.393 2.60218C15.5852 0.904587 13.1858 -0.0287217 10.7019 0.000673888C6.92087 0.000673888 3.46322 2.13185 1.76562 5.51234L5.08732 8.08813C5.87733 5.71811 8.09302 3.95805 10.7019 3.95805V3.95805Z"
179+ fill = "#EA4335"
180+ />
181+ </ g >
182+ < defs >
183+ < clipPath id = "clip0_13183_10121" >
184+ < rect
185+ width = "20"
186+ height = "20"
187+ fill = "white"
188+ transform = "translate(0.5)"
189+ />
190+ </ clipPath >
191+ </ defs >
192+ </ svg >
193+ < span className = "text-gray-900" >
194+ { t ( 'Continue with Google' ) }
195+ </ span >
196+ </ Button >
197+ </ div >
198+ < Button
199+ color = "gray"
200+ href = "#"
201+ className = "mt-2 font-bold hover:bg-gray-50"
202+ onClick = { ( ) => {
203+ analytic . track ( 'Sign In' , {
204+ provider : 'Github' ,
205+ } )
206+ signInWithGithub ( [ 'user:email' ] )
207+ } }
112208 >
113- < g clipPath = "url(#clip0_13183_10121)" >
114- < path
115- d = "M20.3081 10.2303C20.3081 9.55056 20.253 8.86711 20.1354 8.19836H10.7031V12.0492H16.1046C15.8804 13.2911 15.1602 14.3898 14.1057 15.0879V17.5866H17.3282C19.2205 15.8449 20.3081 13.2728 20.3081 10.2303Z"
116- fill = "#3F83F8"
117- />
118- < path
119- d = "M10.7019 20.0006C13.3989 20.0006 15.6734 19.1151 17.3306 17.5865L14.1081 15.0879C13.2115 15.6979 12.0541 16.0433 10.7056 16.0433C8.09669 16.0433 5.88468 14.2832 5.091 11.9169H1.76562V14.4927C3.46322 17.8695 6.92087 20.0006 10.7019 20.0006V20.0006Z"
120- fill = "#34A853"
121- />
122- < path
123- d = "M5.08857 11.9169C4.66969 10.6749 4.66969 9.33008 5.08857 8.08811V5.51233H1.76688C0.348541 8.33798 0.348541 11.667 1.76688 14.4927L5.08857 11.9169V11.9169Z"
124- fill = "#FBBC04"
125- />
209+ < svg
210+ className = "w-6 h-6 text-gray-800 dark:text-white"
211+ aria-hidden = "true"
212+ xmlns = "http://www.w3.org/2000/svg"
213+ width = "24"
214+ height = "24"
215+ fill = "currentColor"
216+ viewBox = "0 0 24 24"
217+ >
126218 < path
127- d = "M10.7019 3.95805C12.1276 3.936 13.5055 4.47247 14.538 5.45722L17.393 2.60218C15.5852 0.904587 13.1858 -0.0287217 10.7019 0.000673888C6.92087 0.000673888 3.46322 2.13185 1.76562 5.51234L5.08732 8.08813C5.87733 5.71811 8.09302 3.95805 10.7019 3.95805V3.95805Z"
128- fill = "#EA4335"
219+ fillRule = "evenodd"
220+ d = "M12.006 2a9.847 9.847 0 0 0-6.484 2.44 10.32 10.32 0 0 0-3.393 6.17 10.48 10.48 0 0 0 1.317 6.955 10.045 10.045 0 0 0 5.4 4.418c.504.095.683-.223.683-.494 0-.245-.01-1.052-.014-1.908-2.78.62-3.366-1.21-3.366-1.21a2.711 2.711 0 0 0-1.11-1.5c-.907-.637.07-.621.07-.621.317.044.62.163.885.346.266.183.487.426.647.71.135.253.318.476.538.655a2.079 2.079 0 0 0 2.37.196c.045-.52.27-1.006.635-1.37-2.219-.259-4.554-1.138-4.554-5.07a4.022 4.022 0 0 1 1.031-2.75 3.77 3.77 0 0 1 .096-2.713s.839-.275 2.749 1.05a9.26 9.26 0 0 1 5.004 0c1.906-1.325 2.74-1.05 2.74-1.05.37.858.406 1.828.101 2.713a4.017 4.017 0 0 1 1.029 2.75c0 3.939-2.339 4.805-4.564 5.058a2.471 2.471 0 0 1 .679 1.897c0 1.372-.012 2.477-.012 2.814 0 .272.18.592.687.492a10.05 10.05 0 0 0 5.388-4.421 10.473 10.473 0 0 0 1.313-6.948 10.32 10.32 0 0 0-3.39-6.165A9.847 9.847 0 0 0 12.007 2Z"
221+ clipRule = "evenodd"
129222 />
130- </ g >
131- < defs >
132- < clipPath id = "clip0_13183_10121" >
133- < rect
134- width = "20"
135- height = "20"
136- fill = "white"
137- transform = "translate(0.5)"
138- />
139- </ clipPath >
140- </ defs >
141- </ svg >
142- < span className = "text-gray-900" >
143- { t ( 'Continue with Google' ) }
144- </ span >
145- </ Button >
146- </ div >
147- < Button
148- color = "gray"
149- href = "#"
150- className = "mt-2 font-bold hover:bg-gray-50"
151- onClick = { ( ) => {
152- analytic . track ( 'Sign In' , {
153- provider : 'Github' ,
154- } )
155- signInWithGithub ( [ 'user:email' ] )
156- } }
157- >
158- < svg
159- className = "w-6 h-6 text-gray-800 dark:text-white"
160- aria-hidden = "true"
161- xmlns = "http://www.w3.org/2000/svg"
162- width = "24"
163- height = "24"
164- fill = "currentColor"
165- viewBox = "0 0 24 24"
166- >
167- < path
168- fillRule = "evenodd"
169- d = "M12.006 2a9.847 9.847 0 0 0-6.484 2.44 10.32 10.32 0 0 0-3.393 6.17 10.48 10.48 0 0 0 1.317 6.955 10.045 10.045 0 0 0 5.4 4.418c.504.095.683-.223.683-.494 0-.245-.01-1.052-.014-1.908-2.78.62-3.366-1.21-3.366-1.21a2.711 2.711 0 0 0-1.11-1.5c-.907-.637.07-.621.07-.621.317.044.62.163.885.346.266.183.487.426.647.71.135.253.318.476.538.655a2.079 2.079 0 0 0 2.37.196c.045-.52.27-1.006.635-1.37-2.219-.259-4.554-1.138-4.554-5.07a4.022 4.022 0 0 1 1.031-2.75 3.77 3.77 0 0 1 .096-2.713s.839-.275 2.749 1.05a9.26 9.26 0 0 1 5.004 0c1.906-1.325 2.74-1.05 2.74-1.05.37.858.406 1.828.101 2.713a4.017 4.017 0 0 1 1.029 2.75c0 3.939-2.339 4.805-4.564 5.058a2.471 2.471 0 0 1 .679 1.897c0 1.372-.012 2.477-.012 2.814 0 .272.18.592.687.492a10.05 10.05 0 0 0 5.388-4.421 10.473 10.473 0 0 0 1.313-6.948 10.32 10.32 0 0 0-3.39-6.165A9.847 9.847 0 0 0 12.007 2Z"
170- clipRule = "evenodd"
171- />
172- </ svg >
173- < span className = "text-gray-900" > { t ( 'Continue with GitHub' ) } </ span >
174- </ Button >
223+ </ svg >
224+ < span className = "text-gray-900" >
225+ { t ( 'Continue with GitHub' ) }
226+ </ span >
227+ </ Button >
228+
229+ < div className = "mt-4 text-center" >
230+ < span className = "text-gray-400" >
231+ { t ( 'Or sign in with email' ) }
232+ </ span >
233+ </ div >
234+
235+ < Button
236+ color = "light"
237+ className = "mt-2 w-full"
238+ onClick = { ( ) => setShowEmailForm ( true ) }
239+ >
240+ { t ( 'Sign In with Email' ) }
241+ </ Button >
242+ </ >
243+ ) : (
244+ < form onSubmit = { handleEmailSignIn } className = "mt-10 space-y-4" >
245+ < div >
246+ < Label
247+ htmlFor = "email"
248+ value = { t ( 'Email address' ) }
249+ className = "text-white"
250+ />
251+ < TextInput
252+ id = "email"
253+ type = "email"
254+ value = { email }
255+ onChange = { ( e ) => setEmail ( e . target . value ) }
256+ placeholder = { t ( 'Enter your email' ) }
257+ required
258+ className = "mt-1"
259+ />
260+ </ div >
261+ < div >
262+ < Label
263+ htmlFor = "password"
264+ value = { t ( 'Password' ) }
265+ className = "text-white"
266+ />
267+ < TextInput
268+ id = "password"
269+ type = "password"
270+ value = { password }
271+ onChange = { ( e ) => setPassword ( e . target . value ) }
272+ placeholder = { t ( 'Enter your password' ) }
273+ required
274+ className = "mt-1"
275+ />
276+ </ div >
277+ < Button
278+ type = "submit"
279+ className = "w-full"
280+ disabled = { emailSignInLoading }
281+ >
282+ { emailSignInLoading ? t ( 'Signing in...' ) : t ( 'Sign In' ) }
283+ </ Button >
284+ < Button
285+ type = "button"
286+ color = "light"
287+ className = "w-full"
288+ onClick = { ( ) => setShowEmailForm ( false ) }
289+ >
290+ { t ( 'Back to social login' ) }
291+ </ Button >
292+ </ form >
293+ ) }
175294 </ Card >
176295 </ div >
177296 </ div >
0 commit comments