119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
import { useRouter } from "expo-router";
|
|
import { useState } from "react";
|
|
import { View, Text, TouchableOpacity } from "react-native";
|
|
import { z } from "zod";
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
|
|
import { AuthLayout } from "@/components/auth/AuthLayout";
|
|
import { AuthHeader } from "@/components/auth/AuthHeader";
|
|
import { AuthCard } from "@/components/auth/AuthCard";
|
|
import { FormInput } from "@/components/auth/FormInput";
|
|
import { getApiUrl } from "@/store/api/baseApi";
|
|
import { AuthButton } from "@/components/auth/AuthButton";
|
|
import { MessageDisplay } from "@/components/auth/MessageDisplay";
|
|
|
|
const schema = z.object({
|
|
email: z.email("Enter a valid email"),
|
|
});
|
|
|
|
type FormValues = z.infer<typeof schema>;
|
|
|
|
const ForgotPasswordScreen = () => {
|
|
const router = useRouter();
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [message, setMessage] = useState("");
|
|
const [error, setError] = useState("");
|
|
|
|
const {
|
|
handleSubmit,
|
|
setValue,
|
|
watch,
|
|
formState: { errors },
|
|
} = useForm<FormValues>({
|
|
resolver: zodResolver(schema),
|
|
mode: "onSubmit",
|
|
reValidateMode: "onChange",
|
|
shouldUnregister: false,
|
|
defaultValues: { email: "" },
|
|
});
|
|
const email = watch("email");
|
|
|
|
const onSubmit = async (values: FormValues) => {
|
|
setSubmitting(true);
|
|
setError("");
|
|
setMessage("");
|
|
try {
|
|
const res = await fetch(
|
|
`${getApiUrl()?.replace(/\/$/, "")}/auth/password/forgot`,
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ email: values.email }),
|
|
}
|
|
);
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
throw new Error(data?.error || "Failed to request reset");
|
|
}
|
|
|
|
setMessage("If an account exists for this email, a reset link was sent");
|
|
} catch (error: unknown) {
|
|
if (error instanceof Error) {
|
|
setError(error.message);
|
|
} else {
|
|
setError("Failed to request reset");
|
|
}
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AuthLayout>
|
|
<AuthHeader title="Bird Eye View" />
|
|
<AuthCard title="Forgot Password" subtitle="We'll email you a reset link">
|
|
{error ? <MessageDisplay message={error} type="error" /> : null}
|
|
{message ? <MessageDisplay message={message} type="success" /> : null}
|
|
<FormInput
|
|
label="Email"
|
|
value={email}
|
|
onChangeText={(t) => setValue("email", t, { shouldValidate: true })}
|
|
icon="email"
|
|
disabled={submitting}
|
|
onSubmitEditing={handleSubmit(onSubmit)}
|
|
returnKeyType="send"
|
|
submitBehavior="blurAndSubmit"
|
|
/>
|
|
{errors.email?.message ? (
|
|
<Text style={{ color: "#F83434", marginTop: -4, marginBottom: 8 }}>
|
|
{errors.email.message}
|
|
</Text>
|
|
) : null}
|
|
<AuthButton
|
|
onPress={handleSubmit(onSubmit)}
|
|
loading={submitting}
|
|
disabled={submitting}
|
|
>
|
|
{submitting ? "Sending..." : "Send Reset Link"}
|
|
</AuthButton>
|
|
|
|
<View style={{ alignItems: "center", marginTop: 16 }}>
|
|
<Text>Remembered your password?</Text>
|
|
<TouchableOpacity
|
|
onPress={() => router.push("/sign-in")}
|
|
disabled={submitting}
|
|
>
|
|
<Text className="text-primaryPurpleDark font-bold mt-4">
|
|
Sign in
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</AuthCard>
|
|
</AuthLayout>
|
|
);
|
|
};
|
|
|
|
export default ForgotPasswordScreen;
|