aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-03-22 15:10:24 +0000
committerMohamedBassem <me@mbassem.com>2024-03-22 15:14:24 +0000
commit2cd2f92e9e0c82eaa5f21fe0c30e20ebea7aba24 (patch)
treeacf86f033b976a40079c3efe8ec1fb727ae2a452 /apps
parent95cc9e6ff29cd39dc80aa09c80a6d1c9489b5d6a (diff)
downloadkarakeep-2cd2f92e9e0c82eaa5f21fe0c30e20ebea7aba24.tar.zst
fix(mobile): Fix setting propagatin
Diffstat (limited to 'apps')
-rw-r--r--apps/mobile/app.json2
-rw-r--r--apps/mobile/app/dashboard/(tabs)/settings.tsx22
-rw-r--r--apps/mobile/app/dashboard/_layout.tsx12
-rw-r--r--apps/mobile/app/index.tsx19
-rw-r--r--apps/mobile/app/signin.tsx15
-rw-r--r--apps/mobile/lib/providers.tsx33
-rw-r--r--apps/mobile/lib/session.ts15
-rw-r--r--apps/mobile/lib/settings.ts51
-rw-r--r--apps/mobile/lib/storage-state.ts51
-rw-r--r--apps/mobile/package.json3
10 files changed, 88 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",