This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

semble / src / webapp / features / auth / components / loginForm / LoginForm.tsx
7.1 kB 253 lines
1'use client'; 2 3import { ExtensionService } from '@/services/extensionService'; 4import { 5 Stack, 6 Text, 7 Button, 8 UnstyledButton, 9 TextInput, 10 PasswordInput, 11 Alert, 12} from '@mantine/core'; 13import { BiRightArrowAlt } from 'react-icons/bi'; 14import { MdOutlineAlternateEmail, MdLock } from 'react-icons/md'; 15import { useAuth } from '@/hooks/useAuth'; 16import { useForm } from '@mantine/form'; 17import { useRouter, useSearchParams } from 'next/navigation'; 18import { useEffect, useState } from 'react'; 19import { createSembleClient } from '@/services/apiClient'; 20 21export default function LoginForm() { 22 const router = useRouter(); 23 const searchParams = useSearchParams(); 24 const { isAuthenticated, refreshAuth } = useAuth(); 25 26 const [isCheckingAuth, setIsCheckingAuth] = useState(true); 27 const [isLoading, setIsLoading] = useState(false); 28 const [error, setError] = useState(''); 29 30 const isExtensionLogin = searchParams.get('extension-login') === 'true'; 31 const client = createSembleClient(); 32 33 const handleExtensionTokenGeneration = async () => { 34 try { 35 setIsLoading(true); 36 const tokens = await client.generateExtensionTokens(); 37 38 await ExtensionService.sendTokensToExtension(tokens); 39 40 setError(''); 41 42 // Clear the extension tokens requested flag 43 ExtensionService.clearExtensionTokensRequested(); 44 45 // Redirect to extension success page after successful extension token generation 46 router.push('/extension/auth/complete'); 47 } catch (err: any) { 48 // Clear the flag even on failure 49 ExtensionService.clearExtensionTokensRequested(); 50 51 // Redirect to extension error page 52 router.push('/extension/auth/error'); 53 } finally { 54 setIsLoading(false); 55 } 56 }; 57 58 useEffect(() => { 59 if (isAuthenticated) { 60 isExtensionLogin 61 ? handleExtensionTokenGeneration() 62 : router.push('/home'); 63 } else { 64 setIsCheckingAuth(false); 65 } 66 }, [isAuthenticated, isExtensionLogin]); 67 68 const handleOAuthSubmit = async (e: React.FormEvent) => { 69 e.preventDefault(); 70 71 // validate form 72 const isValid = form.validateField('handle'); 73 if (!isValid) return; 74 75 try { 76 setIsLoading(true); 77 setError(''); 78 79 if (isExtensionLogin) { 80 ExtensionService.setExtensionTokensRequested(); 81 } 82 console.log('HANDLE', form.values.handle.trimEnd()); 83 const { authUrl } = await client.initiateOAuthSignIn({ 84 handle: form.values.handle.trimEnd(), 85 }); 86 87 window.location.href = authUrl; 88 } catch (err: any) { 89 setError(err.message || 'An error occurred during login'); 90 } finally { 91 setIsLoading(false); 92 } 93 }; 94 95 const handleAppPasswordSubmit = async (e: React.FormEvent) => { 96 e.preventDefault(); 97 98 // validate 99 const validation = form.validate(); 100 if (validation.hasErrors) return; 101 102 try { 103 setIsLoading(true); 104 setError(''); 105 106 await client.loginWithAppPassword({ 107 identifier: form.values.handle.trimEnd(), 108 appPassword: form.values.appPassword, 109 }); 110 111 // Refresh auth state to fetch user profile with new tokens (cookies are set automatically) 112 await refreshAuth(); 113 114 if (isExtensionLogin) { 115 await handleExtensionTokenGeneration(); 116 } else { 117 router.push('/home'); 118 } 119 } catch (err: any) { 120 setError(err.message || 'Invalid credentials'); 121 } finally { 122 setIsLoading(false); 123 } 124 }; 125 126 const form = useForm({ 127 initialValues: { 128 handle: '', 129 appPassword: '', 130 useAppPassword: false, 131 }, 132 133 validate: { 134 handle: (value) => (value.trim() ? null : 'Handle is required'), 135 appPassword: (value, values) => 136 values.useAppPassword && value.trim() === '' 137 ? 'App password is required' 138 : null, 139 }, 140 }); 141 142 if (form.values.useAppPassword) { 143 return ( 144 <Stack gap={'lg'}> 145 <form onSubmit={handleAppPasswordSubmit}> 146 <Stack align="center"> 147 <TextInput 148 autoComplete="username" 149 name="username" 150 label="Handle" 151 placeholder="you.bsky.social" 152 key={form.key('handle')} 153 value={form.values.handle} 154 onChange={(event) => { 155 form.setFieldValue('handle', event.currentTarget.value); 156 if (error) setError(''); 157 }} 158 leftSection={<MdOutlineAlternateEmail size={22} />} 159 variant="filled" 160 size="lg" 161 w={'100%'} 162 required 163 /> 164 <PasswordInput 165 autoComplete="password" 166 name="password" 167 label="App password" 168 placeholder="Your password" 169 key={form.key('appPassword')} 170 {...form.getInputProps('appPassword')} 171 leftSection={<MdLock size={22} />} 172 variant="filled" 173 size="lg" 174 w={'100%'} 175 required 176 /> 177 <Button 178 type="submit" 179 size="lg" 180 color="dark" 181 fullWidth 182 rightSection={<BiRightArrowAlt size={22} />} 183 loading={isLoading} 184 > 185 Log in 186 </Button> 187 {error && <Alert title={error} color="red" />} 188 </Stack> 189 </form> 190 <Stack align="center"> 191 <UnstyledButton 192 fw={500} 193 onClick={() => { 194 form.setFieldValue('useAppPassword', false); 195 setError(''); 196 }} 197 > 198 Use OAuth login 199 </UnstyledButton> 200 </Stack> 201 </Stack> 202 ); 203 } 204 205 return ( 206 <Stack gap={'xl'}> 207 <form onSubmit={handleOAuthSubmit}> 208 <Stack align="center"> 209 <TextInput 210 autoComplete="username" 211 name="username" 212 label="Handle" 213 placeholder="you.bsky.social" 214 key={form.key('handle')} 215 value={form.values.handle} 216 onChange={(event) => { 217 form.setFieldValue('handle', event.currentTarget.value); 218 if (error) setError(''); 219 }} 220 leftSection={<MdOutlineAlternateEmail size={22} />} 221 variant="filled" 222 size="lg" 223 w={'100%'} 224 required 225 /> 226 <Button 227 type="submit" 228 size="lg" 229 color="dark" 230 fullWidth 231 rightSection={<BiRightArrowAlt size={22} />} 232 loading={isLoading} 233 > 234 Log in 235 </Button> 236 {error && <Alert title={error} color="red" />} 237 <Text fw={500} c={'stone'}> 238 Or 239 </Text> 240 <UnstyledButton 241 fw={500} 242 onClick={() => { 243 form.setFieldValue('useAppPassword', true); 244 setError(''); 245 }} 246 > 247 Use your app password 248 </UnstyledButton> 249 </Stack> 250 </form> 251 </Stack> 252 ); 253}