feat: add auth to context

This commit is contained in:
Max Koon
2025-11-15 22:08:58 -05:00
parent 641dc25bee
commit 114eaf88eb
11 changed files with 250 additions and 37 deletions

View File

@@ -1,17 +1,18 @@
import { useLocalSearchParams } from "expo-router";
import { Text } from "react-native";
import { App } from "@money/ui";
import { App, type Route } from "@money/ui";
import { useEffect, useState } from "react";
import { authClient } from "@/lib/auth-client";
export default function Page() {
const { route: initalRoute } = useLocalSearchParams<{ route: string[] }>();
const [route, setRoute] = useState(initalRoute[0]!);
const [route, setRoute] = useState(initalRoute ? "/" + initalRoute.join("/") : "/");
const { data } = authClient.useSession();
// detect back/forward
useEffect(() => {
const handler = () => {
const newRoute = window.location.pathname.slice(1);
// call your apps page change logic
setRoute(newRoute);
};
@@ -21,9 +22,11 @@ export default function Page() {
return (
<App
page={route as any}
onPageChange={(page) => {
window.history.pushState({}, "", "/" + page);
auth={data}
route={route as Route}
setRoute={(page) => {
window.history.pushState({}, "", page);
setRoute(page);
}}
/>
);

View File

@@ -1,8 +1,9 @@
import { RGBA, TextAttributes, createCliRenderer } from "@opentui/core";
import { createRoot } from "@opentui/react";
import { App } from "@money/ui";
import { App, type Route } from "@money/ui";
import { ZeroProvider } from "@rocicorp/zero/react";
import { schema } from '@money/shared';
import { useState } from "react";
const userID = "anon";
const server = "http://laptop:4848";
@@ -11,10 +12,22 @@ const auth = undefined;
function Main() {
return (
<ZeroProvider {...{ userID, auth, server, schema }}>
<App />
<Router />
</ZeroProvider>
);
}
function Router() {
const [route, setRoute] = useState<Route>("/");
return (
<App
auth={null}
route={route}
setRoute={setRoute}
/>
);
}
const renderer = await createCliRenderer();
createRoot(renderer).render(<Main />);

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import type { ViewProps, TextProps } from "react-native";
import type { ViewProps, TextProps, PressableProps } from "react-native";
export function View({ children, style }: ViewProps) {
const bg = style &&
@@ -22,9 +22,42 @@ export function View({ children, style }: ViewProps) {
: undefined;
return <box backgroundColor={bg} flexDirection={flexDirection} flexGrow={flex}>{children}</box>
}
export function Pressable({ children: childrenRaw, style, onPress }: PressableProps) {
const bg = style &&
'backgroundColor' in style
? typeof style.backgroundColor == 'string'
? style.backgroundColor
: undefined
: undefined;
const flexDirection = style &&
'flexDirection' in style
? typeof style.flexDirection == 'string'
? style.flexDirection
: undefined
: undefined;
const flex = style &&
'flex' in style
? typeof style.flex == 'number'
? style.flex
: undefined
: undefined;
const children = childrenRaw instanceof Function ? childrenRaw({ pressed: true }) : childrenRaw;
return <box
backgroundColor={bg}
flexDirection={flexDirection}
flexGrow={flex}
onMouseDown={onPress ? ((_event) => {
// @ts-ignore
onPress();
}) : undefined}
>{children}</box>
}
export function Text({ style, children }: TextProps) {
const fg = style &&
'color' in style

View File

@@ -12,9 +12,9 @@ export const queries = {
.one();
}),
allTransactions: syncedQueryWithContext('allTransactions', z.tuple([]), (authData: AuthData | null) => {
// isLoggedIn(authData);
isLoggedIn(authData);
return builder.transaction
// .where('user_id', '=', authData.user.id)
.where('user_id', '=', authData.user.id)
.orderBy('datetime', 'desc')
.limit(50)
}),
@@ -32,9 +32,9 @@ export const queries = {
.orderBy('name', 'asc');
}),
getItems: syncedQueryWithContext('getItems', z.tuple([]), (authData: AuthData | null) => {
// isLoggedIn(authData);
isLoggedIn(authData);
return builder.plaidAccessTokens
// .where('userId', '=', authData.user.id)
.where('userId', '=', authData.user.id)
.orderBy('createdAt', 'desc');
})
};

View File

@@ -1,31 +1,95 @@
import { useState } from "react";
import { createContext, use, useState } from "react";
import { Transactions } from "./transactions";
import { Text } from "react-native";
import { View, Text } from "react-native";
import { Settings } from "./settings";
import { useKeyboard } from "./useKeyboard";
type Page = "transactions" | "settings";
type AppProps = {
page?: Page;
onPageChange?: (page: Page) => void;
const PAGES = {
'/': {
screen: <Transactions />,
key: "1",
},
'/settings': {
screen: <Settings />,
key: "2",
children: {
"/accounts": {},
"/family": {},
}
},
};
type Join<A extends string, B extends string> =
`${A}${B}` extends `${infer X}` ? X : never;
type ChildRoutes<Parent extends string, Children> =
{
[K in keyof Children & string]:
K extends `/${string}`
? Join<Parent, K>
: never;
}[keyof Children & string];
type Routes<T> = {
[K in keyof T & string]:
| K
| (T[K] extends { children: infer C }
? ChildRoutes<K, C>
: never)
}[keyof T & string];
export type Route = Routes<typeof PAGES>;
type Auth = any;
interface RouterContextType {
auth: Auth;
route: Route;
setRoute: (route: Route) => void;
}
export function App({ page, onPageChange }: AppProps) {
const [curr, setPage] = useState<Page>(page || "transactions");
export const RouterContext = createContext<RouterContextType>({
auth: null,
route: '/',
setRoute: () => {}
});
type AppProps = {
auth: Auth;
route: Route;
setRoute: (page: Route) => void;
}
export function App({ auth, route, setRoute }: AppProps) {
return <RouterContext.Provider value={{ auth, route, setRoute }}>
<Main />
</RouterContext.Provider>
}
function Main() {
const { route, setRoute } = use(RouterContext);
useKeyboard((key) => {
if (key.name == "1") {
setPage("transactions");
if (onPageChange)
onPageChange("transactions");
} else if (key.name == "2") {
setPage("settings");
if (onPageChange)
onPageChange("settings");
}
const screen = Object.entries(PAGES)
.find(([, screen]) => screen.key == key.name);
if (!screen) return;
const [route] = screen as [Route, never];
setRoute(route);
});
return curr == "transactions" ? <Transactions /> : <Settings />;
const match =
route in PAGES
? (route as keyof typeof PAGES)
: (Object.keys(PAGES).sort((a, b) => b.length - a.length).find(p => route.startsWith(p)) as
keyof typeof PAGES);
return PAGES[match].screen;
}

View File

@@ -1,5 +1,68 @@
import { Text } from "react-native";
import { Text, View, Pressable } from "react-native";
import { use, useState, type ReactNode } from "react";
import { RouterContext, type Route } from ".";
import { General } from "./settings/general";
import { Accounts } from "./settings/accounts";
import { Family } from "./settings/family";
import { useKeyboard } from "./useKeyboard";
type SettingsRoute = Extract<Route, `/settings${string}`>;
const TABS = {
"/settings": {
label: "General",
screen: <General />
},
"/settings/accounts": {
label: "Bank Accounts",
screen: <Accounts />
},
"/settings/family": {
label: "Family",
screen: <Family />
},
} as const satisfies Record<SettingsRoute, { label: string, screen: ReactNode }>;
type Tab = keyof typeof TABS;
export function Settings() {
return <Text>Settings</Text>;
const { route, setRoute } = use(RouterContext);
useKeyboard((key) => {
if (key.name == 'h') {
const currentIdx = Object.entries(TABS).findIndex(([tabRoute, _]) => tabRoute == route)
const routes = Object.keys(TABS) as SettingsRoute[];
const last = routes[currentIdx - 1]
if (!last) return;
setRoute(last);
} else if (key.name == 'l') {
const currentIdx = Object.entries(TABS).findIndex(([tabRoute, _]) => tabRoute == route)
const routes = Object.keys(TABS) as SettingsRoute[];
const next = routes[currentIdx + 1]
if (!next) return;
setRoute(next);
}
}, [route]);
return (
<View style={{ flexDirection: "row" }}>
<View>
{Object.entries(TABS).map(([tabRoute, tab]) => {
const isSelected = tabRoute == route;
return (
<Pressable key={tab.label} style={{ backgroundColor: isSelected ? 'black' : undefined }} onPress={() => setRoute(tabRoute as SettingsRoute)}>
<Text style={{ fontFamily: 'mono', color: isSelected ? 'white' : 'black' }}>{tab.label}</Text>
</Pressable>
);
})}
</View>
<View>
{TABS[route as Tab].screen}
</View>
</View>
);
}

View File

@@ -0,0 +1,23 @@
import { useQuery } from "@rocicorp/zero/react";
import { queries } from '@money/shared';
import * as Table from "../table";
import { use } from "react";
import { RouterContext } from "..";
const COLUMNS: Table.Column[] = [
{ name: 'name', label: 'Name' },
{ name: 'createdAt', label: 'Added At', render: (n) => new Date(n).toLocaleString() },
];
export function Accounts() {
const { auth } = use(RouterContext);
const [items] = useQuery(queries.getItems(auth));
return (
<Table.Provider columns={COLUMNS} data={items}>
<Table.Body />
</Table.Provider>
);
}

View File

@@ -0,0 +1,6 @@
import { Text } from "react-native";
export function Family() {
return <Text style={{ fontFamily: 'mono' }}>Welcome to family</Text>
}

View File

@@ -0,0 +1,7 @@
import { Text } from "react-native";
export function General() {
return <Text style={{ fontFamily: 'mono' }}>Welcome to settings</Text>
}

View File

@@ -3,6 +3,7 @@ import { useQuery } from "@rocicorp/zero/react";
import { queries, type Transaction } from '@money/shared';
import { use } from "react";
import { View, Text } from "react-native";
import { RouterContext } from ".";
const FORMAT = new Intl.NumberFormat("en-US", {
@@ -23,7 +24,8 @@ const COLUMNS: Table.Column[] = [
export function Transactions() {
const [items] = useQuery(queries.allTransactions(null));
const { auth } = use(RouterContext);
const [items] = useQuery(queries.allTransactions(auth));
return (
<Table.Provider

View File

@@ -37,7 +37,6 @@ export function useKeyboard(handler: (key: KeyEvent) => void, deps: any[] = [])
// @ts-ignore
window.addEventListener("keydown", handlerWeb);
return () => {
console.log("REMOVING");
// @ts-ignore
window.removeEventListener("keydown", handlerWeb);
};