fix(mobile): show validate message, support for small screen (#3130)
* show validate message * support small screen
This commit is contained in:
parent
08b273ed4b
commit
38a9da26bf
|
|
@ -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)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
Loading…
Reference in New Issue