diff --git a/src/api/commands/database.ts b/src/api/commands/database.ts index ff8f640..97bf791 100644 --- a/src/api/commands/database.ts +++ b/src/api/commands/database.ts @@ -54,7 +54,7 @@ export const deleteKv = declareCommand('database_delete // Account export type FindAccountsByBusinessArgs = Pick -export const findAccountsByBusiness = declareCommand('database_find_accounts_by_business') +export const findAccountsByBusiness = declareCommand('database_find_accounts_by_business') export type FindAccountByBusinessAndUidArgs = Pick export const findAccountByBusinessAndUid = declareCommand('database_find_account_by_business_and_uid') diff --git a/src/api/queries/account.ts b/src/api/queries/account.ts new file mode 100644 index 0000000..01832d0 --- /dev/null +++ b/src/api/queries/account.ts @@ -0,0 +1,60 @@ +import { queryOptions, useMutation } from '@tanstack/react-query' +import { produce } from 'immer' +import { CreateAccountArgs, SqlxDatabaseError, SqlxError, createAccount, findAccountsByBusiness } from '@/api/commands/database' +import { Account } from '@/interfaces/Account' +import { Businesses, KeyofBusinesses, ReversedBusinesses } from '@/interfaces/Business' +import { OmitParametersFirst } from '@/interfaces/Declare' +import queryClient from '@/queryClient' + +type AccountsKey = [KeyofBusinesses, 'Accounts'] +function accountsKey (keyofBusinesses: KeyofBusinesses): AccountsKey { + return [keyofBusinesses, 'Accounts'] +} + +function setAccountsData ( + keyofBusinesses: KeyofBusinesses, + ...rest: OmitParametersFirst> +) { + return queryClient.setQueryData( + accountsKey(keyofBusinesses), + ...rest + ) +} + +export function accountsQueryOptions (keyofBusinesses: KeyofBusinesses) { + return queryOptions< + Account[], + SqlxError | SqlxDatabaseError | Error, + Account[], + AccountsKey + >({ + staleTime: Infinity, + queryKey: accountsKey(keyofBusinesses), + queryFn: () => findAccountsByBusiness({ + business: Businesses[keyofBusinesses] + }) + }) +} + +const CreateAccountKey = ['Accounts', 'Create'] +export function useCreateAccountMutation () { + return useMutation< + Account, + SqlxError | SqlxDatabaseError | Error, + CreateAccountArgs + >({ + mutationKey: CreateAccountKey, + mutationFn: createAccount, + onSuccess (data) { + setAccountsData( + ReversedBusinesses[data.business], + (prev) => { + return produce(prev, (draft) => { + draft ||= [] + draft.push(data) + }) + } + ) + } + }) +} diff --git a/src/components/BusinessContext.tsx b/src/components/BusinessContext.tsx new file mode 100644 index 0000000..35642ca --- /dev/null +++ b/src/components/BusinessContext.tsx @@ -0,0 +1,22 @@ +import React, { PropsWithChildren, useMemo } from 'react' +import BusinessContext, { BusinessState } from '@/contexts/BusinessContext' +import { Business, KeyofBusinesses } from '@/interfaces/Business' + +interface Props { + business: Business + keyofBusinesses: KeyofBusinesses +} + +export default function BusinessProvider (props: PropsWithChildren) { + const { business, keyofBusinesses, children } = props + const state = useMemo(() => ({ + business, + keyofBusinesses + }), [business, keyofBusinesses]) + + return ( + + {children} + + ) +} diff --git a/src/contexts/BusinessContext.tsx b/src/contexts/BusinessContext.tsx new file mode 100644 index 0000000..ebb6554 --- /dev/null +++ b/src/contexts/BusinessContext.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { Business, KeyofBusinesses } from '@/interfaces/Business' + +export interface BusinessState { + readonly business: Business + readonly keyofBusinesses: KeyofBusinesses +} + +const BusinessContext = React.createContext(null) + +BusinessContext.displayName = 'BusinessContext' + +export default BusinessContext diff --git a/src/interfaces/Declare.ts b/src/interfaces/Declare.ts new file mode 100644 index 0000000..e21149b --- /dev/null +++ b/src/interfaces/Declare.ts @@ -0,0 +1,11 @@ +export type OmitParametersFirst unknown> = + T extends (...args: infer P) => unknown + ? P extends [unknown, ...infer R] + ? R + : P + : never + +export type PickParametersFirst unknown> = + T extends (...args: infer P) => unknown + ? P[0] + : never diff --git a/src/pages/Gacha/PageView.tsx b/src/pages/Gacha/PageView.tsx new file mode 100644 index 0000000..6d7c9dd --- /dev/null +++ b/src/pages/Gacha/PageView.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import { makeStyles } from '@fluentui/react-components' + +const useStyles = makeStyles({ + root: { + } +}) + +export default function GachaPageView () { + const classes = useStyles() + return ( +
+ PageView +
+ ) +} diff --git a/src/pages/Gacha/index.tsx b/src/pages/Gacha/index.tsx new file mode 100644 index 0000000..babe37e --- /dev/null +++ b/src/pages/Gacha/index.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import BusinessProvider from '@/components/BusinessContext' +import GachaPageView from './PageView' +import gachaRoute from './route' + +export default function Gacha () { + const { business, keyofBusinesses } = gachaRoute.useLoaderData() + return ( + + + + ) +} diff --git a/src/pages/Gacha/route.ts b/src/pages/Gacha/route.ts new file mode 100644 index 0000000..5721c8d --- /dev/null +++ b/src/pages/Gacha/route.ts @@ -0,0 +1,46 @@ +import { createRoute } from '@tanstack/react-router' +import { accountsQueryOptions } from '@/api/queries/account' +import { Business, Businesses, KeyofBusinesses } from '@/interfaces/Business' +import rootRoute from '@/pages/Root/route' +import queryClient from '@/queryClient' +import Gacha from '.' + +const gachaRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/gacha/$business', + async loader (ctx) { + const keyofBusinesses: KeyofBusinesses = ctx.params.business as KeyofBusinesses + const business: Business | undefined = Businesses[keyofBusinesses] + + // You can't use `!business` because 0 is also a valid value. + if (business === null || typeof business === 'undefined') { + throw new Error(`Invalid path parameter business: ${keyofBusinesses}`) + } + + // TODO: + // 1. Load accounts + // 2. Get current activated account (CAC) + // 3. Preload CAC gacha records... + await queryClient.ensureQueryData(accountsQueryOptions(keyofBusinesses)) + + return { + business, + keyofBusinesses + } + }, + component: Gacha, + pendingComponent: function Pending () { + // TODO + return ( + 'loading...' + ) + }, + errorComponent: ({ error }) => { + // TODO + return ( + 'Error: ' + (error instanceof Error ? error.message : String(error)) + ) + } +}) + +export default gachaRoute diff --git a/src/pages/Home/route.ts b/src/pages/Home/route.ts index 7be4314..0059f26 100644 --- a/src/pages/Home/route.ts +++ b/src/pages/Home/route.ts @@ -3,8 +3,8 @@ import rootRoute from '@/pages/Root/route' import Home from '.' const homeRoute = createRoute({ - path: '/', getParentRoute: () => rootRoute, + path: '/', component: Home }) diff --git a/src/pages/Settings/route.ts b/src/pages/Settings/route.ts index 8fc9ed3..c475a64 100644 --- a/src/pages/Settings/route.ts +++ b/src/pages/Settings/route.ts @@ -3,8 +3,8 @@ import rootRoute from '@/pages/Root/route' import Settings from '.' const settingsRoute = createRoute({ - path: '/settings', getParentRoute: () => rootRoute, + path: '/settings', component: Settings }) diff --git a/src/router.tsx b/src/router.tsx index 4c268bb..505efc5 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -1,9 +1,11 @@ import { createRouter } from '@tanstack/react-router' +import gachaRoute from '@/pages/Gacha/route' import homeRoute from '@/pages/Home/route' import rootRoute from '@/pages/Root/route' import settingsRoute from '@/pages/Settings/route' const routeTree = rootRoute.addChildren([ + gachaRoute, homeRoute, settingsRoute ])