Skip to content

Commit

Permalink
feat: gacha infrastructure
Browse files Browse the repository at this point in the history
  • Loading branch information
lgou2w committed Aug 11, 2024
1 parent aa685aa commit 1be3d52
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/api/commands/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const deleteKv = declareCommand<DeleteKvArgs, Kv | null>('database_delete
// Account

export type FindAccountsByBusinessArgs = Pick<Account, 'business'>
export const findAccountsByBusiness = declareCommand<FindAccountsByBusinessArgs, Account[] | null>('database_find_accounts_by_business')
export const findAccountsByBusiness = declareCommand<FindAccountsByBusinessArgs, Account[]>('database_find_accounts_by_business')

export type FindAccountByBusinessAndUidArgs = Pick<Account, 'business' | 'uid'>
export const findAccountByBusinessAndUid = declareCommand<FindAccountByBusinessAndUidArgs, Account | null>('database_find_account_by_business_and_uid')
Expand Down
60 changes: 60 additions & 0 deletions src/api/queries/account.ts
Original file line number Diff line number Diff line change
@@ -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<typeof queryClient.setQueryData<Account[], AccountsKey>>
) {
return queryClient.setQueryData<Account[], AccountsKey>(
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)
})
}
)
}
})
}
22 changes: 22 additions & 0 deletions src/components/BusinessContext.tsx
Original file line number Diff line number Diff line change
@@ -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<Props>) {
const { business, keyofBusinesses, children } = props
const state = useMemo<BusinessState>(() => ({
business,
keyofBusinesses
}), [business, keyofBusinesses])

return (
<BusinessContext.Provider value={state}>
{children}
</BusinessContext.Provider>
)
}
13 changes: 13 additions & 0 deletions src/contexts/BusinessContext.tsx
Original file line number Diff line number Diff line change
@@ -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<BusinessState | null>(null)

BusinessContext.displayName = 'BusinessContext'

export default BusinessContext
11 changes: 11 additions & 0 deletions src/interfaces/Declare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type OmitParametersFirst<T extends (...args: never[]) => unknown> =
T extends (...args: infer P) => unknown
? P extends [unknown, ...infer R]
? R
: P
: never

export type PickParametersFirst<T extends (...args: never[]) => unknown> =
T extends (...args: infer P) => unknown
? P[0]
: never
16 changes: 16 additions & 0 deletions src/pages/Gacha/PageView.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={classes.root}>
PageView
</div>
)
}
13 changes: 13 additions & 0 deletions src/pages/Gacha/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<BusinessProvider business={business} keyofBusinesses={keyofBusinesses}>
<GachaPageView />
</BusinessProvider>
)
}
46 changes: 46 additions & 0 deletions src/pages/Gacha/route.ts
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/pages/Home/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import rootRoute from '@/pages/Root/route'
import Home from '.'

const homeRoute = createRoute({
path: '/',
getParentRoute: () => rootRoute,
path: '/',
component: Home
})

Expand Down
2 changes: 1 addition & 1 deletion src/pages/Settings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import rootRoute from '@/pages/Root/route'
import Settings from '.'

const settingsRoute = createRoute({
path: '/settings',
getParentRoute: () => rootRoute,
path: '/settings',
component: Settings
})

Expand Down
2 changes: 2 additions & 0 deletions src/router.tsx
Original file line number Diff line number Diff line change
@@ -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
])
Expand Down

0 comments on commit 1be3d52

Please sign in to comment.