Add in-app wiki help system with accounts and members articles
Markdown-based help pages rendered in the admin UI. Sidebar category navigation with search. Articles: Getting Started, Accounts Overview, Members Overview, Payment Methods, Tax Exemptions. Written for non-technical store staff.
This commit is contained in:
@@ -1,323 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
|
|
||||||
// This file was automatically generated by TanStack Router.
|
|
||||||
// You should NOT make any changes in this file as it will be overwritten.
|
|
||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
|
||||||
import { Route as LoginRouteImport } from './routes/login'
|
|
||||||
import { Route as AuthenticatedRouteImport } from './routes/_authenticated'
|
|
||||||
import { Route as AuthenticatedIndexRouteImport } from './routes/_authenticated/index'
|
|
||||||
import { Route as AuthenticatedMembersRouteImport } from './routes/_authenticated/members'
|
|
||||||
import { Route as AuthenticatedAccountsIndexRouteImport } from './routes/_authenticated/accounts/index'
|
|
||||||
import { Route as AuthenticatedAccountsNewRouteImport } from './routes/_authenticated/accounts/new'
|
|
||||||
import { Route as AuthenticatedAccountsAccountIdRouteImport } from './routes/_authenticated/accounts/$accountId'
|
|
||||||
import { Route as AuthenticatedAccountsAccountIdIndexRouteImport } from './routes/_authenticated/accounts/$accountId/index'
|
|
||||||
import { Route as AuthenticatedAccountsAccountIdTaxExemptionsRouteImport } from './routes/_authenticated/accounts/$accountId/tax-exemptions'
|
|
||||||
import { Route as AuthenticatedAccountsAccountIdProcessorLinksRouteImport } from './routes/_authenticated/accounts/$accountId/processor-links'
|
|
||||||
import { Route as AuthenticatedAccountsAccountIdPaymentMethodsRouteImport } from './routes/_authenticated/accounts/$accountId/payment-methods'
|
|
||||||
import { Route as AuthenticatedAccountsAccountIdMembersRouteImport } from './routes/_authenticated/accounts/$accountId/members'
|
|
||||||
|
|
||||||
const LoginRoute = LoginRouteImport.update({
|
|
||||||
id: '/login',
|
|
||||||
path: '/login',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedRoute = AuthenticatedRouteImport.update({
|
|
||||||
id: '/_authenticated',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedIndexRoute = AuthenticatedIndexRouteImport.update({
|
|
||||||
id: '/',
|
|
||||||
path: '/',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedMembersRoute = AuthenticatedMembersRouteImport.update({
|
|
||||||
id: '/members',
|
|
||||||
path: '/members',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedAccountsIndexRoute =
|
|
||||||
AuthenticatedAccountsIndexRouteImport.update({
|
|
||||||
id: '/accounts/',
|
|
||||||
path: '/accounts/',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedAccountsNewRoute =
|
|
||||||
AuthenticatedAccountsNewRouteImport.update({
|
|
||||||
id: '/accounts/new',
|
|
||||||
path: '/accounts/new',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedAccountsAccountIdRoute =
|
|
||||||
AuthenticatedAccountsAccountIdRouteImport.update({
|
|
||||||
id: '/accounts/$accountId',
|
|
||||||
path: '/accounts/$accountId',
|
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedAccountsAccountIdIndexRoute =
|
|
||||||
AuthenticatedAccountsAccountIdIndexRouteImport.update({
|
|
||||||
id: '/',
|
|
||||||
path: '/',
|
|
||||||
getParentRoute: () => AuthenticatedAccountsAccountIdRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedAccountsAccountIdTaxExemptionsRoute =
|
|
||||||
AuthenticatedAccountsAccountIdTaxExemptionsRouteImport.update({
|
|
||||||
id: '/tax-exemptions',
|
|
||||||
path: '/tax-exemptions',
|
|
||||||
getParentRoute: () => AuthenticatedAccountsAccountIdRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedAccountsAccountIdProcessorLinksRoute =
|
|
||||||
AuthenticatedAccountsAccountIdProcessorLinksRouteImport.update({
|
|
||||||
id: '/processor-links',
|
|
||||||
path: '/processor-links',
|
|
||||||
getParentRoute: () => AuthenticatedAccountsAccountIdRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedAccountsAccountIdPaymentMethodsRoute =
|
|
||||||
AuthenticatedAccountsAccountIdPaymentMethodsRouteImport.update({
|
|
||||||
id: '/payment-methods',
|
|
||||||
path: '/payment-methods',
|
|
||||||
getParentRoute: () => AuthenticatedAccountsAccountIdRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthenticatedAccountsAccountIdMembersRoute =
|
|
||||||
AuthenticatedAccountsAccountIdMembersRouteImport.update({
|
|
||||||
id: '/members',
|
|
||||||
path: '/members',
|
|
||||||
getParentRoute: () => AuthenticatedAccountsAccountIdRoute,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
|
||||||
'/': typeof AuthenticatedIndexRoute
|
|
||||||
'/login': typeof LoginRoute
|
|
||||||
'/members': typeof AuthenticatedMembersRoute
|
|
||||||
'/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
|
|
||||||
'/accounts/new': typeof AuthenticatedAccountsNewRoute
|
|
||||||
'/accounts/': typeof AuthenticatedAccountsIndexRoute
|
|
||||||
'/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
|
|
||||||
'/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
|
|
||||||
'/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute
|
|
||||||
'/accounts/$accountId/tax-exemptions': typeof AuthenticatedAccountsAccountIdTaxExemptionsRoute
|
|
||||||
'/accounts/$accountId/': typeof AuthenticatedAccountsAccountIdIndexRoute
|
|
||||||
}
|
|
||||||
export interface FileRoutesByTo {
|
|
||||||
'/login': typeof LoginRoute
|
|
||||||
'/members': typeof AuthenticatedMembersRoute
|
|
||||||
'/': typeof AuthenticatedIndexRoute
|
|
||||||
'/accounts/new': typeof AuthenticatedAccountsNewRoute
|
|
||||||
'/accounts': typeof AuthenticatedAccountsIndexRoute
|
|
||||||
'/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
|
|
||||||
'/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
|
|
||||||
'/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute
|
|
||||||
'/accounts/$accountId/tax-exemptions': typeof AuthenticatedAccountsAccountIdTaxExemptionsRoute
|
|
||||||
'/accounts/$accountId': typeof AuthenticatedAccountsAccountIdIndexRoute
|
|
||||||
}
|
|
||||||
export interface FileRoutesById {
|
|
||||||
__root__: typeof rootRouteImport
|
|
||||||
'/_authenticated': typeof AuthenticatedRouteWithChildren
|
|
||||||
'/login': typeof LoginRoute
|
|
||||||
'/_authenticated/members': typeof AuthenticatedMembersRoute
|
|
||||||
'/_authenticated/': typeof AuthenticatedIndexRoute
|
|
||||||
'/_authenticated/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
|
|
||||||
'/_authenticated/accounts/new': typeof AuthenticatedAccountsNewRoute
|
|
||||||
'/_authenticated/accounts/': typeof AuthenticatedAccountsIndexRoute
|
|
||||||
'/_authenticated/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
|
|
||||||
'/_authenticated/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
|
|
||||||
'/_authenticated/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute
|
|
||||||
'/_authenticated/accounts/$accountId/tax-exemptions': typeof AuthenticatedAccountsAccountIdTaxExemptionsRoute
|
|
||||||
'/_authenticated/accounts/$accountId/': typeof AuthenticatedAccountsAccountIdIndexRoute
|
|
||||||
}
|
|
||||||
export interface FileRouteTypes {
|
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
|
||||||
fullPaths:
|
|
||||||
| '/'
|
|
||||||
| '/login'
|
|
||||||
| '/members'
|
|
||||||
| '/accounts/$accountId'
|
|
||||||
| '/accounts/new'
|
|
||||||
| '/accounts/'
|
|
||||||
| '/accounts/$accountId/members'
|
|
||||||
| '/accounts/$accountId/payment-methods'
|
|
||||||
| '/accounts/$accountId/processor-links'
|
|
||||||
| '/accounts/$accountId/tax-exemptions'
|
|
||||||
| '/accounts/$accountId/'
|
|
||||||
fileRoutesByTo: FileRoutesByTo
|
|
||||||
to:
|
|
||||||
| '/login'
|
|
||||||
| '/members'
|
|
||||||
| '/'
|
|
||||||
| '/accounts/new'
|
|
||||||
| '/accounts'
|
|
||||||
| '/accounts/$accountId/members'
|
|
||||||
| '/accounts/$accountId/payment-methods'
|
|
||||||
| '/accounts/$accountId/processor-links'
|
|
||||||
| '/accounts/$accountId/tax-exemptions'
|
|
||||||
| '/accounts/$accountId'
|
|
||||||
id:
|
|
||||||
| '__root__'
|
|
||||||
| '/_authenticated'
|
|
||||||
| '/login'
|
|
||||||
| '/_authenticated/members'
|
|
||||||
| '/_authenticated/'
|
|
||||||
| '/_authenticated/accounts/$accountId'
|
|
||||||
| '/_authenticated/accounts/new'
|
|
||||||
| '/_authenticated/accounts/'
|
|
||||||
| '/_authenticated/accounts/$accountId/members'
|
|
||||||
| '/_authenticated/accounts/$accountId/payment-methods'
|
|
||||||
| '/_authenticated/accounts/$accountId/processor-links'
|
|
||||||
| '/_authenticated/accounts/$accountId/tax-exemptions'
|
|
||||||
| '/_authenticated/accounts/$accountId/'
|
|
||||||
fileRoutesById: FileRoutesById
|
|
||||||
}
|
|
||||||
export interface RootRouteChildren {
|
|
||||||
AuthenticatedRoute: typeof AuthenticatedRouteWithChildren
|
|
||||||
LoginRoute: typeof LoginRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
|
||||||
interface FileRoutesByPath {
|
|
||||||
'/login': {
|
|
||||||
id: '/login'
|
|
||||||
path: '/login'
|
|
||||||
fullPath: '/login'
|
|
||||||
preLoaderRoute: typeof LoginRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
'/_authenticated': {
|
|
||||||
id: '/_authenticated'
|
|
||||||
path: ''
|
|
||||||
fullPath: '/'
|
|
||||||
preLoaderRoute: typeof AuthenticatedRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
'/_authenticated/': {
|
|
||||||
id: '/_authenticated/'
|
|
||||||
path: '/'
|
|
||||||
fullPath: '/'
|
|
||||||
preLoaderRoute: typeof AuthenticatedIndexRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/members': {
|
|
||||||
id: '/_authenticated/members'
|
|
||||||
path: '/members'
|
|
||||||
fullPath: '/members'
|
|
||||||
preLoaderRoute: typeof AuthenticatedMembersRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/accounts/': {
|
|
||||||
id: '/_authenticated/accounts/'
|
|
||||||
path: '/accounts'
|
|
||||||
fullPath: '/accounts/'
|
|
||||||
preLoaderRoute: typeof AuthenticatedAccountsIndexRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/accounts/new': {
|
|
||||||
id: '/_authenticated/accounts/new'
|
|
||||||
path: '/accounts/new'
|
|
||||||
fullPath: '/accounts/new'
|
|
||||||
preLoaderRoute: typeof AuthenticatedAccountsNewRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/accounts/$accountId': {
|
|
||||||
id: '/_authenticated/accounts/$accountId'
|
|
||||||
path: '/accounts/$accountId'
|
|
||||||
fullPath: '/accounts/$accountId'
|
|
||||||
preLoaderRoute: typeof AuthenticatedAccountsAccountIdRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/accounts/$accountId/': {
|
|
||||||
id: '/_authenticated/accounts/$accountId/'
|
|
||||||
path: '/'
|
|
||||||
fullPath: '/accounts/$accountId/'
|
|
||||||
preLoaderRoute: typeof AuthenticatedAccountsAccountIdIndexRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedAccountsAccountIdRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/accounts/$accountId/tax-exemptions': {
|
|
||||||
id: '/_authenticated/accounts/$accountId/tax-exemptions'
|
|
||||||
path: '/tax-exemptions'
|
|
||||||
fullPath: '/accounts/$accountId/tax-exemptions'
|
|
||||||
preLoaderRoute: typeof AuthenticatedAccountsAccountIdTaxExemptionsRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedAccountsAccountIdRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/accounts/$accountId/processor-links': {
|
|
||||||
id: '/_authenticated/accounts/$accountId/processor-links'
|
|
||||||
path: '/processor-links'
|
|
||||||
fullPath: '/accounts/$accountId/processor-links'
|
|
||||||
preLoaderRoute: typeof AuthenticatedAccountsAccountIdProcessorLinksRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedAccountsAccountIdRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/accounts/$accountId/payment-methods': {
|
|
||||||
id: '/_authenticated/accounts/$accountId/payment-methods'
|
|
||||||
path: '/payment-methods'
|
|
||||||
fullPath: '/accounts/$accountId/payment-methods'
|
|
||||||
preLoaderRoute: typeof AuthenticatedAccountsAccountIdPaymentMethodsRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedAccountsAccountIdRoute
|
|
||||||
}
|
|
||||||
'/_authenticated/accounts/$accountId/members': {
|
|
||||||
id: '/_authenticated/accounts/$accountId/members'
|
|
||||||
path: '/members'
|
|
||||||
fullPath: '/accounts/$accountId/members'
|
|
||||||
preLoaderRoute: typeof AuthenticatedAccountsAccountIdMembersRouteImport
|
|
||||||
parentRoute: typeof AuthenticatedAccountsAccountIdRoute
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AuthenticatedAccountsAccountIdRouteChildren {
|
|
||||||
AuthenticatedAccountsAccountIdMembersRoute: typeof AuthenticatedAccountsAccountIdMembersRoute
|
|
||||||
AuthenticatedAccountsAccountIdPaymentMethodsRoute: typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
|
|
||||||
AuthenticatedAccountsAccountIdProcessorLinksRoute: typeof AuthenticatedAccountsAccountIdProcessorLinksRoute
|
|
||||||
AuthenticatedAccountsAccountIdTaxExemptionsRoute: typeof AuthenticatedAccountsAccountIdTaxExemptionsRoute
|
|
||||||
AuthenticatedAccountsAccountIdIndexRoute: typeof AuthenticatedAccountsAccountIdIndexRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
const AuthenticatedAccountsAccountIdRouteChildren: AuthenticatedAccountsAccountIdRouteChildren =
|
|
||||||
{
|
|
||||||
AuthenticatedAccountsAccountIdMembersRoute:
|
|
||||||
AuthenticatedAccountsAccountIdMembersRoute,
|
|
||||||
AuthenticatedAccountsAccountIdPaymentMethodsRoute:
|
|
||||||
AuthenticatedAccountsAccountIdPaymentMethodsRoute,
|
|
||||||
AuthenticatedAccountsAccountIdProcessorLinksRoute:
|
|
||||||
AuthenticatedAccountsAccountIdProcessorLinksRoute,
|
|
||||||
AuthenticatedAccountsAccountIdTaxExemptionsRoute:
|
|
||||||
AuthenticatedAccountsAccountIdTaxExemptionsRoute,
|
|
||||||
AuthenticatedAccountsAccountIdIndexRoute:
|
|
||||||
AuthenticatedAccountsAccountIdIndexRoute,
|
|
||||||
}
|
|
||||||
|
|
||||||
const AuthenticatedAccountsAccountIdRouteWithChildren =
|
|
||||||
AuthenticatedAccountsAccountIdRoute._addFileChildren(
|
|
||||||
AuthenticatedAccountsAccountIdRouteChildren,
|
|
||||||
)
|
|
||||||
|
|
||||||
interface AuthenticatedRouteChildren {
|
|
||||||
AuthenticatedMembersRoute: typeof AuthenticatedMembersRoute
|
|
||||||
AuthenticatedIndexRoute: typeof AuthenticatedIndexRoute
|
|
||||||
AuthenticatedAccountsAccountIdRoute: typeof AuthenticatedAccountsAccountIdRouteWithChildren
|
|
||||||
AuthenticatedAccountsNewRoute: typeof AuthenticatedAccountsNewRoute
|
|
||||||
AuthenticatedAccountsIndexRoute: typeof AuthenticatedAccountsIndexRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
|
|
||||||
AuthenticatedMembersRoute: AuthenticatedMembersRoute,
|
|
||||||
AuthenticatedIndexRoute: AuthenticatedIndexRoute,
|
|
||||||
AuthenticatedAccountsAccountIdRoute:
|
|
||||||
AuthenticatedAccountsAccountIdRouteWithChildren,
|
|
||||||
AuthenticatedAccountsNewRoute: AuthenticatedAccountsNewRoute,
|
|
||||||
AuthenticatedAccountsIndexRoute: AuthenticatedAccountsIndexRoute,
|
|
||||||
}
|
|
||||||
|
|
||||||
const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren(
|
|
||||||
AuthenticatedRouteChildren,
|
|
||||||
)
|
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
|
||||||
AuthenticatedRoute: AuthenticatedRouteWithChildren,
|
|
||||||
LoginRoute: LoginRoute,
|
|
||||||
}
|
|
||||||
export const routeTree = rootRouteImport
|
|
||||||
._addFileChildren(rootRouteChildren)
|
|
||||||
._addFileTypes<FileRouteTypes>()
|
|
||||||
@@ -13,6 +13,7 @@ import { Route as LoginRouteImport } from './routes/login'
|
|||||||
import { Route as AuthenticatedRouteImport } from './routes/_authenticated'
|
import { Route as AuthenticatedRouteImport } from './routes/_authenticated'
|
||||||
import { Route as AuthenticatedIndexRouteImport } from './routes/_authenticated/index'
|
import { Route as AuthenticatedIndexRouteImport } from './routes/_authenticated/index'
|
||||||
import { Route as AuthenticatedMembersRouteImport } from './routes/_authenticated/members'
|
import { Route as AuthenticatedMembersRouteImport } from './routes/_authenticated/members'
|
||||||
|
import { Route as AuthenticatedHelpRouteImport } from './routes/_authenticated/help'
|
||||||
import { Route as AuthenticatedAccountsIndexRouteImport } from './routes/_authenticated/accounts/index'
|
import { Route as AuthenticatedAccountsIndexRouteImport } from './routes/_authenticated/accounts/index'
|
||||||
import { Route as AuthenticatedAccountsNewRouteImport } from './routes/_authenticated/accounts/new'
|
import { Route as AuthenticatedAccountsNewRouteImport } from './routes/_authenticated/accounts/new'
|
||||||
import { Route as AuthenticatedAccountsAccountIdRouteImport } from './routes/_authenticated/accounts/$accountId'
|
import { Route as AuthenticatedAccountsAccountIdRouteImport } from './routes/_authenticated/accounts/$accountId'
|
||||||
@@ -41,6 +42,11 @@ const AuthenticatedMembersRoute = AuthenticatedMembersRouteImport.update({
|
|||||||
path: '/members',
|
path: '/members',
|
||||||
getParentRoute: () => AuthenticatedRoute,
|
getParentRoute: () => AuthenticatedRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AuthenticatedHelpRoute = AuthenticatedHelpRouteImport.update({
|
||||||
|
id: '/help',
|
||||||
|
path: '/help',
|
||||||
|
getParentRoute: () => AuthenticatedRoute,
|
||||||
|
} as any)
|
||||||
const AuthenticatedAccountsIndexRoute =
|
const AuthenticatedAccountsIndexRoute =
|
||||||
AuthenticatedAccountsIndexRouteImport.update({
|
AuthenticatedAccountsIndexRouteImport.update({
|
||||||
id: '/accounts/',
|
id: '/accounts/',
|
||||||
@@ -93,6 +99,7 @@ const AuthenticatedAccountsAccountIdMembersRoute =
|
|||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof AuthenticatedIndexRoute
|
'/': typeof AuthenticatedIndexRoute
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
|
'/help': typeof AuthenticatedHelpRoute
|
||||||
'/members': typeof AuthenticatedMembersRoute
|
'/members': typeof AuthenticatedMembersRoute
|
||||||
'/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
|
'/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
|
||||||
'/accounts/new': typeof AuthenticatedAccountsNewRoute
|
'/accounts/new': typeof AuthenticatedAccountsNewRoute
|
||||||
@@ -105,6 +112,7 @@ export interface FileRoutesByFullPath {
|
|||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
|
'/help': typeof AuthenticatedHelpRoute
|
||||||
'/members': typeof AuthenticatedMembersRoute
|
'/members': typeof AuthenticatedMembersRoute
|
||||||
'/': typeof AuthenticatedIndexRoute
|
'/': typeof AuthenticatedIndexRoute
|
||||||
'/accounts/new': typeof AuthenticatedAccountsNewRoute
|
'/accounts/new': typeof AuthenticatedAccountsNewRoute
|
||||||
@@ -119,6 +127,7 @@ export interface FileRoutesById {
|
|||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/_authenticated': typeof AuthenticatedRouteWithChildren
|
'/_authenticated': typeof AuthenticatedRouteWithChildren
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
|
'/_authenticated/help': typeof AuthenticatedHelpRoute
|
||||||
'/_authenticated/members': typeof AuthenticatedMembersRoute
|
'/_authenticated/members': typeof AuthenticatedMembersRoute
|
||||||
'/_authenticated/': typeof AuthenticatedIndexRoute
|
'/_authenticated/': typeof AuthenticatedIndexRoute
|
||||||
'/_authenticated/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
|
'/_authenticated/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
|
||||||
@@ -135,6 +144,7 @@ export interface FileRouteTypes {
|
|||||||
fullPaths:
|
fullPaths:
|
||||||
| '/'
|
| '/'
|
||||||
| '/login'
|
| '/login'
|
||||||
|
| '/help'
|
||||||
| '/members'
|
| '/members'
|
||||||
| '/accounts/$accountId'
|
| '/accounts/$accountId'
|
||||||
| '/accounts/new'
|
| '/accounts/new'
|
||||||
@@ -147,6 +157,7 @@ export interface FileRouteTypes {
|
|||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/login'
|
| '/login'
|
||||||
|
| '/help'
|
||||||
| '/members'
|
| '/members'
|
||||||
| '/'
|
| '/'
|
||||||
| '/accounts/new'
|
| '/accounts/new'
|
||||||
@@ -160,6 +171,7 @@ export interface FileRouteTypes {
|
|||||||
| '__root__'
|
| '__root__'
|
||||||
| '/_authenticated'
|
| '/_authenticated'
|
||||||
| '/login'
|
| '/login'
|
||||||
|
| '/_authenticated/help'
|
||||||
| '/_authenticated/members'
|
| '/_authenticated/members'
|
||||||
| '/_authenticated/'
|
| '/_authenticated/'
|
||||||
| '/_authenticated/accounts/$accountId'
|
| '/_authenticated/accounts/$accountId'
|
||||||
@@ -207,6 +219,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticatedMembersRouteImport
|
preLoaderRoute: typeof AuthenticatedMembersRouteImport
|
||||||
parentRoute: typeof AuthenticatedRoute
|
parentRoute: typeof AuthenticatedRoute
|
||||||
}
|
}
|
||||||
|
'/_authenticated/help': {
|
||||||
|
id: '/_authenticated/help'
|
||||||
|
path: '/help'
|
||||||
|
fullPath: '/help'
|
||||||
|
preLoaderRoute: typeof AuthenticatedHelpRouteImport
|
||||||
|
parentRoute: typeof AuthenticatedRoute
|
||||||
|
}
|
||||||
'/_authenticated/accounts/': {
|
'/_authenticated/accounts/': {
|
||||||
id: '/_authenticated/accounts/'
|
id: '/_authenticated/accounts/'
|
||||||
path: '/accounts'
|
path: '/accounts'
|
||||||
@@ -294,6 +313,7 @@ const AuthenticatedAccountsAccountIdRouteWithChildren =
|
|||||||
)
|
)
|
||||||
|
|
||||||
interface AuthenticatedRouteChildren {
|
interface AuthenticatedRouteChildren {
|
||||||
|
AuthenticatedHelpRoute: typeof AuthenticatedHelpRoute
|
||||||
AuthenticatedMembersRoute: typeof AuthenticatedMembersRoute
|
AuthenticatedMembersRoute: typeof AuthenticatedMembersRoute
|
||||||
AuthenticatedIndexRoute: typeof AuthenticatedIndexRoute
|
AuthenticatedIndexRoute: typeof AuthenticatedIndexRoute
|
||||||
AuthenticatedAccountsAccountIdRoute: typeof AuthenticatedAccountsAccountIdRouteWithChildren
|
AuthenticatedAccountsAccountIdRoute: typeof AuthenticatedAccountsAccountIdRouteWithChildren
|
||||||
@@ -302,6 +322,7 @@ interface AuthenticatedRouteChildren {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
|
const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
|
||||||
|
AuthenticatedHelpRoute: AuthenticatedHelpRoute,
|
||||||
AuthenticatedMembersRoute: AuthenticatedMembersRoute,
|
AuthenticatedMembersRoute: AuthenticatedMembersRoute,
|
||||||
AuthenticatedIndexRoute: AuthenticatedIndexRoute,
|
AuthenticatedIndexRoute: AuthenticatedIndexRoute,
|
||||||
AuthenticatedAccountsAccountIdRoute:
|
AuthenticatedAccountsAccountIdRoute:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
DropdownMenuSubTrigger,
|
DropdownMenuSubTrigger,
|
||||||
DropdownMenuSubContent,
|
DropdownMenuSubContent,
|
||||||
} from '@/components/ui/dropdown-menu'
|
} from '@/components/ui/dropdown-menu'
|
||||||
import { Users, UserRound, Sun, Moon, Monitor, LogOut, User, Palette } from 'lucide-react'
|
import { Users, UserRound, HelpCircle, Sun, Moon, Monitor, LogOut, User, Palette } from 'lucide-react'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_authenticated')({
|
export const Route = createFileRoute('/_authenticated')({
|
||||||
beforeLoad: () => {
|
beforeLoad: () => {
|
||||||
@@ -69,6 +69,14 @@ function AuthenticatedLayout() {
|
|||||||
<UserRound className="h-4 w-4" />
|
<UserRound className="h-4 w-4" />
|
||||||
Members
|
Members
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/help"
|
||||||
|
className="flex items-center gap-2 px-3 py-2 rounded-md text-sm text-sidebar-foreground hover:bg-sidebar-accent"
|
||||||
|
activeProps={{ className: 'flex items-center gap-2 px-3 py-2 rounded-md text-sm bg-sidebar-accent text-sidebar-accent-foreground' }}
|
||||||
|
>
|
||||||
|
<HelpCircle className="h-4 w-4" />
|
||||||
|
Help
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 border-t border-sidebar-border">
|
<div className="p-3 border-t border-sidebar-border">
|
||||||
|
|||||||
148
packages/admin/src/routes/_authenticated/help.tsx
Normal file
148
packages/admin/src/routes/_authenticated/help.tsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { getWikiCategories, getWikiPage, type WikiPage } from '@/wiki'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Search } from 'lucide-react'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_authenticated/help')({
|
||||||
|
validateSearch: (search: Record<string, unknown>) => ({
|
||||||
|
page: (search.page as string) || 'getting-started',
|
||||||
|
}),
|
||||||
|
component: HelpPage,
|
||||||
|
})
|
||||||
|
|
||||||
|
function renderMarkdown(content: string) {
|
||||||
|
// Simple markdown to HTML — handles headers, bold, lists, tips, notes
|
||||||
|
return content
|
||||||
|
.split('\n\n')
|
||||||
|
.map((block, i) => {
|
||||||
|
const trimmed = block.trim()
|
||||||
|
if (!trimmed) return null
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
if (trimmed.startsWith('## '))
|
||||||
|
return <h2 key={i} className="text-lg font-semibold mt-6 mb-2">{trimmed.slice(3)}</h2>
|
||||||
|
if (trimmed.startsWith('# '))
|
||||||
|
return <h1 key={i} className="text-2xl font-bold mb-4">{trimmed.slice(2)}</h1>
|
||||||
|
|
||||||
|
// Lists
|
||||||
|
if (trimmed.match(/^\d+\./m) || trimmed.startsWith('- ')) {
|
||||||
|
const items = trimmed.split('\n').filter(Boolean)
|
||||||
|
const isOrdered = items[0].match(/^\d+\./)
|
||||||
|
const Tag = isOrdered ? 'ol' : 'ul'
|
||||||
|
return (
|
||||||
|
<Tag key={i} className={`my-2 pl-6 space-y-1 ${isOrdered ? 'list-decimal' : 'list-disc'}`}>
|
||||||
|
{items.map((item, j) => (
|
||||||
|
<li key={j} className="text-sm">
|
||||||
|
{renderInline(item.replace(/^(\d+\.\s*|-\s*)/, ''))}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paragraph
|
||||||
|
return <p key={i} className="text-sm leading-relaxed my-2">{renderInline(trimmed)}</p>
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderInline(text: string) {
|
||||||
|
// Bold
|
||||||
|
const parts = text.split(/(\*\*[^*]+\*\*)/)
|
||||||
|
return parts.map((part, i) => {
|
||||||
|
if (part.startsWith('**') && part.endsWith('**')) {
|
||||||
|
return <strong key={i}>{part.slice(2, -2)}</strong>
|
||||||
|
}
|
||||||
|
return part
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function HelpPage() {
|
||||||
|
const categories = getWikiCategories()
|
||||||
|
const search = Route.useSearch()
|
||||||
|
const navigate = Route.useNavigate()
|
||||||
|
const currentPage = getWikiPage(search.page)
|
||||||
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
|
|
||||||
|
const allPages = categories.flatMap((c) => c.pages)
|
||||||
|
const filteredPages = searchQuery
|
||||||
|
? allPages.filter(
|
||||||
|
(p) =>
|
||||||
|
p.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
p.content.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
|
||||||
|
function goToPage(slug: string) {
|
||||||
|
navigate({ search: { page: slug } })
|
||||||
|
setSearchQuery('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-6 max-w-5xl">
|
||||||
|
{/* Sidebar */}
|
||||||
|
<div className="w-56 shrink-0 space-y-4">
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
placeholder="Search help..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="pl-9 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{filteredPages ? (
|
||||||
|
<div className="space-y-1">
|
||||||
|
{filteredPages.length === 0 ? (
|
||||||
|
<p className="text-sm text-muted-foreground px-2">No results</p>
|
||||||
|
) : (
|
||||||
|
filteredPages.map((p) => (
|
||||||
|
<button
|
||||||
|
key={p.slug}
|
||||||
|
onClick={() => goToPage(p.slug)}
|
||||||
|
className="block w-full text-left px-2 py-1.5 text-sm rounded-md hover:bg-accent"
|
||||||
|
>
|
||||||
|
{p.title}
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
categories.map((cat) => (
|
||||||
|
<div key={cat.name}>
|
||||||
|
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wide px-2 mb-1">
|
||||||
|
{cat.name}
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
{cat.pages.map((p) => (
|
||||||
|
<button
|
||||||
|
key={p.slug}
|
||||||
|
onClick={() => goToPage(p.slug)}
|
||||||
|
className={`block w-full text-left px-2 py-1.5 text-sm rounded-md transition-colors ${
|
||||||
|
search.page === p.slug
|
||||||
|
? 'bg-accent text-accent-foreground font-medium'
|
||||||
|
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{p.title}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
{currentPage ? (
|
||||||
|
<div className="prose-sm">{renderMarkdown(currentPage.content)}</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-muted-foreground">Page not found</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
203
packages/admin/src/wiki/index.ts
Normal file
203
packages/admin/src/wiki/index.ts
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
export interface WikiPage {
|
||||||
|
slug: string
|
||||||
|
title: string
|
||||||
|
category: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WikiCategory {
|
||||||
|
name: string
|
||||||
|
pages: WikiPage[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages: WikiPage[] = [
|
||||||
|
{
|
||||||
|
slug: 'getting-started',
|
||||||
|
title: 'Getting Started',
|
||||||
|
category: 'General',
|
||||||
|
content: `
|
||||||
|
# Getting Started with Forte
|
||||||
|
|
||||||
|
Welcome to Forte — your music store management platform.
|
||||||
|
|
||||||
|
## Signing In
|
||||||
|
|
||||||
|
1. Open Forte in your browser
|
||||||
|
2. Enter your email and password
|
||||||
|
3. Click **Sign in**
|
||||||
|
|
||||||
|
If you don't have an account, ask your store manager to create one for you.
|
||||||
|
|
||||||
|
## Navigation
|
||||||
|
|
||||||
|
Use the sidebar on the left to navigate between sections:
|
||||||
|
|
||||||
|
- **Accounts** — manage customer accounts and their members
|
||||||
|
- **Members** — find and manage individual people across all accounts
|
||||||
|
- **Help** — you're here!
|
||||||
|
|
||||||
|
## Need Help?
|
||||||
|
|
||||||
|
If you can't find what you're looking for, contact your store manager or system administrator.
|
||||||
|
`.trim(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'accounts-overview',
|
||||||
|
title: 'Accounts Overview',
|
||||||
|
category: 'Accounts',
|
||||||
|
content: `
|
||||||
|
# Accounts
|
||||||
|
|
||||||
|
An **account** is a billing entity — it could be a family, a business, a school, or an individual person. All billing, invoices, and payments are tied to an account.
|
||||||
|
|
||||||
|
## Creating an Account
|
||||||
|
|
||||||
|
1. Go to **Accounts** in the sidebar
|
||||||
|
2. Click **New Account** in the top right
|
||||||
|
3. Fill in the account name (e.g. "Smith Family" or "Lincoln Elementary")
|
||||||
|
4. Optionally add email, phone, and address
|
||||||
|
5. Add the **primary contact** — this is the main person on the account
|
||||||
|
6. Click **Create Account**
|
||||||
|
|
||||||
|
**Tip:** If you leave the account name blank and fill in the primary contact, the account name will be automatically set to the person's name.
|
||||||
|
|
||||||
|
## Account Details
|
||||||
|
|
||||||
|
Click any account in the list to see its details. The detail page has tabs:
|
||||||
|
|
||||||
|
- **Overview** — edit account name, contact info, billing mode
|
||||||
|
- **Members** — people on the account (family members, students, etc.)
|
||||||
|
- **Payment Methods** — cards on file
|
||||||
|
- **Tax Exemptions** — tax-exempt certificates
|
||||||
|
- **Processor Links** — payment processor connections
|
||||||
|
|
||||||
|
## Searching
|
||||||
|
|
||||||
|
Use the search bar to find accounts by name, email, phone, or account number. You can also find accounts by searching for a member's name.
|
||||||
|
|
||||||
|
## Account Numbers
|
||||||
|
|
||||||
|
Every account gets a unique 6-digit number automatically. This number appears in the account list and can be used for quick lookup.
|
||||||
|
`.trim(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'members-overview',
|
||||||
|
title: 'Members Overview',
|
||||||
|
category: 'Accounts',
|
||||||
|
content: `
|
||||||
|
# Members
|
||||||
|
|
||||||
|
A **member** is an individual person associated with an account. This could be a parent, a child, a student, a band director — anyone who takes lessons, rents instruments, or needs to be tracked.
|
||||||
|
|
||||||
|
## Adding a Member
|
||||||
|
|
||||||
|
1. Go to an account's **Members** tab
|
||||||
|
2. Click **Add Member**
|
||||||
|
3. Enter first and last name (required)
|
||||||
|
4. Optionally add email, phone, date of birth
|
||||||
|
5. Click **Add Member**
|
||||||
|
|
||||||
|
**Tip:** If you don't provide email, phone, or address, the member will automatically inherit these from the account.
|
||||||
|
|
||||||
|
## Minor Flag
|
||||||
|
|
||||||
|
If a member is under 18, they're flagged as a **Minor**. This happens automatically if you enter a date of birth. If the family prefers not to share a birthday, you can check the "This person is a minor" box instead.
|
||||||
|
|
||||||
|
## Member Numbers
|
||||||
|
|
||||||
|
Like accounts, every member gets a unique 6-digit number for quick reference.
|
||||||
|
|
||||||
|
## Finding Members
|
||||||
|
|
||||||
|
Use the **Members** page in the sidebar to search across all members in all accounts. Click a member to go to their account.
|
||||||
|
|
||||||
|
## Moving a Member
|
||||||
|
|
||||||
|
If a member needs to be moved to a different account (e.g. a student aging out and getting their own account), ask your administrator — members can be reassigned between accounts.
|
||||||
|
`.trim(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'payment-methods',
|
||||||
|
title: 'Payment Methods',
|
||||||
|
category: 'Accounts',
|
||||||
|
content: `
|
||||||
|
# Payment Methods
|
||||||
|
|
||||||
|
Payment methods are cards on file for an account. These are used for recurring billing (rentals, lessons) and in-store purchases.
|
||||||
|
|
||||||
|
## Adding a Payment Method
|
||||||
|
|
||||||
|
1. Go to an account's **Payment Methods** tab
|
||||||
|
2. Click **Add Method**
|
||||||
|
3. Select the processor (Stripe or Global Payments)
|
||||||
|
4. Enter the payment method ID from the processor
|
||||||
|
5. Optionally enter card brand, last four digits, and expiration
|
||||||
|
6. Click **Add Payment Method**
|
||||||
|
|
||||||
|
**Note:** Card details are stored securely with the payment processor — Forte only keeps a reference and display info (last 4 digits, brand).
|
||||||
|
|
||||||
|
## Default Payment Method
|
||||||
|
|
||||||
|
One payment method can be marked as the **default**. Click the star icon next to a method to make it the default. The previous default is automatically unset.
|
||||||
|
|
||||||
|
## Needs Update Flag
|
||||||
|
|
||||||
|
If a payment method was migrated from an old system, it may show a "Needs Update" badge. This means the customer needs to re-enter their card information.
|
||||||
|
`.trim(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'tax-exemptions',
|
||||||
|
title: 'Tax Exemptions',
|
||||||
|
category: 'Accounts',
|
||||||
|
content: `
|
||||||
|
# Tax Exemptions
|
||||||
|
|
||||||
|
Schools, churches, and resellers may be exempt from sales tax. Forte tracks tax exemption certificates per account.
|
||||||
|
|
||||||
|
## Adding a Tax Exemption
|
||||||
|
|
||||||
|
1. Go to an account's **Tax Exemptions** tab
|
||||||
|
2. Click **Add Exemption**
|
||||||
|
3. Enter the certificate number (required)
|
||||||
|
4. Optionally add certificate type (e.g. "resale"), issuing state, and expiration date
|
||||||
|
5. Click **Add Tax Exemption**
|
||||||
|
|
||||||
|
The exemption starts in **Pending** status.
|
||||||
|
|
||||||
|
## Approving an Exemption
|
||||||
|
|
||||||
|
A manager must verify the certificate and approve it:
|
||||||
|
|
||||||
|
1. Click the **check mark** icon next to a pending exemption
|
||||||
|
2. The status changes to **Approved**
|
||||||
|
|
||||||
|
## Revoking an Exemption
|
||||||
|
|
||||||
|
If a certificate expires or is no longer valid:
|
||||||
|
|
||||||
|
1. Click the **X** icon next to an approved exemption
|
||||||
|
2. Enter a reason for revoking
|
||||||
|
3. The status changes back to **None**
|
||||||
|
|
||||||
|
All approvals and revocations are logged with who did it and when.
|
||||||
|
`.trim(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function getWikiPages(): WikiPage[] {
|
||||||
|
return pages
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWikiPage(slug: string): WikiPage | undefined {
|
||||||
|
return pages.find((p) => p.slug === slug)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWikiCategories(): WikiCategory[] {
|
||||||
|
const categoryMap = new Map<string, WikiPage[]>()
|
||||||
|
for (const page of pages) {
|
||||||
|
const existing = categoryMap.get(page.category) ?? []
|
||||||
|
existing.push(page)
|
||||||
|
categoryMap.set(page.category, existing)
|
||||||
|
}
|
||||||
|
return Array.from(categoryMap.entries()).map(([name, pages]) => ({ name, pages }))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user