Skip to content

Commit

Permalink
fix(mobile): Fix setting propagatin
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamedBassem committed Mar 22, 2024
1 parent 95cc9e6 commit 2cd2f92
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 135 deletions.
2 changes: 1 addition & 1 deletion apps/mobile/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
22 changes: 5 additions & 17 deletions apps/mobile/app/dashboard/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
@@ -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>
Expand Down
12 changes: 12 additions & 0 deletions apps/mobile/app/dashboard/_layout.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down
19 changes: 8 additions & 11 deletions apps/mobile/app/index.tsx
Original file line number Diff line number Diff line change
@@ -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 />;
}
15 changes: 5 additions & 10 deletions apps/mobile/app/signin.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useState } from "react";
import {
Keyboard,
KeyboardAvoidingView,
Expand All @@ -7,24 +7,21 @@ 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";
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>();

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") {
Expand All @@ -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);
Expand Down
33 changes: 13 additions & 20 deletions apps/mobile/lib/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
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";
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}`
Expand All @@ -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>
Expand All @@ -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
Expand Down
15 changes: 8 additions & 7 deletions apps/mobile/lib/session.ts
Original file line number Diff line number Diff line change
@@ -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;
}
51 changes: 34 additions & 17 deletions apps/mobile/lib/settings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as SecureStore from "expo-secure-store";

import { useStorageState } from "./storage-state";
import { create } from "zustand";

const SETTING_NAME = "settings";

Expand All @@ -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 };
}
51 changes: 0 additions & 51 deletions apps/mobile/lib/storage-state.ts

This file was deleted.

3 changes: 2 additions & 1 deletion apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2cd2f92

Please sign in to comment.