11import { zodResolver } from '@hookform/resolvers/zod' ;
2- import { AlertTriangle , EyeIcon , EyeOffIcon } from 'lucide-react-native' ;
2+ import { AlertTriangle , EyeIcon , EyeOffIcon , LogIn , ShieldCheck } from 'lucide-react-native' ;
33import { useColorScheme } from 'nativewind' ;
44import React , { useState } from 'react' ;
55import type { SubmitHandler } from 'react-hook-form' ;
@@ -28,7 +28,7 @@ const createLoginFormSchema = () =>
2828 . string ( {
2929 required_error : 'Password is required' ,
3030 } )
31- . min ( 6 , 'Password must be at least 6 characters ' ) ,
31+ . min ( 1 , 'Password is required ' ) ,
3232 } ) ;
3333
3434const loginFormSchema = createLoginFormSchema ( ) ;
@@ -40,14 +40,17 @@ export type LoginFormProps = {
4040 isLoading ?: boolean ;
4141 error ?: string ;
4242 onServerUrlPress ?: ( ) => void ;
43+ /** Called when the user taps "Sign In with SSO" to navigate to the SSO login page */
44+ onSsoPress ?: ( ) => void ;
4345} ;
4446
45- export const LoginForm = ( { onSubmit = ( ) => { } , isLoading = false , error = undefined , onServerUrlPress } : LoginFormProps ) => {
47+ export const LoginForm = ( { onSubmit = ( ) => { } , isLoading = false , error = undefined , onServerUrlPress, onSsoPress } : LoginFormProps ) => {
4648 const { colorScheme } = useColorScheme ( ) ;
4749 const { t } = useTranslation ( ) ;
4850 const {
4951 control,
5052 handleSubmit,
53+ getValues,
5154 formState : { errors } ,
5255 } = useForm < FormType > ( {
5356 resolver : zodResolver ( loginFormSchema ) ,
@@ -60,9 +63,7 @@ export const LoginForm = ({ onSubmit = () => {}, isLoading = false, error = unde
6063 const [ showPassword , setShowPassword ] = useState ( false ) ;
6164
6265 const handleState = ( ) => {
63- setShowPassword ( ( showState ) => {
64- return ! showState ;
65- } ) ;
66+ setShowPassword ( ( showState ) => ! showState ) ;
6667 } ;
6768 const handleKeyPress = ( ) => {
6869 Keyboard . dismiss ( ) ;
@@ -74,12 +75,11 @@ export const LoginForm = ({ onSubmit = () => {}, isLoading = false, error = unde
7475 < View className = "flex-1 justify-center p-4" >
7576 < View className = "items-center justify-center" >
7677 < Image style = { { width : '96%' } } source = { colorScheme === 'dark' ? require ( '@assets/images/Resgrid_JustText_White.png' ) : require ( '@assets/images/Resgrid_JustText.png' ) } resizeMode = "contain" />
77- < Text className = "pb-6 text-center text-4xl font-bold" > Sign In</ Text >
78-
79- < Text className = "mb-6 max-w-xl text-center text-gray-500" >
80- To login in to the Resgrid Unit app, please enter your username and password. Resgrid Unit is an app designed to interface between a Unit (apparatus, team, etc) and the Resgrid system.
81- </ Text >
78+ < Text className = "pb-6 text-center text-4xl font-bold" > { t ( 'login.title' ) } </ Text >
79+ < Text className = "mb-6 max-w-xl text-center text-gray-500" > { t ( 'login.subtitle' ) } </ Text >
8280 </ View >
81+
82+ { /* Username */ }
8383 < FormControl isInvalid = { ! ! errors ?. username || ! validated . usernameValid } className = "w-full" >
8484 < FormControlLabel >
8585 < FormControlLabelText > { t ( 'login.username' ) } </ FormControlLabelText >
@@ -91,10 +91,10 @@ export const LoginForm = ({ onSubmit = () => {}, isLoading = false, error = unde
9191 rules = { {
9292 validate : async ( value ) => {
9393 try {
94- await loginFormSchema . parseAsync ( { username : value } ) ;
94+ await loginFormSchema . parseAsync ( { username : value , password : 'placeholder' } ) ;
9595 return true ;
96- } catch ( error : any ) {
97- return error . message ;
96+ } catch ( err : any ) {
97+ return err . message ;
9898 }
9999 } ,
100100 } }
@@ -106,7 +106,7 @@ export const LoginForm = ({ onSubmit = () => {}, isLoading = false, error = unde
106106 onChangeText = { onChange }
107107 onBlur = { onBlur }
108108 onSubmitEditing = { handleKeyPress }
109- returnKeyType = "done "
109+ returnKeyType = "next "
110110 autoCapitalize = "none"
111111 autoComplete = "off"
112112 />
@@ -115,10 +115,11 @@ export const LoginForm = ({ onSubmit = () => {}, isLoading = false, error = unde
115115 />
116116 < FormControlError >
117117 < FormControlErrorIcon as = { AlertTriangle } className = "text-red-500" />
118- < FormControlErrorText className = "text-red-500" > { errors ?. username ?. message || ( ! validated . usernameValid && 'Username not found' ) } </ FormControlErrorText >
118+ < FormControlErrorText className = "text-red-500" > { errors ?. username ?. message } </ FormControlErrorText >
119119 </ FormControlError >
120120 </ FormControl >
121- { /* Label Message */ }
121+
122+ { /* Password form */ }
122123 < FormControl isInvalid = { ! ! errors . password || ! validated . passwordValid } className = "w-full" >
123124 < FormControlLabel >
124125 < FormControlLabelText > { t ( 'login.password' ) } </ FormControlLabelText >
@@ -130,10 +131,10 @@ export const LoginForm = ({ onSubmit = () => {}, isLoading = false, error = unde
130131 rules = { {
131132 validate : async ( value ) => {
132133 try {
133- await loginFormSchema . parseAsync ( { password : value } ) ;
134+ await loginFormSchema . parseAsync ( { username : getValues ( 'username' ) , password : value } ) ;
134135 return true ;
135- } catch ( error : any ) {
136- return error . message ;
136+ } catch ( err : any ) {
137+ return err . message ;
137138 }
138139 } ,
139140 } }
@@ -168,16 +169,27 @@ export const LoginForm = ({ onSubmit = () => {}, isLoading = false, error = unde
168169 < ButtonText className = "ml-2 text-sm font-medium" > { t ( 'login.login_button_loading' ) } </ ButtonText >
169170 </ Button >
170171 ) : (
171- < Button className = "mt-8 w-full" variant = "solid" action = "primary" onPress = { handleSubmit ( onSubmit ) } >
172- < ButtonText > Log in </ ButtonText >
172+ < Button className = "mt-8 w-full" variant = "solid" action = "primary" onPress = { handleSubmit ( onSubmit ) } accessibilityLabel = { t ( 'login.login_button' ) } >
173+ < ButtonText > { t ( 'login.login_button' ) } </ ButtonText >
173174 </ Button >
174175 ) }
175176
176- { onServerUrlPress && (
177- < Button className = "mt-14 w-full" variant = "outline" action = "secondary" onPress = { onServerUrlPress } >
178- < ButtonText > { t ( 'settings.server_url' ) } </ ButtonText >
179- </ Button >
180- ) }
177+ { error ? < Text className = "mt-4 text-center text-sm text-red-500" > { error } </ Text > : null }
178+
179+ { /* Server URL + Sign In with SSO — side by side small buttons */ }
180+ < View className = "mt-6 flex-row gap-x-2" >
181+ { onServerUrlPress ? (
182+ < Button className = "flex-1" variant = "outline" action = "secondary" size = "sm" onPress = { onServerUrlPress } >
183+ < ButtonText className = "text-xs" > { t ( 'settings.server_url' ) } </ ButtonText >
184+ </ Button >
185+ ) : null }
186+ { onSsoPress ? (
187+ < Button className = "flex-1" variant = "outline" action = "secondary" size = "sm" onPress = { onSsoPress } >
188+ < ShieldCheck size = { 14 } style = { { marginRight : 4 } } />
189+ < ButtonText className = "text-xs" > { t ( 'login.sso_button' ) } </ ButtonText >
190+ </ Button >
191+ ) : null }
192+ </ View >
181193 </ View >
182194 </ KeyboardAvoidingView >
183195 ) ;
0 commit comments