diff options
| author | MohamedBassem <me@mbassem.com> | 2024-03-22 15:10:24 +0000 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-03-22 15:14:24 +0000 |
| commit | 2cd2f92e9e0c82eaa5f21fe0c30e20ebea7aba24 (patch) | |
| tree | acf86f033b976a40079c3efe8ec1fb727ae2a452 | |
| parent | 95cc9e6ff29cd39dc80aa09c80a6d1c9489b5d6a (diff) | |
| download | karakeep-2cd2f92e9e0c82eaa5f21fe0c30e20ebea7aba24.tar.zst | |
fix(mobile): Fix setting propagatin
| -rw-r--r-- | apps/mobile/app.json | 2 | ||||
| -rw-r--r-- | apps/mobile/app/dashboard/(tabs)/settings.tsx | 22 | ||||
| -rw-r--r-- | apps/mobile/app/dashboard/_layout.tsx | 12 | ||||
| -rw-r--r-- | apps/mobile/app/index.tsx | 19 | ||||
| -rw-r--r-- | apps/mobile/app/signin.tsx | 15 | ||||
| -rw-r--r-- | apps/mobile/lib/providers.tsx | 33 | ||||
| -rw-r--r-- | apps/mobile/lib/session.ts | 15 | ||||
| -rw-r--r-- | apps/mobile/lib/settings.ts | 51 | ||||
| -rw-r--r-- | apps/mobile/lib/storage-state.ts | 51 | ||||
| -rw-r--r-- | apps/mobile/package.json | 3 | ||||
| -rw-r--r-- | pnpm-lock.yaml | 3 |
11 files changed, 91 insertions, 135 deletions
diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 9df4c895..a857022c 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -3,7 +3,7 @@ "name": "Hoarder App", "slug": "hoarder", "scheme": "hoarder", - "version": "1.3.0", + "version": "1.3.1", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", diff --git a/apps/mobile/app/dashboard/(tabs)/settings.tsx b/apps/mobile/app/dashboard/(tabs)/settings.tsx index fe138f52..f60c2495 100644 --- a/apps/mobile/app/dashboard/(tabs)/settings.tsx +++ b/apps/mobile/app/dashboard/(tabs)/settings.tsx @@ -1,29 +1,17 @@ -import { useEffect } from "react"; import { SafeAreaView, Text, View } from "react-native"; -import { useRouter } from "expo-router"; import { Button } from "@/components/ui/Button"; +import PageTitle from "@/components/ui/PageTitle"; import { useSession } from "@/lib/session"; import { api } from "@/lib/trpc"; -import PageTitle from "@/components/ui/PageTitle"; export default function Dashboard() { - const router = useRouter(); - - const { isLoggedIn, logout } = useSession(); - - useEffect(() => { - if (isLoggedIn !== undefined && !isLoggedIn) { - router.replace("signin"); - } - }, [isLoggedIn]); + const { logout } = useSession(); const { data, error, isLoading } = api.users.whoami.useQuery(); - useEffect(() => { - if (error?.data?.code === "UNAUTHORIZED") { - logout(); - } - }, [error]); + if (error?.data?.code === "UNAUTHORIZED") { + logout(); + } return ( <SafeAreaView> diff --git a/apps/mobile/app/dashboard/_layout.tsx b/apps/mobile/app/dashboard/_layout.tsx index bb14a203..ef04fcd1 100644 --- a/apps/mobile/app/dashboard/_layout.tsx +++ b/apps/mobile/app/dashboard/_layout.tsx @@ -1,6 +1,18 @@ +import { useIsLoggedIn } from "@/lib/session"; +import { useRouter } from "expo-router"; import { Stack } from "expo-router/stack"; +import { useEffect } from "react"; export default function Dashboard() { + const router = useRouter(); + + const isLoggedIn = useIsLoggedIn(); + useEffect(() => { + if (isLoggedIn !== undefined && !isLoggedIn) { + return router.replace("signin"); + } + }, [isLoggedIn]); + return ( <Stack> <Stack.Screen diff --git a/apps/mobile/app/index.tsx b/apps/mobile/app/index.tsx index f075fd6d..235b19e4 100644 --- a/apps/mobile/app/index.tsx +++ b/apps/mobile/app/index.tsx @@ -1,19 +1,16 @@ -import { useEffect } from "react"; -import { View } from "react-native"; -import { useRouter } from "expo-router"; -import { useSession } from "@/lib/session"; +import { useIsLoggedIn } from "@/lib/session"; +import { Redirect } from "expo-router"; +import FullPageSpinner from "@/components/ui/FullPageSpinner"; export default function App() { - const router = useRouter(); - const { isLoggedIn } = useSession(); - useEffect(() => { + const isLoggedIn = useIsLoggedIn(); + if (isLoggedIn === undefined) { // Wait until it's loaded + return <FullPageSpinner />; } else if (isLoggedIn) { - router.replace("dashboard"); + return <Redirect href="dashboard" /> } else { - router.replace("signin"); + return <Redirect href="signin" /> } - }, [isLoggedIn]); - return <View />; } diff --git a/apps/mobile/app/signin.tsx b/apps/mobile/app/signin.tsx index 07ab8e08..ceb4c8d1 100644 --- a/apps/mobile/app/signin.tsx +++ b/apps/mobile/app/signin.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; import { Keyboard, KeyboardAvoidingView, @@ -7,7 +7,7 @@ import { TouchableWithoutFeedback, View, } from "react-native"; -import { useRouter } from "expo-router"; +import { Redirect } from "expo-router"; import Logo from "@/components/Logo"; import { Button } from "@/components/ui/Button"; import { Input } from "@/components/ui/Input"; @@ -15,8 +15,6 @@ import useAppSettings from "@/lib/settings"; import { api } from "@/lib/trpc"; export default function Signin() { - const router = useRouter(); - const { settings, setSettings } = useAppSettings(); const [error, setError] = useState<string | undefined>(); @@ -24,7 +22,6 @@ export default function Signin() { const { mutate: login, isPending } = api.apiKeys.exchange.useMutation({ onSuccess: (resp) => { setSettings({ ...settings, apiKey: resp.key }); - router.replace("dashboard"); }, onError: (e) => { if (e.data?.code === "UNAUTHORIZED") { @@ -43,11 +40,9 @@ export default function Signin() { password: "", }); - useEffect(() => { - if (settings.apiKey) { - router.navigate("dashboard"); - } - }, [settings]); + if (settings.apiKey) { + return <Redirect href="dashboard" />; + } const onSignin = () => { const randStr = (Math.random() + 1).toString(36).substring(5); diff --git a/apps/mobile/lib/providers.tsx b/apps/mobile/lib/providers.tsx index 38eaa99e..688ecd5d 100644 --- a/apps/mobile/lib/providers.tsx +++ b/apps/mobile/lib/providers.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useMemo } from "react"; import FullPageSpinner from "@/components/ui/FullPageSpinner"; import { ToastProvider } from "@/components/ui/Toast"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; @@ -6,16 +6,15 @@ import { httpBatchLink } from "@trpc/client"; import superjson from "superjson"; import type { Settings } from "./settings"; -import useAppSettings, { getAppSettings } from "./settings"; +import useAppSettings from "./settings"; import { api } from "./trpc"; -function getTRPCClient(address: string) { +function getTRPCClient(settings: Settings) { return api.createClient({ links: [ httpBatchLink({ - url: `${address}/api/trpc`, - async headers() { - const settings = await getAppSettings(); + url: `${settings.address}/api/trpc`, + headers() { return { Authorization: settings?.apiKey ? `Bearer ${settings.apiKey}` @@ -35,22 +34,12 @@ function TrpcProvider({ settings: Settings; children: React.ReactNode; }) { - const [queryClient] = useState(() => new QueryClient()); + const queryClient = useMemo(() => new QueryClient(), [settings]); - const [trpcClient, setTrpcClient] = useState< - ReturnType<typeof getTRPCClient> - >(getTRPCClient(settings.address)); - - useEffect(() => { - setTrpcClient(getTRPCClient(settings.address)); - }, [settings.address]); + const trpcClient = useMemo(() => getTRPCClient(settings), [settings]); return ( - <api.Provider - key={settings.address} - client={trpcClient} - queryClient={queryClient} - > + <api.Provider client={trpcClient} queryClient={queryClient}> <QueryClientProvider client={queryClient}> <ToastProvider>{children}</ToastProvider> </QueryClientProvider> @@ -59,7 +48,11 @@ function TrpcProvider({ } export function Providers({ children }: { children: React.ReactNode }) { - const { settings, isLoading } = useAppSettings(); + const { settings, isLoading, load } = useAppSettings(); + + useEffect(() => { + load(); + }, []); if (isLoading) { // Don't render anything if the settings still hasn't been loaded diff --git a/apps/mobile/lib/session.ts b/apps/mobile/lib/session.ts index 071748b9..bafb3a09 100644 --- a/apps/mobile/lib/session.ts +++ b/apps/mobile/lib/session.ts @@ -1,20 +1,21 @@ -import { useCallback, useMemo } from "react"; +import { useCallback } from "react"; import useAppSettings from "./settings"; export function useSession() { - const { settings, isLoading, setSettings } = useAppSettings(); - const isLoggedIn = useMemo(() => { - return isLoading ? undefined : !!settings.apiKey; - }, [isLoading, settings]); + const { settings, setSettings } = useAppSettings(); const logout = useCallback(() => { setSettings({ ...settings, apiKey: undefined }); }, [settings, setSettings]); return { - isLoggedIn, - isLoading, logout, }; } + +export function useIsLoggedIn() { + const { settings, isLoading } = useAppSettings(); + + return isLoading ? undefined : !!settings.apiKey; +} diff --git a/apps/mobile/lib/settings.ts b/apps/mobile/lib/settings.ts index 13de067e..3ec53231 100644 --- a/apps/mobile/lib/settings.ts +++ b/apps/mobile/lib/settings.ts @@ -1,6 +1,5 @@ import * as SecureStore from "expo-secure-store"; - -import { useStorageState } from "./storage-state"; +import { create } from "zustand"; const SETTING_NAME = "settings"; @@ -9,22 +8,40 @@ export interface Settings { address: string; } -export default function useAppSettings() { - const [settingsState, setSettings] = useStorageState<Settings>(SETTING_NAME); - const [isLoading] = settingsState; - let [, settings] = settingsState; +interface AppSettingsState { + settings: { isLoading: boolean; settings: Settings }; + setSettings: (settings: Settings) => Promise<void>; + load: () => Promise<void>; +} - settings ||= { - address: "https://demo.hoarder.app", - }; +const useSettings = create<AppSettingsState>((set, get) => ({ + settings: { + isLoading: true, + settings: { address: "" }, + }, + setSettings: async (settings) => { + await SecureStore.setItemAsync(SETTING_NAME, JSON.stringify(settings)); + set((_state) => ({ settings: { isLoading: false, settings } })); + }, + load: async () => { + if (!get().settings.isLoading) { + return; + } + const strVal = await SecureStore.getItemAsync(SETTING_NAME); + if (!strVal) { + set((state) => ({ + settings: { isLoading: false, settings: state.settings.settings }, + })); + return; + } + // TODO Wipe the state if invalid + const parsed = JSON.parse(strVal) as Settings; + set((_state) => ({ settings: { isLoading: false, settings: parsed } })); + }, +})); - return { settings, setSettings, isLoading }; -} +export default function useAppSettings() { + const { settings, setSettings, load } = useSettings(); -export async function getAppSettings() { - const val = await SecureStore.getItemAsync(SETTING_NAME); - if (!val) { - return null; - } - return JSON.parse(val) as Settings; + return { ...settings, setSettings, load }; } diff --git a/apps/mobile/lib/storage-state.ts b/apps/mobile/lib/storage-state.ts deleted file mode 100644 index f45ddfe5..00000000 --- a/apps/mobile/lib/storage-state.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as React from "react"; -import * as SecureStore from "expo-secure-store"; - -type UseStateHook<T> = [[boolean, T | null], (value: T | null) => void]; - -function useAsyncState<T>( - initialValue: [boolean, T | null] = [true, null], -): UseStateHook<T> { - return React.useReducer( - ( - _state: [boolean, T | null], - action: T | null = null, - ): [boolean, T | null] => [false, action], - initialValue, - ) as UseStateHook<T>; -} - -export async function setStorageItemAsync(key: string, value: string | null) { - if (value == null) { - await SecureStore.deleteItemAsync(key); - } else { - await SecureStore.setItemAsync(key, value); - } -} - -export function useStorageState<T>(key: string): UseStateHook<T> { - // Public - const [state, setState] = useAsyncState<T>(); - - // Get - React.useEffect(() => { - SecureStore.getItemAsync(key).then((value) => { - if (!value) { - setState(null); - return null; - } - setState(JSON.parse(value) as T); - }); - }, [key]); - - // Set - const setValue = React.useCallback( - (value: T | null) => { - setState(value); - setStorageItemAsync(key, JSON.stringify(value)); - }, - [key], - ); - - return [state, setValue]; -} diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 3c0c6206..63c30426 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -41,7 +41,8 @@ "react-native-svg": "^15.1.0", "tailwind-merge": "^2.2.1", "use-debounce": "^10.0.0", - "zod": "^3.22.4" + "zod": "^3.22.4", + "zustand": "^4.5.1" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f25c242e..b1ed0ff4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -291,6 +291,9 @@ importers: zod: specifier: ^3.22.4 version: 3.22.4 + zustand: + specifier: ^4.5.1 + version: 4.5.1(@types/react@18.2.58)(react@18.2.0) devDependencies: '@babel/core': specifier: ^7.20.0 |
