From 17c9885ccce4fcd800cda8f51a2d357f729c0f83 Mon Sep 17 00:00:00 2001 From: john Date: Mon, 19 May 2025 09:23:15 +0200 Subject: [PATCH 01/79] refresh user --- scripts/generate-schema.mjs | 4 +- src/App.tsx | 31 ++++++++------- src/app/api/schema.ts | 52 +++++++++++++++++++++++-- src/app/auth/authService.ts | 31 +++++++++++++++ src/app/auth/components/RefreshUser.tsx | 27 +++++++++++++ src/app/messageBus/messageTypes.ts | 2 + 6 files changed, 127 insertions(+), 20 deletions(-) create mode 100644 src/app/auth/components/RefreshUser.tsx diff --git a/scripts/generate-schema.mjs b/scripts/generate-schema.mjs index 4381bbf..edc8972 100644 --- a/scripts/generate-schema.mjs +++ b/scripts/generate-schema.mjs @@ -14,9 +14,7 @@ export async function generateApiSchema(openapiUrl, outputFilePath, pathToPretti const request = new Request(openapiUrl) const response = await fetch(request) const json = await response.text() - const ast = await openapiTS(json, { - pathParamsAsTypes: true, - }) + const ast = await openapiTS(json, {}) const prettierConfig = await resolveConfig(pathToPrettierRc, { useCache: true, }) diff --git a/src/App.tsx b/src/App.tsx index 74cc867..6f04dca 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import LogoutPage from './app/auth/pages/LogoutPage.tsx' import UnauthorizedHandler from './app/auth/components/UnauthorizedHandler.tsx' import AdminPage from './app/admin/pages/AdminPage.tsx' import SignupCodesManagementPage from './app/admin/pages/subpages/SignupCodesManagementPage.tsx' +import RefreshUser from './app/auth/components/RefreshUser.tsx' function App() { const postService = new PostsService() @@ -19,22 +20,24 @@ function App() { return ( - - } - /> - } /> - } /> - } /> - } /> - }> + + } + path={'/'} + element={} /> - - + } /> + } /> + } /> + } /> + }> + } + /> + + + ) diff --git a/src/app/api/schema.ts b/src/app/api/schema.ts index 32d4ad8..f476eb0 100644 --- a/src/app/api/schema.ts +++ b/src/app/api/schema.ts @@ -87,8 +87,7 @@ export interface paths { requestBody: { content: { 'multipart/form-data': { - /** Format: binary */ - file?: string + file?: components['schemas']['IFormFile'] } } } @@ -112,7 +111,7 @@ export interface paths { patch?: never trace?: never } - [path: `/media/${string}`]: { + '/media/{id}': { parameters: { query?: never header?: never @@ -266,6 +265,45 @@ export interface paths { patch?: never trace?: never } + '/auth/user/{userId}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get: { + parameters: { + query?: never + header?: never + path: { + userId: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'text/plain': components['schemas']['RefreshUserResult'] + 'application/json': components['schemas']['RefreshUserResult'] + 'text/json': components['schemas']['RefreshUserResult'] + } + } + } + } + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } '/auth/signup-codes': { parameters: { query?: never @@ -362,6 +400,8 @@ export interface components { /** Format: uuid */ next: string | null } + /** Format: binary */ + IFormFile: string ListSignupCodesResult: { signupCodes: components['schemas']['SignupCodeDto'][] } @@ -397,6 +437,12 @@ export interface components { /** Format: int32 */ height: number | null } + RefreshUserResult: { + /** Format: uuid */ + userId: string + username: string + isSuperUser: boolean + } RegisterRequest: { username: string password: string diff --git a/src/app/auth/authService.ts b/src/app/auth/authService.ts index a5733d6..293c242 100644 --- a/src/app/auth/authService.ts +++ b/src/app/auth/authService.ts @@ -63,4 +63,35 @@ export class AuthService { return res.data.signupCodes.map(SignupCode.fromDto) } + + async refreshUser(userId: string) { + if (this.getCookie('hasSession') !== 'true') { + return + } + + const res = await client.GET(`/auth/user/{userId}`, { + params: { + path: { userId }, + }, + credentials: 'include', + }) + + if (!res.data) { + dispatchMessage('auth:user-refresh-failed', null) + } else { + dispatchMessage('auth:user-refreshed', { ...res.data }) + } + } + + private getCookie(cookieName: string): string | undefined { + const cookie = document.cookie + .split('; ') + .map((c) => { + const [name, value] = c.split('=') + return { name, value } + }) + .find((c) => c.name === cookieName) + + return cookie?.value + } } diff --git a/src/app/auth/components/RefreshUser.tsx b/src/app/auth/components/RefreshUser.tsx new file mode 100644 index 0000000..34dd268 --- /dev/null +++ b/src/app/auth/components/RefreshUser.tsx @@ -0,0 +1,27 @@ +import { PropsWithChildren, useEffect, useRef } from 'react' +import { AuthService } from '../authService.ts' +import { useUser } from '../../user/userStore.ts' + +interface RefreshUserProps { + authService: AuthService +} + +export default function RefreshUser({ + authService, + children, +}: PropsWithChildren) { + const { user } = useUser() + const didRefresh = useRef(false) + + useEffect(() => { + const timeoutId = setTimeout(async () => { + if (didRefresh.current) return + if (user == null) return + didRefresh.current = true + await authService.refreshUser(user.userId) + }) + return () => clearTimeout(timeoutId) + }, [authService, user]) + + return <>{children} +} diff --git a/src/app/messageBus/messageTypes.ts b/src/app/messageBus/messageTypes.ts index 8d15f6c..080964e 100644 --- a/src/app/messageBus/messageTypes.ts +++ b/src/app/messageBus/messageTypes.ts @@ -5,4 +5,6 @@ export interface MessageTypes { 'auth:registered': User 'auth:logged-out': null 'auth:unauthorized': null + 'auth:user-refreshed': User + 'auth:user-refresh-failed': null } From 5f47162a5068e9629ef1fbc4a75bdbce8de1c5e8 Mon Sep 17 00:00:00 2001 From: john Date: Mon, 19 May 2025 17:52:18 +0200 Subject: [PATCH 02/79] fix user refresher --- src/App.tsx | 65 +++++++++++++++++++++---------- src/app/auth/authService.ts | 11 ++---- src/app/auth/pages/SignupPage.tsx | 4 +- src/app/user/userStore.ts | 4 +- src/hooks/useOnMounted.ts | 17 ++++++++ src/utils/store.ts | 20 ++++++++-- 6 files changed, 85 insertions(+), 36 deletions(-) create mode 100644 src/hooks/useOnMounted.ts diff --git a/src/App.tsx b/src/App.tsx index 6f04dca..f7a78c6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,37 +10,60 @@ import LogoutPage from './app/auth/pages/LogoutPage.tsx' import UnauthorizedHandler from './app/auth/components/UnauthorizedHandler.tsx' import AdminPage from './app/admin/pages/AdminPage.tsx' import SignupCodesManagementPage from './app/admin/pages/subpages/SignupCodesManagementPage.tsx' -import RefreshUser from './app/auth/components/RefreshUser.tsx' +import { useUser } from './app/user/userStore.ts' +import { useEffect, useMemo } from 'react' -function App() { +export default function App() { const postService = new PostsService() const mediaService = new MediaService() - const authService = new AuthService() + const authService = useMemo(() => new AuthService(), []) + + const { user, setUser } = useUser() + + const userId = user?.userId ?? null + + useEffect(() => { + if (userId == null) { + return + } + + const timeouts: number[] = [] + + timeouts.push( + setTimeout(async function refreshUser() { + const userInfo = await authService.refreshUser(userId) + + setUser(userInfo) + + timeouts.push(setTimeout(refreshUser, 60_000)) + }), + ) + + return () => { + timeouts.forEach(clearTimeout) + } + }, [authService, setUser, userId]) return ( - - + + } + /> + } /> + } /> + } /> + } /> + }> } + path={'codes'} + element={} /> - } /> - } /> - } /> - } /> - }> - } - /> - - - + + ) } - -export default App diff --git a/src/app/auth/authService.ts b/src/app/auth/authService.ts index 293c242..bdd721c 100644 --- a/src/app/auth/authService.ts +++ b/src/app/auth/authService.ts @@ -2,6 +2,7 @@ import { dispatchMessage } from '../messageBus/messageBus.ts' import client from '../api/client.ts' import { ProblemDetails } from '../../types' import { SignupCode } from './signupCode.ts' +import { User } from '../user/userStore.ts' export class AuthService { constructor() {} @@ -64,9 +65,9 @@ export class AuthService { return res.data.signupCodes.map(SignupCode.fromDto) } - async refreshUser(userId: string) { + async refreshUser(userId: string): Promise { if (this.getCookie('hasSession') !== 'true') { - return + return null } const res = await client.GET(`/auth/user/{userId}`, { @@ -76,11 +77,7 @@ export class AuthService { credentials: 'include', }) - if (!res.data) { - dispatchMessage('auth:user-refresh-failed', null) - } else { - dispatchMessage('auth:user-refreshed', { ...res.data }) - } + return res.data ?? null } private getCookie(cookieName: string): string | undefined { diff --git a/src/app/auth/pages/SignupPage.tsx b/src/app/auth/pages/SignupPage.tsx index ba3b994..19b2ab4 100644 --- a/src/app/auth/pages/SignupPage.tsx +++ b/src/app/auth/pages/SignupPage.tsx @@ -52,9 +52,7 @@ export default function SignupPage({ authService }: SignupPageProps) { } }, [code, signupCode]) - useEffect(() => { - console.debug('signup code', signupCode) - }, [signupCode]) + useEffect(() => {}, [signupCode]) const onSubmit = async (e: FormEvent) => { e.preventDefault() diff --git a/src/app/user/userStore.ts b/src/app/user/userStore.ts index 49107fc..f52d391 100644 --- a/src/app/user/userStore.ts +++ b/src/app/user/userStore.ts @@ -23,9 +23,9 @@ addMessageListener('auth:registered', setUser) addMessageListener('auth:logged-out', setUser) export const useUser = () => { - const [user] = useStore(userStore) + const [user, setUser] = useStore(userStore) - return { user } + return { user, setUser } } function loadStoredUser(): User | null { diff --git a/src/hooks/useOnMounted.ts b/src/hooks/useOnMounted.ts new file mode 100644 index 0000000..2053977 --- /dev/null +++ b/src/hooks/useOnMounted.ts @@ -0,0 +1,17 @@ +import { useEffect, useRef } from 'react' + +export function useOnMounted(callback: () => void | Promise) { + const isMounted = useRef(false) + + useEffect(() => { + if (isMounted.current) return + isMounted.current = true + + const timeoutId = setTimeout(callback) + + return () => { + isMounted.current = false + clearTimeout(timeoutId) + } + }, [callback]) +} diff --git a/src/utils/store.ts b/src/utils/store.ts index 5db61dc..ae99ef7 100644 --- a/src/utils/store.ts +++ b/src/utils/store.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' export interface Store { getState: () => T @@ -37,7 +37,21 @@ export function createStore(initialState: T): Store export function useStore(store: Store) { const [selectedState, setSelectedState] = useState(() => store.getState()) - useEffect(() => store.subscribe((newState) => setSelectedState(newState)), [store]) + useEffect(() => { + const unsubscribe = store.subscribe((newState) => setSelectedState(newState)) - return [selectedState, setSelectedState] as const + return () => { + unsubscribe() + } + }, [store]) + + const setState = useCallback( + (nextState: T | ((prevState: T) => T)) => { + setSelectedState(nextState) + store.setState(nextState) + }, + [store], + ) + + return [selectedState, setState] as const } From 700eaf3eb20a500f73f5bc426a7b15828b2341dd Mon Sep 17 00:00:00 2001 From: john Date: Tue, 20 May 2025 10:06:18 +0200 Subject: [PATCH 03/79] use user from session --- src/App.tsx | 40 ++++-------------- src/app/auth/authService.ts | 26 ++++-------- src/app/auth/components/AuthNavButtons.tsx | 4 +- src/app/auth/components/Protected.tsx | 4 +- src/app/auth/components/RefreshUser.tsx | 4 +- src/app/auth/cookies.ts | 16 ++++++++ src/app/auth/pages/LoginPage.tsx | 4 +- src/app/auth/pages/LogoutPage.tsx | 5 ++- src/app/feed/pages/HomePage.tsx | 4 +- src/app/femtoApp.ts | 8 ++++ src/app/messageBus/messageTypes.ts | 10 ++--- src/app/user/user.ts | 48 ++++++++++++++++++++++ src/app/user/userStore.ts | 38 ----------------- src/components/NavBar.tsx | 4 +- src/types.d.ts | 12 ++++++ src/useRefreshSessionLoop.ts | 28 +++++++++++++ 16 files changed, 148 insertions(+), 107 deletions(-) create mode 100644 src/app/auth/cookies.ts create mode 100644 src/app/femtoApp.ts create mode 100644 src/app/user/user.ts delete mode 100644 src/app/user/userStore.ts create mode 100644 src/useRefreshSessionLoop.ts diff --git a/src/App.tsx b/src/App.tsx index f7a78c6..3e0a263 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,39 +10,17 @@ import LogoutPage from './app/auth/pages/LogoutPage.tsx' import UnauthorizedHandler from './app/auth/components/UnauthorizedHandler.tsx' import AdminPage from './app/admin/pages/AdminPage.tsx' import SignupCodesManagementPage from './app/admin/pages/subpages/SignupCodesManagementPage.tsx' -import { useUser } from './app/user/userStore.ts' -import { useEffect, useMemo } from 'react' +import { initUser } from './app/user/user.ts' +import { useRefreshSessionLoop } from './useRefreshSessionLoop.ts' + +const postService = new PostsService() +const mediaService = new MediaService() +const authService = new AuthService() + +initUser() export default function App() { - const postService = new PostsService() - const mediaService = new MediaService() - const authService = useMemo(() => new AuthService(), []) - - const { user, setUser } = useUser() - - const userId = user?.userId ?? null - - useEffect(() => { - if (userId == null) { - return - } - - const timeouts: number[] = [] - - timeouts.push( - setTimeout(async function refreshUser() { - const userInfo = await authService.refreshUser(userId) - - setUser(userInfo) - - timeouts.push(setTimeout(refreshUser, 60_000)) - }), - ) - - return () => { - timeouts.forEach(clearTimeout) - } - }, [authService, setUser, userId]) + useRefreshSessionLoop(authService) return ( diff --git a/src/app/auth/authService.ts b/src/app/auth/authService.ts index bdd721c..3557c52 100644 --- a/src/app/auth/authService.ts +++ b/src/app/auth/authService.ts @@ -2,7 +2,7 @@ import { dispatchMessage } from '../messageBus/messageBus.ts' import client from '../api/client.ts' import { ProblemDetails } from '../../types' import { SignupCode } from './signupCode.ts' -import { User } from '../user/userStore.ts' +import { getCookie } from './cookies.ts' export class AuthService { constructor() {} @@ -17,7 +17,7 @@ export class AuthService { throw new Error('invalid credentials') } - dispatchMessage('auth:logged-in', { ...res.data }) + dispatchMessage('auth:logged-in', null) } async signup(username: string, password: string, signupCode: string) { @@ -31,7 +31,7 @@ export class AuthService { throw new Error((res.error as ProblemDetails)?.detail ?? 'invalid credentials') } - dispatchMessage('auth:registered', { ...res.data }) + dispatchMessage('auth:registered', null) } async logout() { @@ -65,30 +65,18 @@ export class AuthService { return res.data.signupCodes.map(SignupCode.fromDto) } - async refreshUser(userId: string): Promise { - if (this.getCookie('hasSession') !== 'true') { + async refreshUser(userId: string) { + if (getCookie('hasSession') !== 'true') { return null } - const res = await client.GET(`/auth/user/{userId}`, { + await client.GET(`/auth/user/{userId}`, { params: { path: { userId }, }, credentials: 'include', }) - return res.data ?? null - } - - private getCookie(cookieName: string): string | undefined { - const cookie = document.cookie - .split('; ') - .map((c) => { - const [name, value] = c.split('=') - return { name, value } - }) - .find((c) => c.name === cookieName) - - return cookie?.value + dispatchMessage('auth:refreshed', null) } } diff --git a/src/app/auth/components/AuthNavButtons.tsx b/src/app/auth/components/AuthNavButtons.tsx index 33769c1..6e58d01 100644 --- a/src/app/auth/components/AuthNavButtons.tsx +++ b/src/app/auth/components/AuthNavButtons.tsx @@ -1,9 +1,9 @@ -import { useUser } from '../../user/userStore.ts' +import { useUser } from '../../user/user.ts' import NavButton from '../../../components/buttons/NavButton.tsx' import { useLocation } from 'react-router-dom' export default function AuthNavButtons() { - const { user } = useUser() + const user = useUser() const { pathname } = useLocation() diff --git a/src/app/auth/components/Protected.tsx b/src/app/auth/components/Protected.tsx index ad72109..f5ea238 100644 --- a/src/app/auth/components/Protected.tsx +++ b/src/app/auth/components/Protected.tsx @@ -1,9 +1,9 @@ -import { useUser } from '../../user/userStore.ts' +import { useUser } from '../../user/user.ts' import { useNavigate, Outlet } from 'react-router-dom' import { useEffect } from 'react' export default function Protected() { - const { user } = useUser() + const user = useUser() const navigate = useNavigate() diff --git a/src/app/auth/components/RefreshUser.tsx b/src/app/auth/components/RefreshUser.tsx index 34dd268..b49bb1e 100644 --- a/src/app/auth/components/RefreshUser.tsx +++ b/src/app/auth/components/RefreshUser.tsx @@ -1,6 +1,6 @@ import { PropsWithChildren, useEffect, useRef } from 'react' import { AuthService } from '../authService.ts' -import { useUser } from '../../user/userStore.ts' +import { useUser } from '../../user/user.ts' interface RefreshUserProps { authService: AuthService @@ -10,7 +10,7 @@ export default function RefreshUser({ authService, children, }: PropsWithChildren) { - const { user } = useUser() + const user = useUser() const didRefresh = useRef(false) useEffect(() => { diff --git a/src/app/auth/cookies.ts b/src/app/auth/cookies.ts new file mode 100644 index 0000000..ba861c7 --- /dev/null +++ b/src/app/auth/cookies.ts @@ -0,0 +1,16 @@ +export function getCookie(cookieName: string): string | undefined { + return getCookies().get(cookieName) +} + +export function getCookies(): Map { + return document.cookie + .split('; ') + .map((c) => { + const [name, value] = c.split('=') as [string, string] + return { name, value } + }) + .reduce((acc, c) => { + acc.set(c.name, c.value) + return acc + }, new Map()) +} diff --git a/src/app/auth/pages/LoginPage.tsx b/src/app/auth/pages/LoginPage.tsx index 954a2cf..5163d71 100644 --- a/src/app/auth/pages/LoginPage.tsx +++ b/src/app/auth/pages/LoginPage.tsx @@ -4,7 +4,7 @@ import TextInput from '../../../components/inputs/TextInput.tsx' import Button from '../../../components/buttons/Button.tsx' import { AuthService } from '../authService.ts' import { useNavigate } from 'react-router-dom' -import { useUser } from '../../user/userStore.ts' +import { useUser } from '../../user/user.ts' import NavBar from '../../../components/NavBar.tsx' import NavButton from '../../../components/buttons/NavButton.tsx' import LinkButton from '../../../components/buttons/LinkButton.tsx' @@ -22,7 +22,7 @@ export default function LoginPage({ authService }: LoginPageProps) { const passwordInputRef = useRef(null) const navigate = useNavigate() - const { user } = useUser() + const user = useUser() useEffect(() => { if (user) { diff --git a/src/app/auth/pages/LogoutPage.tsx b/src/app/auth/pages/LogoutPage.tsx index 8ae4ef4..b0e20b6 100644 --- a/src/app/auth/pages/LogoutPage.tsx +++ b/src/app/auth/pages/LogoutPage.tsx @@ -1,7 +1,7 @@ import { useNavigate } from 'react-router-dom' import { AuthService } from '../authService.ts' import { useEffect } from 'react' -import { useUser } from '../../user/userStore.ts' +import { useUser } from '../../user/user.ts' interface LogoutPageProps { authService: AuthService @@ -9,9 +9,10 @@ interface LogoutPageProps { export default function LogoutPage({ authService }: LogoutPageProps) { const navigate = useNavigate() - const { user } = useUser() + const user = useUser() useEffect(() => { + console.debug(user) if (!user) { navigate('/login') } diff --git a/src/app/feed/pages/HomePage.tsx b/src/app/feed/pages/HomePage.tsx index 5ba5d27..7abd10e 100644 --- a/src/app/feed/pages/HomePage.tsx +++ b/src/app/feed/pages/HomePage.tsx @@ -1,7 +1,7 @@ import { useCallback, useState } from 'react' import FeedView from '../components/FeedView.tsx' import { PostsService } from '../posts/postsService.ts' -import { useUser } from '../../user/userStore.ts' +import { useUser } from '../../user/user.ts' import { MediaService } from '../../media/mediaService.ts' import NewPostWidget from '../../../components/NewPostWidget.tsx' import { useFeedViewModel } from '../components/FeedView.ts' @@ -18,7 +18,7 @@ interface HomePageProps { } export default function HomePage({ postsService, mediaService }: HomePageProps) { - const { user } = useUser() + const user = useUser() useSaveSignupCodeToLocalStorage() const [isSubmitting, setIsSubmitting] = useState(false) diff --git a/src/app/femtoApp.ts b/src/app/femtoApp.ts new file mode 100644 index 0000000..cc9593a --- /dev/null +++ b/src/app/femtoApp.ts @@ -0,0 +1,8 @@ +import { FemtoApp } from '../types' +import { produce } from 'immer' + +export function setGlobal(k: K, v: FemtoApp[K]) { + window.$femto = produce(window.$femto ?? {}, (draft) => { + draft[k] = v + }) +} diff --git a/src/app/messageBus/messageTypes.ts b/src/app/messageBus/messageTypes.ts index 080964e..2338618 100644 --- a/src/app/messageBus/messageTypes.ts +++ b/src/app/messageBus/messageTypes.ts @@ -1,10 +1,10 @@ -import { User } from '../user/userStore.ts' +import { User } from '../user/user.ts' export interface MessageTypes { - 'auth:logged-in': User - 'auth:registered': User + 'auth:logged-in': null + 'auth:registered': null 'auth:logged-out': null 'auth:unauthorized': null - 'auth:user-refreshed': User - 'auth:user-refresh-failed': null + 'auth:refreshed': null + 'user:updated': User | null } diff --git a/src/app/user/user.ts b/src/app/user/user.ts new file mode 100644 index 0000000..a962607 --- /dev/null +++ b/src/app/user/user.ts @@ -0,0 +1,48 @@ +import { addMessageListener, dispatchMessage } from '../messageBus/messageBus.ts' +import { getCookie } from '../auth/cookies.ts' +import { useMessageListener } from '../../hooks/useMessageListener.ts' +import { useState } from 'react' +import { setGlobal } from '../femtoApp.ts' + +export interface User { + userId: string + username: string + isSuperUser: boolean +} + +let globalUser: User | null + +export function initUser() { + updateUser() +} + +function updateUser() { + globalUser = getUserFromCookie() + console.debug(globalUser) + setGlobal('user', globalUser) + dispatchMessage('user:updated', globalUser) +} + +addMessageListener('auth:logged-in', updateUser) +addMessageListener('auth:registered', updateUser) +addMessageListener('auth:logged-out', updateUser) +addMessageListener('auth:refreshed', updateUser) + +export function useUser(): User | null { + const [user, setUser] = useState(globalUser) + + useMessageListener('user:updated', (u) => { + setUser(u) + }) + + return user +} + +function getUserFromCookie(): User | null { + const userCookie = getCookie('user') + + if (!userCookie) return null + + // TODO validate but it should be fine + return JSON.parse(decodeURIComponent(userCookie)) as User +} diff --git a/src/app/user/userStore.ts b/src/app/user/userStore.ts deleted file mode 100644 index f52d391..0000000 --- a/src/app/user/userStore.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { createStore, Store, useStore } from '../../utils/store.ts' -import { addMessageListener } from '../messageBus/messageBus.ts' - -export interface User { - userId: string - username: string - isSuperUser: boolean -} - -export type UserStore = Store - -const UserKey = 'user' - -export const userStore = createStore(loadStoredUser()) - -userStore.subscribe((user) => { - localStorage.setItem(UserKey, JSON.stringify(user)) -}) - -const setUser = (u: User | null) => userStore.setState(u) -addMessageListener('auth:logged-in', setUser) -addMessageListener('auth:registered', setUser) -addMessageListener('auth:logged-out', setUser) - -export const useUser = () => { - const [user, setUser] = useStore(userStore) - - return { user, setUser } -} - -function loadStoredUser(): User | null { - const json = localStorage.getItem(UserKey) - if (json) { - return JSON.parse(json) as User - } else { - return null - } -} diff --git a/src/components/NavBar.tsx b/src/components/NavBar.tsx index 5dc99dd..8127122 100644 --- a/src/components/NavBar.tsx +++ b/src/components/NavBar.tsx @@ -1,11 +1,11 @@ import { PropsWithChildren } from 'react' -import { useUser } from '../app/user/userStore.ts' +import { useUser } from '../app/user/user.ts' import NavButton from './buttons/NavButton.tsx' type NavBarProps = unknown export default function NavBar({ children }: PropsWithChildren) { - const { user } = useUser() + const user = useUser() return (