fix(mobile): show validate message, support for small screen (#3130)

* show validate message

* support small screen
This commit is contained in:
Stephen Zhou 2025-03-17 09:37:25 +08:00 committed by GitHub
parent 08b273ed4b
commit 38a9da26bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 97 additions and 82 deletions

View File

@ -20,14 +20,15 @@ export function SubmitButton({
title,
...props
}: PressableProps & { isLoading?: boolean; title: string }) {
const disabled = props.disabled || isLoading
const disableColor = useColor("gray6")
const disabledTextColor = useColor("gray2")
const disabledValue = useSharedValue(1)
useEffect(() => {
cancelAnimation(disabledValue)
disabledValue.value = withTiming(props.disabled ? 1 : 0)
}, [props.disabled])
disabledValue.value = withTiming(disabled ? 1 : 0)
}, [disabled])
const buttonStyle = useAnimatedStyle(() => ({
backgroundColor: interpolateColor(disabledValue.value, [1, 0], [disableColor, accentColor]),
@ -40,7 +41,7 @@ export function SubmitButton({
return (
<ReAnimatedPressable
{...props}
disabled={props.disabled || isLoading}
disabled={disabled}
style={[buttonStyle, props.style]}
className={cn("h-[48] flex-row items-center justify-center rounded-2xl", props.className)}
>

View File

@ -1,17 +1,13 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { useMutation } from "@tanstack/react-query"
import { router } from "expo-router"
import type { Control } from "react-hook-form"
import { useController, useForm } from "react-hook-form"
import type { TextInputProps } from "react-native"
import { Text, TouchableOpacity, View } from "react-native"
import { useCallback, useRef } from "react"
import { Alert, Text, TouchableOpacity, View } from "react-native"
import { KeyboardController } from "react-native-keyboard-controller"
import { z } from "zod"
import { SubmitButton } from "@/src/components/common/SubmitButton"
import { PlainTextField } from "@/src/components/ui/form/TextField"
import { signIn } from "@/src/lib/auth"
import { toast } from "@/src/lib/toast"
import { getTokenHeaders } from "@/src/lib/token"
import { accentColor } from "@/src/theme/colors"
@ -23,11 +19,18 @@ const formSchema = z.object({
type FormValue = z.infer<typeof formSchema>
async function onSubmit(values: FormValue) {
const result = formSchema.safeParse(values)
if (!result.success) {
const issue = result.error.issues[0]
Alert.alert("Invalid email or password", issue?.message)
return
}
await signIn
.email(
{
email: values.email,
password: values.password,
email: result.data.email,
password: result.data.password,
},
{
headers: await getTokenHeaders(),
@ -43,62 +46,39 @@ async function onSubmit(values: FormValue) {
}
})
.catch((error) => {
toast.error(error.message)
Alert.alert(error.message)
})
}
function Input({
control,
name,
...rest
}: TextInputProps & {
control: Control<FormValue>
name: keyof FormValue
}) {
const { field } = useController({
control,
name,
})
return (
<PlainTextField
hitSlop={10}
selectionColor={accentColor}
{...rest}
value={field.value}
onChangeText={field.onChange}
/>
)
}
export function EmailLogin() {
const { control, handleSubmit, formState } = useForm<FormValue>({
resolver: zodResolver(formSchema),
defaultValues: {
email: "",
password: "",
},
})
const emailValueRef = useRef("")
const passwordValueRef = useRef("")
const submitMutation = useMutation({
mutationFn: onSubmit,
})
const login = handleSubmit((values) => {
submitMutation.mutate(values)
})
const onLogin = useCallback(() => {
submitMutation.mutate({
email: emailValueRef.current,
password: passwordValueRef.current,
})
}, [submitMutation])
return (
<View className="mx-auto flex w-full max-w-sm">
<View className="bg-secondary-system-background gap-4 rounded-2xl px-6 py-4">
<View className="flex-row">
<Input
<PlainTextField
onChangeText={(text) => {
emailValueRef.current = text
}}
selectionColor={accentColor}
hitSlop={20}
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
autoComplete="email"
control={control}
name="email"
placeholder="Email"
className="text-text flex-1"
returnKeyType="next"
@ -109,28 +89,27 @@ export function EmailLogin() {
</View>
<View className="border-b-opaque-separator border-b-hairline" />
<View className="flex-row">
<Input
<PlainTextField
onChangeText={(text) => {
passwordValueRef.current = text
}}
selectionColor={accentColor}
hitSlop={20}
autoCapitalize="none"
autoCorrect={false}
autoComplete="current-password"
control={control}
name="password"
placeholder="Password"
className="text-text flex-1"
secureTextEntry
returnKeyType="go"
onSubmitEditing={() => {
login()
}}
onSubmitEditing={onLogin}
/>
</View>
</View>
<SubmitButton
disabled={submitMutation.isPending || !formState.isValid}
isLoading={submitMutation.isPending}
onPress={login}
onPress={onLogin}
title="Continue"
className="mt-8"
/>

View File

@ -6,36 +6,55 @@ import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanima
import * as ContextMenu from "zeego/context-menu"
import { Logo } from "@/src/components/ui/logo"
import { useScaleHeight } from "@/src/lib/responsive"
import { TermsMarkdown } from "@/src/screens/(headless)/terms"
import { EmailLogin } from "./email"
import { SocialLogin } from "./social"
export function Login() {
const logoSize = useScaleHeight()(80)
const gapSize = useScaleHeight()(28)
const fontSize = useScaleHeight()(28)
const lineHeight = useScaleHeight()(32)
return (
<View className="p-safe flex-1">
<TouchableWithoutFeedback
onPress={() => {
KeyboardController.dismiss()
}}
accessible={false}
>
<View className="mb-10 items-center gap-8">
<Logo style={{ width: 80, height: 80 }} />
<Text className="text-label text-3xl">
Sign in to <Text className="font-bold">Follow</Text>
</Text>
<EmailLogin />
<View className="p-safe pb-safe-or-2 flex-1 justify-between">
<View>
<TouchableWithoutFeedback
onPress={() => {
KeyboardController.dismiss()
}}
accessible={false}
>
<View
className="items-center"
style={{
gap: gapSize,
}}
>
<Logo style={{ width: logoSize, height: logoSize }} />
<Text
className="text-label"
style={{
fontSize,
lineHeight,
}}
>
Sign in to <Text className="font-bold">Follow</Text>
</Text>
<EmailLogin />
</View>
</TouchableWithoutFeedback>
<View className="border-t-opaque-separator border-t-hairline mx-28 mb-2 mt-4" />
<View className="items-center gap-4">
<View className="flex w-full max-w-sm flex-row items-center gap-4">
<View className="bg-separator my-4 h-[0.5px] flex-1" />
<Text className="text-secondary-label text-lg">or</Text>
<View className="bg-separator my-4 h-[0.5px] flex-1" />
</View>
<SocialLogin />
</View>
</TouchableWithoutFeedback>
<View className="border-t-opaque-separator border-t-hairline mx-28" />
<View className="mb-20 mt-2 items-center">
<View className="mb-4 flex w-full max-w-sm flex-row items-center gap-4">
<View className="bg-separator my-4 h-[0.5px] flex-1" />
<Text className="text-secondary-label text-lg">or</Text>
<View className="bg-separator my-4 h-[0.5px] flex-1" />
</View>
<SocialLogin />
</View>
<TermsCheckBox />
</View>

View File

@ -46,7 +46,7 @@ export default function ForgetPassword() {
<KeyboardAvoidingView behavior="padding" style={{ flex: 1 }}>
<View className="p-safe px-safe-offset-4 flex-1 justify-between">
<View className="items-center">
<Text className="text-text text-4xl font-bold">Forgot password?</Text>
<Text className="text-text text-center text-4xl font-bold">Forgot password?</Text>
<Text className="text-text mx-10 mt-6 text-lg">
Enter your email address that you use with your account to continue.
</Text>

View File

@ -12,6 +12,7 @@ import { SubmitButton } from "@/src/components/common/SubmitButton"
import { PlainTextField } from "@/src/components/ui/form/TextField"
import { Logo } from "@/src/components/ui/logo"
import { signUp } from "@/src/lib/auth"
import { useScaleHeight } from "@/src/lib/responsive"
import { toast } from "@/src/lib/toast"
import { getTokenHeaders } from "@/src/lib/token"
import { accentColor } from "@/src/theme/colors"
@ -157,6 +158,10 @@ function EmailSignUp() {
}
export default function SignUpModal() {
const logoSize = useScaleHeight()(80)
const gapSize = useScaleHeight()(28)
const fontSize = useScaleHeight()(28)
const lineHeight = useScaleHeight()(32)
return (
<View className="p-safe flex-1">
<TouchableWithoutFeedback
@ -165,9 +170,20 @@ export default function SignUpModal() {
}}
accessible={false}
>
<View className="flex-1 items-center gap-8">
<Logo style={{ width: 80, height: 80 }} />
<Text className="text-label text-3xl">
<View
className="flex-1 items-center"
style={{
gap: gapSize,
}}
>
<Logo style={{ width: logoSize, height: logoSize }} />
<Text
className="text-label"
style={{
fontSize,
lineHeight,
}}
>
Sign up to <Text className="font-bold">Follow</Text>
</Text>
<EmailSignUp />