ICS React Native App
1import { useState, useEffect, useCallback } from "react";
2import { useMutation } from "@tanstack/react-query";
3import { LoginRequest, LoginError, postLogin } from "@/generated";
4import { z } from "zod";
5import { useToken } from "@/providers/token-provider";
6import { useRouter } from "expo-router";
7
8const FieldErrorSchema = z.object({
9 errors: z.array(z.string()),
10});
11
12const LoginErrorSchema = z.object({
13 errors: z.array(z.string()),
14 properties: z
15 .object({
16 username: FieldErrorSchema.optional(),
17 password: FieldErrorSchema.optional(),
18 })
19 .optional(),
20});
21
22const isLoginError = (e: unknown): e is LoginError => {
23 const { success } = LoginErrorSchema.safeParse(e);
24 return success;
25};
26
27const fromError = (error: unknown): LoginError => ({
28 errors: [error instanceof Error ? error.message : "unknown error"],
29 properties: {},
30});
31
32export const useLogin = () => {
33 const { setToken, token } = useToken();
34 const router = useRouter();
35 const [error, setError] = useState<null | LoginError>(null);
36
37 const {
38 isPending,
39 mutateAsync,
40 error: mutationError,
41 data,
42 } = useMutation({
43 mutationFn: (body: LoginRequest) => {
44 setError(null);
45
46 return postLogin({ body });
47 },
48 });
49
50 useEffect(() => {
51 if (token) {
52 router.replace("/(tabs)");
53 }
54 }, [token, router]);
55
56 useEffect(() => {
57 if (mutationError) {
58 setError(fromError(mutationError));
59 }
60 }, [mutationError, setError]);
61
62 const login = useCallback(
63 async (request: LoginRequest) => {
64 try {
65 const { data, error } = await mutateAsync(request);
66
67 const realError = data && "error" in data ? data.error : error;
68
69 if (realError || !data) {
70 throw realError ?? "no data";
71 }
72
73 setToken(data);
74 router.push("/(tabs)");
75 } catch (e) {
76 setError(isLoginError(e) ? e : fromError(e));
77 }
78 },
79 [mutateAsync, setToken, router],
80 );
81
82 return {
83 data,
84 error,
85 loading: isPending,
86 login,
87 };
88};