Rename Forte to LunarFront, generalize for any small business

Rebrand from Forte (music-store-specific) to LunarFront (any small business):
- Package namespace @forte/* → @lunarfront/*
- Database forte/forte_test → lunarfront/lunarfront_test
- Docker containers, volumes, connection strings
- UI branding, localStorage keys, test emails
- All documentation and planning docs

Generalize music-specific terminology:
- instrumentDescription → itemDescription
- instrumentCount → itemCount
- instrumentType → itemCategory (on service templates)
- New migration 0027_generalize_terminology for column renames
- Seed data updated with generic examples
- RBAC descriptions updated
This commit is contained in:
Ryan Moon
2026-03-30 08:51:54 -05:00
parent 535446696c
commit 9400828f62
84 changed files with 390 additions and 820 deletions

View File

@@ -1,473 +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 AuthenticatedUsersRouteImport } from './routes/_authenticated/users'
import { Route as AuthenticatedProfileRouteImport } from './routes/_authenticated/profile'
import { Route as AuthenticatedHelpRouteImport } from './routes/_authenticated/help'
import { Route as AuthenticatedRolesIndexRouteImport } from './routes/_authenticated/roles/index'
import { Route as AuthenticatedMembersIndexRouteImport } from './routes/_authenticated/members/index'
import { Route as AuthenticatedAccountsIndexRouteImport } from './routes/_authenticated/accounts/index'
import { Route as AuthenticatedRolesNewRouteImport } from './routes/_authenticated/roles/new'
import { Route as AuthenticatedRolesRoleIdRouteImport } from './routes/_authenticated/roles/$roleId'
import { Route as AuthenticatedMembersMemberIdRouteImport } from './routes/_authenticated/members/$memberId'
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 AuthenticatedUsersRoute = AuthenticatedUsersRouteImport.update({
id: '/users',
path: '/users',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedProfileRoute = AuthenticatedProfileRouteImport.update({
id: '/profile',
path: '/profile',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedHelpRoute = AuthenticatedHelpRouteImport.update({
id: '/help',
path: '/help',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedRolesIndexRoute = AuthenticatedRolesIndexRouteImport.update({
id: '/roles/',
path: '/roles/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedMembersIndexRoute =
AuthenticatedMembersIndexRouteImport.update({
id: '/members/',
path: '/members/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedAccountsIndexRoute =
AuthenticatedAccountsIndexRouteImport.update({
id: '/accounts/',
path: '/accounts/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedRolesNewRoute = AuthenticatedRolesNewRouteImport.update({
id: '/roles/new',
path: '/roles/new',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedRolesRoleIdRoute =
AuthenticatedRolesRoleIdRouteImport.update({
id: '/roles/$roleId',
path: '/roles/$roleId',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedMembersMemberIdRoute =
AuthenticatedMembersMemberIdRouteImport.update({
id: '/members/$memberId',
path: '/members/$memberId',
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
'/help': typeof AuthenticatedHelpRoute
'/profile': typeof AuthenticatedProfileRoute
'/users': typeof AuthenticatedUsersRoute
'/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
'/accounts/new': typeof AuthenticatedAccountsNewRoute
'/members/$memberId': typeof AuthenticatedMembersMemberIdRoute
'/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute
'/roles/new': typeof AuthenticatedRolesNewRoute
'/accounts/': typeof AuthenticatedAccountsIndexRoute
'/members/': typeof AuthenticatedMembersIndexRoute
'/roles/': typeof AuthenticatedRolesIndexRoute
'/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
'/help': typeof AuthenticatedHelpRoute
'/profile': typeof AuthenticatedProfileRoute
'/users': typeof AuthenticatedUsersRoute
'/': typeof AuthenticatedIndexRoute
'/accounts/new': typeof AuthenticatedAccountsNewRoute
'/members/$memberId': typeof AuthenticatedMembersMemberIdRoute
'/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute
'/roles/new': typeof AuthenticatedRolesNewRoute
'/accounts': typeof AuthenticatedAccountsIndexRoute
'/members': typeof AuthenticatedMembersIndexRoute
'/roles': typeof AuthenticatedRolesIndexRoute
'/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/help': typeof AuthenticatedHelpRoute
'/_authenticated/profile': typeof AuthenticatedProfileRoute
'/_authenticated/users': typeof AuthenticatedUsersRoute
'/_authenticated/': typeof AuthenticatedIndexRoute
'/_authenticated/accounts/$accountId': typeof AuthenticatedAccountsAccountIdRouteWithChildren
'/_authenticated/accounts/new': typeof AuthenticatedAccountsNewRoute
'/_authenticated/members/$memberId': typeof AuthenticatedMembersMemberIdRoute
'/_authenticated/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute
'/_authenticated/roles/new': typeof AuthenticatedRolesNewRoute
'/_authenticated/accounts/': typeof AuthenticatedAccountsIndexRoute
'/_authenticated/members/': typeof AuthenticatedMembersIndexRoute
'/_authenticated/roles/': typeof AuthenticatedRolesIndexRoute
'/_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'
| '/help'
| '/profile'
| '/users'
| '/accounts/$accountId'
| '/accounts/new'
| '/members/$memberId'
| '/roles/$roleId'
| '/roles/new'
| '/accounts/'
| '/members/'
| '/roles/'
| '/accounts/$accountId/members'
| '/accounts/$accountId/payment-methods'
| '/accounts/$accountId/processor-links'
| '/accounts/$accountId/tax-exemptions'
| '/accounts/$accountId/'
fileRoutesByTo: FileRoutesByTo
to:
| '/login'
| '/help'
| '/profile'
| '/users'
| '/'
| '/accounts/new'
| '/members/$memberId'
| '/roles/$roleId'
| '/roles/new'
| '/accounts'
| '/members'
| '/roles'
| '/accounts/$accountId/members'
| '/accounts/$accountId/payment-methods'
| '/accounts/$accountId/processor-links'
| '/accounts/$accountId/tax-exemptions'
| '/accounts/$accountId'
id:
| '__root__'
| '/_authenticated'
| '/login'
| '/_authenticated/help'
| '/_authenticated/profile'
| '/_authenticated/users'
| '/_authenticated/'
| '/_authenticated/accounts/$accountId'
| '/_authenticated/accounts/new'
| '/_authenticated/members/$memberId'
| '/_authenticated/roles/$roleId'
| '/_authenticated/roles/new'
| '/_authenticated/accounts/'
| '/_authenticated/members/'
| '/_authenticated/roles/'
| '/_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/users': {
id: '/_authenticated/users'
path: '/users'
fullPath: '/users'
preLoaderRoute: typeof AuthenticatedUsersRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/profile': {
id: '/_authenticated/profile'
path: '/profile'
fullPath: '/profile'
preLoaderRoute: typeof AuthenticatedProfileRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/help': {
id: '/_authenticated/help'
path: '/help'
fullPath: '/help'
preLoaderRoute: typeof AuthenticatedHelpRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/roles/': {
id: '/_authenticated/roles/'
path: '/roles'
fullPath: '/roles/'
preLoaderRoute: typeof AuthenticatedRolesIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/members/': {
id: '/_authenticated/members/'
path: '/members'
fullPath: '/members/'
preLoaderRoute: typeof AuthenticatedMembersIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/accounts/': {
id: '/_authenticated/accounts/'
path: '/accounts'
fullPath: '/accounts/'
preLoaderRoute: typeof AuthenticatedAccountsIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/roles/new': {
id: '/_authenticated/roles/new'
path: '/roles/new'
fullPath: '/roles/new'
preLoaderRoute: typeof AuthenticatedRolesNewRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/roles/$roleId': {
id: '/_authenticated/roles/$roleId'
path: '/roles/$roleId'
fullPath: '/roles/$roleId'
preLoaderRoute: typeof AuthenticatedRolesRoleIdRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/members/$memberId': {
id: '/_authenticated/members/$memberId'
path: '/members/$memberId'
fullPath: '/members/$memberId'
preLoaderRoute: typeof AuthenticatedMembersMemberIdRouteImport
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 {
AuthenticatedHelpRoute: typeof AuthenticatedHelpRoute
AuthenticatedProfileRoute: typeof AuthenticatedProfileRoute
AuthenticatedUsersRoute: typeof AuthenticatedUsersRoute
AuthenticatedIndexRoute: typeof AuthenticatedIndexRoute
AuthenticatedAccountsAccountIdRoute: typeof AuthenticatedAccountsAccountIdRouteWithChildren
AuthenticatedAccountsNewRoute: typeof AuthenticatedAccountsNewRoute
AuthenticatedMembersMemberIdRoute: typeof AuthenticatedMembersMemberIdRoute
AuthenticatedRolesRoleIdRoute: typeof AuthenticatedRolesRoleIdRoute
AuthenticatedRolesNewRoute: typeof AuthenticatedRolesNewRoute
AuthenticatedAccountsIndexRoute: typeof AuthenticatedAccountsIndexRoute
AuthenticatedMembersIndexRoute: typeof AuthenticatedMembersIndexRoute
AuthenticatedRolesIndexRoute: typeof AuthenticatedRolesIndexRoute
}
const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
AuthenticatedHelpRoute: AuthenticatedHelpRoute,
AuthenticatedProfileRoute: AuthenticatedProfileRoute,
AuthenticatedUsersRoute: AuthenticatedUsersRoute,
AuthenticatedIndexRoute: AuthenticatedIndexRoute,
AuthenticatedAccountsAccountIdRoute:
AuthenticatedAccountsAccountIdRouteWithChildren,
AuthenticatedAccountsNewRoute: AuthenticatedAccountsNewRoute,
AuthenticatedMembersMemberIdRoute: AuthenticatedMembersMemberIdRoute,
AuthenticatedRolesRoleIdRoute: AuthenticatedRolesRoleIdRoute,
AuthenticatedRolesNewRoute: AuthenticatedRolesNewRoute,
AuthenticatedAccountsIndexRoute: AuthenticatedAccountsIndexRoute,
AuthenticatedMembersIndexRoute: AuthenticatedMembersIndexRoute,
AuthenticatedRolesIndexRoute: AuthenticatedRolesIndexRoute,
}
const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren(
AuthenticatedRouteChildren,
)
const rootRouteChildren: RootRouteChildren = {
AuthenticatedRoute: AuthenticatedRouteWithChildren,
LoginRoute: LoginRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

View File

@@ -3,13 +3,13 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forte Admin</title>
<title>LunarFront Admin</title>
</head>
<body>
<script>
// Apply mode before React renders to prevent flash
(function() {
var mode = localStorage.getItem('forte-mode') || 'system';
var mode = localStorage.getItem('lunarfront-mode') || 'system';
var isDark = mode === 'dark' || (mode === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (isDark) document.documentElement.classList.add('dark');
})();

View File

@@ -1,5 +1,5 @@
{
"name": "@forte/admin",
"name": "@lunarfront/admin",
"version": "0.0.1",
"private": true,
"type": "module",
@@ -10,7 +10,7 @@
"lint": "eslint ."
},
"dependencies": {
"@forte/shared": "workspace:*",
"@lunarfront/shared": "workspace:*",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",

View File

@@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { Account } from '@/types/account'
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
export const accountKeys = {
all: ['accounts'] as const,

View File

@@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { MemberIdentifier } from '@/types/account'
import type { PaginatedResponse } from '@forte/shared/schemas'
import type { PaginatedResponse } from '@lunarfront/shared/schemas'
export const identifierKeys = {
all: (memberId: string) => ['members', memberId, 'identifiers'] as const,

View File

@@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { Member } from '@/types/account'
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
interface MemberWithAccount extends Member {
accountName: string | null

View File

@@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { PaymentMethod } from '@/types/account'
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
export const paymentMethodKeys = {
all: (accountId: string) => ['accounts', accountId, 'payment-methods'] as const,

View File

@@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { ProcessorLink } from '@/types/account'
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
export const processorLinkKeys = {
all: (accountId: string) => ['accounts', accountId, 'processor-links'] as const,

View File

@@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { Permission, Role } from '@/types/rbac'
import type { PaginationInput, PaginatedResponse } from '@forte/shared/schemas'
import type { PaginationInput, PaginatedResponse } from '@lunarfront/shared/schemas'
export const rbacKeys = {
permissions: ['permissions'] as const,

View File

@@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { RepairTicket, RepairLineItem, RepairBatch, RepairNote, RepairServiceTemplate } from '@/types/repair'
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
// --- Repair Tickets ---

View File

@@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { StorageFolder, StorageFolderPermission, StorageFile } from '@/types/storage'
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
// --- Folders ---

View File

@@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { TaxExemption } from '@/types/account'
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
export const taxExemptionKeys = {
all: (accountId: string) => ['accounts', accountId, 'tax-exemptions'] as const,

View File

@@ -1,6 +1,6 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { PaginationInput, PaginatedResponse } from '@forte/shared/schemas'
import type { PaginationInput, PaginatedResponse } from '@lunarfront/shared/schemas'
export interface UserRole {
id: string

View File

@@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { VaultStatus, VaultCategory, VaultCategoryPermission, VaultEntry } from '@/types/vault'
import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas'
import type { PaginatedResponse, PaginationInput } from '@lunarfront/shared/schemas'
// --- Keys ---

View File

@@ -1,7 +1,7 @@
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { AccountCreateSchema } from '@forte/shared/schemas'
import { AccountCreateSchema } from '@lunarfront/shared/schemas'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'

View File

@@ -1,7 +1,7 @@
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { PaymentMethodCreateSchema } from '@forte/shared/schemas'
import type { PaymentMethodCreateInput } from '@forte/shared/schemas'
import { PaymentMethodCreateSchema } from '@lunarfront/shared/schemas'
import type { PaymentMethodCreateInput } from '@lunarfront/shared/schemas'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'

View File

@@ -1,7 +1,7 @@
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { ProcessorLinkCreateSchema } from '@forte/shared/schemas'
import type { ProcessorLinkCreateInput } from '@forte/shared/schemas'
import { ProcessorLinkCreateSchema } from '@lunarfront/shared/schemas'
import type { ProcessorLinkCreateInput } from '@lunarfront/shared/schemas'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'

View File

@@ -1,7 +1,7 @@
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { TaxExemptionCreateSchema } from '@forte/shared/schemas'
import type { TaxExemptionCreateInput } from '@forte/shared/schemas'
import { TaxExemptionCreateSchema } from '@lunarfront/shared/schemas'
import type { TaxExemptionCreateInput } from '@lunarfront/shared/schemas'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'

View File

@@ -24,7 +24,7 @@ interface GeneratePdfOptions {
companyName?: string
}
export function generateRepairPdf({ ticket, lineItems, notes, includeNotes, companyName = 'Forte Music' }: GeneratePdfOptions): jsPDF {
export function generateRepairPdf({ ticket, lineItems, notes, includeNotes, companyName = 'LunarFront' }: GeneratePdfOptions): jsPDF {
const doc = new jsPDF()
let y = 20
@@ -57,11 +57,11 @@ export function generateRepairPdf({ ticket, lineItems, notes, includeNotes, comp
doc.setFontSize(10)
doc.setFont('helvetica', 'bold')
doc.text('Customer', 14, y)
doc.text('Instrument', 110, y)
doc.text('Item', 110, y)
y += 5
doc.setFont('helvetica', 'normal')
doc.text(ticket.customerName, 14, y)
doc.text(ticket.instrumentDescription ?? '-', 110, y)
doc.text(ticket.itemDescription ?? '-', 110, y)
y += 5
if (ticket.customerPhone) { doc.text(ticket.customerPhone, 14, y); y += 5 }
if (ticket.serialNumber) { doc.text(`S/N: ${ticket.serialNumber}`, 110, y - 5) }

View File

@@ -1,5 +1,5 @@
import { useNavigate, useSearch } from '@tanstack/react-router'
import type { PaginationInput } from '@forte/shared/schemas'
import type { PaginationInput } from '@lunarfront/shared/schemas'
interface PaginationSearch {
page?: number

View File

@@ -16,6 +16,7 @@ import { Route as AuthenticatedUsersRouteImport } from './routes/_authenticated/
import { Route as AuthenticatedSettingsRouteImport } from './routes/_authenticated/settings'
import { Route as AuthenticatedProfileRouteImport } from './routes/_authenticated/profile'
import { Route as AuthenticatedHelpRouteImport } from './routes/_authenticated/help'
import { Route as AuthenticatedVaultIndexRouteImport } from './routes/_authenticated/vault/index'
import { Route as AuthenticatedRolesIndexRouteImport } from './routes/_authenticated/roles/index'
import { Route as AuthenticatedRepairsIndexRouteImport } from './routes/_authenticated/repairs/index'
import { Route as AuthenticatedRepairBatchesIndexRouteImport } from './routes/_authenticated/repair-batches/index'
@@ -72,6 +73,11 @@ const AuthenticatedHelpRoute = AuthenticatedHelpRouteImport.update({
path: '/help',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedVaultIndexRoute = AuthenticatedVaultIndexRouteImport.update({
id: '/vault/',
path: '/vault/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedRolesIndexRoute = AuthenticatedRolesIndexRouteImport.update({
id: '/roles/',
path: '/roles/',
@@ -218,6 +224,7 @@ export interface FileRoutesByFullPath {
'/repair-batches/': typeof AuthenticatedRepairBatchesIndexRoute
'/repairs/': typeof AuthenticatedRepairsIndexRoute
'/roles/': typeof AuthenticatedRolesIndexRoute
'/vault/': typeof AuthenticatedVaultIndexRoute
'/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
'/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
'/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute
@@ -246,6 +253,7 @@ export interface FileRoutesByTo {
'/repair-batches': typeof AuthenticatedRepairBatchesIndexRoute
'/repairs': typeof AuthenticatedRepairsIndexRoute
'/roles': typeof AuthenticatedRolesIndexRoute
'/vault': typeof AuthenticatedVaultIndexRoute
'/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
'/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
'/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute
@@ -277,6 +285,7 @@ export interface FileRoutesById {
'/_authenticated/repair-batches/': typeof AuthenticatedRepairBatchesIndexRoute
'/_authenticated/repairs/': typeof AuthenticatedRepairsIndexRoute
'/_authenticated/roles/': typeof AuthenticatedRolesIndexRoute
'/_authenticated/vault/': typeof AuthenticatedVaultIndexRoute
'/_authenticated/accounts/$accountId/members': typeof AuthenticatedAccountsAccountIdMembersRoute
'/_authenticated/accounts/$accountId/payment-methods': typeof AuthenticatedAccountsAccountIdPaymentMethodsRoute
'/_authenticated/accounts/$accountId/processor-links': typeof AuthenticatedAccountsAccountIdProcessorLinksRoute
@@ -308,6 +317,7 @@ export interface FileRouteTypes {
| '/repair-batches/'
| '/repairs/'
| '/roles/'
| '/vault/'
| '/accounts/$accountId/members'
| '/accounts/$accountId/payment-methods'
| '/accounts/$accountId/processor-links'
@@ -336,6 +346,7 @@ export interface FileRouteTypes {
| '/repair-batches'
| '/repairs'
| '/roles'
| '/vault'
| '/accounts/$accountId/members'
| '/accounts/$accountId/payment-methods'
| '/accounts/$accountId/processor-links'
@@ -366,6 +377,7 @@ export interface FileRouteTypes {
| '/_authenticated/repair-batches/'
| '/_authenticated/repairs/'
| '/_authenticated/roles/'
| '/_authenticated/vault/'
| '/_authenticated/accounts/$accountId/members'
| '/_authenticated/accounts/$accountId/payment-methods'
| '/_authenticated/accounts/$accountId/processor-links'
@@ -429,6 +441,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedHelpRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/vault/': {
id: '/_authenticated/vault/'
path: '/vault'
fullPath: '/vault/'
preLoaderRoute: typeof AuthenticatedVaultIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/roles/': {
id: '/_authenticated/roles/'
path: '/roles'
@@ -628,6 +647,7 @@ interface AuthenticatedRouteChildren {
AuthenticatedRepairBatchesIndexRoute: typeof AuthenticatedRepairBatchesIndexRoute
AuthenticatedRepairsIndexRoute: typeof AuthenticatedRepairsIndexRoute
AuthenticatedRolesIndexRoute: typeof AuthenticatedRolesIndexRoute
AuthenticatedVaultIndexRoute: typeof AuthenticatedVaultIndexRoute
}
const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
@@ -654,6 +674,7 @@ const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
AuthenticatedRepairBatchesIndexRoute: AuthenticatedRepairBatchesIndexRoute,
AuthenticatedRepairsIndexRoute: AuthenticatedRepairsIndexRoute,
AuthenticatedRolesIndexRoute: AuthenticatedRolesIndexRoute,
AuthenticatedVaultIndexRoute: AuthenticatedVaultIndexRoute,
}
const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren(

View File

@@ -34,7 +34,7 @@ const STATUS_LABELS: Record<string, string> = {
const ticketColumns: Column<RepairTicket>[] = [
{ key: 'ticket_number', header: 'Ticket #', sortable: true, render: (t) => <span className="font-mono text-sm">{t.ticketNumber}</span> },
{ key: 'instrument', header: 'Instrument', render: (t) => <>{t.instrumentDescription ?? '-'}</> },
{ key: 'item_description', header: 'Item', render: (t) => <>{t.itemDescription ?? '-'}</> },
{ key: 'problem', header: 'Problem', render: (t) => <span className="truncate max-w-[200px] block">{t.problemDescription}</span> },
{ key: 'status', header: 'Status', sortable: true, render: (t) => <Badge variant="outline">{STATUS_LABELS[t.status] ?? t.status}</Badge> },
{
@@ -111,7 +111,7 @@ function RepairBatchDetailPage() {
doc.setFontSize(18)
doc.setFont('helvetica', 'bold')
doc.text('Forte Music', 14, y)
doc.text('LunarFront', 14, y)
y += 8
doc.setFontSize(12)
doc.setFont('helvetica', 'normal')
@@ -169,7 +169,7 @@ function RepairBatchDetailPage() {
doc.setFillColor(245, 245, 245)
doc.rect(14, y - 3, 182, 6, 'F')
doc.text('Ticket #', 16, y)
doc.text('Instrument', 40, y)
doc.text('Item', 40, y)
doc.text('Problem', 100, y)
doc.text('Status', 155, y)
doc.text('Estimate', 190, y, { align: 'right' })
@@ -179,7 +179,7 @@ function RepairBatchDetailPage() {
for (const ticket of tickets) {
if (y > 270) { doc.addPage(); y = 20 }
doc.text(ticket.ticketNumber ?? '-', 16, y)
doc.text((ticket.instrumentDescription ?? '-').slice(0, 30), 40, y)
doc.text((ticket.itemDescription ?? '-').slice(0, 30), 40, y)
doc.text(ticket.problemDescription.slice(0, 28), 100, y)
doc.text(STATUS_LABELS[ticket.status] ?? ticket.status, 155, y)
doc.text(ticket.estimatedCost ? `$${ticket.estimatedCost}` : '-', 190, y, { align: 'right' })

View File

@@ -49,9 +49,9 @@ const columns: Column<RepairBatch>[] = [
},
},
{
key: 'instruments',
header: 'Instruments',
render: (b) => <>{b.receivedCount}/{b.instrumentCount}</>,
key: 'items',
header: 'Items',
render: (b) => <>{b.receivedCount}/{b.itemCount}</>,
},
{
key: 'due_date',

View File

@@ -3,7 +3,7 @@ import { createFileRoute, useNavigate, Link } from '@tanstack/react-router'
import { useQuery, useMutation } from '@tanstack/react-query'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { RepairBatchCreateSchema } from '@forte/shared/schemas'
import { RepairBatchCreateSchema } from '@lunarfront/shared/schemas'
import { repairBatchMutations } from '@/api/repairs'
import { accountListOptions } from '@/api/accounts'
import { Button } from '@/components/ui/button'
@@ -167,7 +167,7 @@ function NewRepairBatchPage() {
<div className="space-y-2">
<Label>Notes</Label>
<Textarea {...register('notes')} rows={3} placeholder="e.g. Annual instrument checkup, multiple guitars needing setups" />
<Textarea {...register('notes')} rows={3} placeholder="e.g. Annual checkup, multiple items needing service" />
</div>
</CardContent>
</Card>

View File

@@ -118,7 +118,7 @@ function RepairTicketDetailPage() {
setEditFields({
customerName: ticket!.customerName,
customerPhone: ticket!.customerPhone ?? '',
instrumentDescription: ticket!.instrumentDescription ?? '',
itemDescription: ticket!.itemDescription ?? '',
serialNumber: ticket!.serialNumber ?? '',
conditionIn: ticket!.conditionIn ?? '',
conditionInNotes: ticket!.conditionInNotes ?? '',
@@ -134,7 +134,7 @@ function RepairTicketDetailPage() {
const data: Record<string, unknown> = {}
if (editFields.customerName !== ticket!.customerName) data.customerName = editFields.customerName
if (editFields.customerPhone !== (ticket!.customerPhone ?? '')) data.customerPhone = editFields.customerPhone || undefined
if (editFields.instrumentDescription !== (ticket!.instrumentDescription ?? '')) data.instrumentDescription = editFields.instrumentDescription || undefined
if (editFields.itemDescription !== (ticket!.itemDescription ?? '')) data.itemDescription = editFields.itemDescription || undefined
if (editFields.serialNumber !== (ticket!.serialNumber ?? '')) data.serialNumber = editFields.serialNumber || undefined
if (editFields.conditionIn !== (ticket!.conditionIn ?? '')) data.conditionIn = editFields.conditionIn || undefined
if (editFields.conditionInNotes !== (ticket!.conditionInNotes ?? '')) data.conditionInNotes = editFields.conditionInNotes || undefined
@@ -180,7 +180,7 @@ function RepairTicketDetailPage() {
</Button>
<div className="flex-1">
<h1 className="text-2xl font-bold">Ticket #{ticket.ticketNumber}</h1>
<p className="text-sm text-muted-foreground">{ticket.customerName} {ticket.instrumentDescription ?? 'No instrument'}</p>
<p className="text-sm text-muted-foreground">{ticket.customerName} {ticket.itemDescription ?? 'No item description'}</p>
</div>
<PdfModal ticket={ticket} lineItems={lineItemsData?.data ?? []} ticketId={ticketId} />
</div>
@@ -278,7 +278,7 @@ function RepairTicketDetailPage() {
<div className="space-y-2"><Label>Phone</Label><Input value={editFields.customerPhone} onChange={(e) => setEditFields((p) => ({ ...p, customerPhone: e.target.value }))} /></div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2"><Label>Instrument</Label><Input value={editFields.instrumentDescription} onChange={(e) => setEditFields((p) => ({ ...p, instrumentDescription: e.target.value }))} /></div>
<div className="space-y-2"><Label>Item Description</Label><Input value={editFields.itemDescription} onChange={(e) => setEditFields((p) => ({ ...p, itemDescription: e.target.value }))} /></div>
<div className="space-y-2"><Label>Serial Number</Label><Input value={editFields.serialNumber} onChange={(e) => setEditFields((p) => ({ ...p, serialNumber: e.target.value }))} /></div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
@@ -310,7 +310,7 @@ function RepairTicketDetailPage() {
<div><span className="text-muted-foreground">Account:</span> {ticket.accountId ?? 'Walk-in'}</div>
</div>
<div className="space-y-2 text-sm">
<div><span className="text-muted-foreground">Instrument:</span> {ticket.instrumentDescription ?? '-'}</div>
<div><span className="text-muted-foreground">Item:</span> {ticket.itemDescription ?? '-'}</div>
<div><span className="text-muted-foreground">Serial:</span> {ticket.serialNumber ?? '-'}</div>
<div><span className="text-muted-foreground">Condition:</span> {ticket.conditionIn ?? '-'}</div>
</div>
@@ -411,8 +411,8 @@ function AddLineItemDialog({ ticketId, open, onOpenChange }: { ticketId: string;
setDescription(''); setQty('1'); setUnitPrice('0'); setCost(''); setItemType('labor'); setTemplateSearch(''); setShowTemplates(false)
}
function selectTemplate(template: { name: string; instrumentType: string | null; size: string | null; itemType: string; defaultPrice: string; defaultCost: string | null }) {
const desc = [template.name, template.instrumentType, template.size].filter(Boolean).join(' — ')
function selectTemplate(template: { name: string; itemCategory: string | null; size: string | null; itemType: string; defaultPrice: string; defaultCost: string | null }) {
const desc = [template.name, template.itemCategory, template.size].filter(Boolean).join(' — ')
setDescription(desc); setItemType(template.itemType); setUnitPrice(template.defaultPrice); setCost(template.defaultCost ?? ''); setShowTemplates(false); setTemplateSearch('')
}
@@ -443,7 +443,7 @@ function AddLineItemDialog({ ticketId, open, onOpenChange }: { ticketId: string;
<div className="absolute z-50 mt-1 w-full rounded-md border bg-popover shadow-lg max-h-48 overflow-auto">
{templates.length === 0 ? <div className="p-3 text-sm text-muted-foreground">No templates found</div> : templates.map((t) => (
<button key={t.id} type="button" className="w-full text-left px-3 py-2 text-sm hover:bg-accent flex justify-between" onClick={() => selectTemplate(t)}>
<span>{t.name}{t.instrumentType ? `${t.instrumentType}` : ''}{t.size ? ` ${t.size}` : ''}</span>
<span>{t.name}{t.itemCategory ? `${t.itemCategory}` : ''}{t.size ? ` ${t.size}` : ''}</span>
<span className="text-muted-foreground">${t.defaultPrice}</span>
</button>
))}

View File

@@ -11,7 +11,7 @@ import { Badge } from '@/components/ui/badge'
import { Plus, Search } from 'lucide-react'
import { useAuthStore } from '@/stores/auth.store'
import type { RepairTicket } from '@/types/repair'
import type { PaginationInput } from '@forte/shared/schemas'
import type { PaginationInput } from '@lunarfront/shared/schemas'
export const Route = createFileRoute('/_authenticated/repairs/')({
validateSearch: (search: Record<string, unknown>) => ({
@@ -70,9 +70,9 @@ const columns: Column<RepairTicket>[] = [
render: (t) => <span className="font-medium">{t.customerName}</span>,
},
{
key: 'instrument',
header: 'Instrument',
render: (t) => <>{t.instrumentDescription ?? '-'}</>,
key: 'item_description',
header: 'Item',
render: (t) => <>{t.itemDescription ?? '-'}</>,
},
{
key: 'status',

View File

@@ -3,7 +3,7 @@ import { createFileRoute, useNavigate, Link } from '@tanstack/react-router'
import { useQuery, useMutation } from '@tanstack/react-query'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { RepairTicketCreateSchema } from '@forte/shared/schemas'
import { RepairTicketCreateSchema } from '@lunarfront/shared/schemas'
import { repairTicketMutations, repairLineItemMutations, repairServiceTemplateListOptions } from '@/api/repairs'
import { accountListOptions } from '@/api/accounts'
import { useAuthStore } from '@/stores/auth.store'
@@ -88,7 +88,7 @@ function NewRepairPage() {
defaultValues: {
customerName: linkedContactName ?? '',
customerPhone: '',
instrumentDescription: '',
itemDescription: '',
serialNumber: '',
problemDescription: '',
conditionIn: undefined,
@@ -157,8 +157,8 @@ function NewRepairPage() {
setValue('customerPhone', '')
}
function addFromTemplate(template: { name: string; instrumentType: string | null; size: string | null; itemType: string; defaultPrice: string; defaultCost: string | null }) {
const desc = [template.name, template.instrumentType, template.size].filter(Boolean).join(' — ')
function addFromTemplate(template: { name: string; itemCategory: string | null; size: string | null; itemType: string; defaultPrice: string; defaultCost: string | null }) {
const desc = [template.name, template.itemCategory, template.size].filter(Boolean).join(' — ')
setLineItems((prev) => [...prev, {
itemType: template.itemType,
description: desc,
@@ -297,14 +297,14 @@ function NewRepairPage() {
</CardContent>
</Card>
{/* Instrument Section */}
{/* Item Section */}
<Card>
<CardHeader><CardTitle className="text-lg">Instrument</CardTitle></CardHeader>
<CardHeader><CardTitle className="text-lg">Item</CardTitle></CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Instrument Description</Label>
<Input {...register('instrumentDescription')} placeholder="e.g. Yamaha Trumpet YTR-2330" />
<Label>Item Description</Label>
<Input {...register('itemDescription')} placeholder="e.g. Brand, model, description" />
</div>
<div className="space-y-2">
<Label>Serial Number</Label>
@@ -364,7 +364,7 @@ function NewRepairPage() {
) : (
templates.map((t) => (
<button key={t.id} type="button" className="w-full text-left px-3 py-2 text-sm hover:bg-accent flex justify-between" onClick={() => addFromTemplate(t)}>
<span>{t.name}{t.instrumentType ? `${t.instrumentType}` : ''}{t.size ? ` ${t.size}` : ''}</span>
<span>{t.name}{t.itemCategory ? `${t.itemCategory}` : ''}{t.size ? ` ${t.size}` : ''}</span>
<span className="text-muted-foreground">${t.defaultPrice}</span>
</button>
))
@@ -462,7 +462,7 @@ function NewRepairPage() {
<Card>
<CardHeader><CardTitle className="text-lg">Intake Photos</CardTitle></CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground">Optional document instrument condition at intake</p>
<p className="text-sm text-muted-foreground">Optional document item condition at intake</p>
<div className="flex flex-wrap gap-3">
{photos.map((photo, i) => (
<div key={i} className="relative group">

View File

@@ -28,7 +28,7 @@ export const Route = createFileRoute('/_authenticated/repairs/templates')({
const columns: Column<RepairServiceTemplate>[] = [
{ key: 'name', header: 'Name', sortable: true, render: (t) => <span className="font-medium">{t.name}</span> },
{ key: 'instrument_type', header: 'Instrument', sortable: true, render: (t) => <>{t.instrumentType ?? '-'}</> },
{ key: 'item_category', header: 'Item Category', sortable: true, render: (t) => <>{t.itemCategory ?? '-'}</> },
{ key: 'size', header: 'Size', render: (t) => <>{t.size ?? '-'}</> },
{ key: 'item_type', header: 'Type', render: (t) => <Badge variant="outline">{t.itemType.replace('_', ' ')}</Badge> },
{ key: 'default_price', header: 'Price', sortable: true, render: (t) => <>${t.defaultPrice}</> },
@@ -110,7 +110,7 @@ function RepairTemplatesPage() {
function CreateTemplateDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) {
const queryClient = useQueryClient()
const [name, setName] = useState('')
const [instrumentType, setInstrumentType] = useState('')
const [itemCategory, setItemCategory] = useState('')
const [size, setSize] = useState('')
const [description, setDescription] = useState('')
const [itemType, setItemType] = useState('flat_rate')
@@ -131,7 +131,7 @@ function CreateTemplateDialog({ open, onOpenChange }: { open: boolean; onOpenCha
function resetForm() {
setName('')
setInstrumentType('')
setItemCategory('')
setSize('')
setDescription('')
setItemType('flat_rate')
@@ -144,7 +144,7 @@ function CreateTemplateDialog({ open, onOpenChange }: { open: boolean; onOpenCha
e.preventDefault()
mutation.mutate({
name,
instrumentType: instrumentType || undefined,
itemCategory: itemCategory || undefined,
size: size || undefined,
description: description || undefined,
itemType,
@@ -168,8 +168,8 @@ function CreateTemplateDialog({ open, onOpenChange }: { open: boolean; onOpenCha
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Instrument Type</Label>
<Input value={instrumentType} onChange={(e) => setInstrumentType(e.target.value)} placeholder="e.g. Violin, Trumpet, Guitar" />
<Label>Item Category</Label>
<Input value={itemCategory} onChange={(e) => setItemCategory(e.target.value)} placeholder="e.g. Electronics, Appliances, Furniture" />
</div>
<div className="space-y-2">
<Label>Size</Label>

View File

@@ -48,8 +48,8 @@ function LoginPage() {
style={{ backgroundColor: '#131c2e', borderColor: '#1e2d45' }}
>
<div className="text-center mb-8">
<h1 className="text-3xl font-bold" style={{ color: '#d8dfe9' }}>Forte</h1>
<p className="text-sm mt-1" style={{ color: '#6b7a8d' }}>Music Store Management</p>
<h1 className="text-3xl font-bold" style={{ color: '#d8dfe9' }}>LunarFront</h1>
<p className="text-sm mt-1" style={{ color: '#6b7a8d' }}>Small Business Management</p>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">

View File

@@ -50,7 +50,7 @@ function expandPermissions(slugs: string[]): Set<string> {
function loadSession(): { token: string; user: User; permissions?: string[] } | null {
try {
const raw = sessionStorage.getItem('forte-auth')
const raw = sessionStorage.getItem('lunarfront-auth')
if (!raw) return null
return JSON.parse(raw)
} catch {
@@ -59,11 +59,11 @@ function loadSession(): { token: string; user: User; permissions?: string[] } |
}
function saveSession(token: string, user: User, permissions?: string[]) {
sessionStorage.setItem('forte-auth', JSON.stringify({ token, user, permissions }))
sessionStorage.setItem('lunarfront-auth', JSON.stringify({ token, user, permissions }))
}
function clearSession() {
sessionStorage.removeItem('forte-auth')
sessionStorage.removeItem('lunarfront-auth')
}
export const useAuthStore = create<AuthState>((set, get) => {

View File

@@ -22,8 +22,8 @@ function apply(mode: Mode, colorTheme: string) {
}
export const useThemeStore = create<ThemeState>((set) => {
const initialMode = (typeof window !== 'undefined' ? localStorage.getItem('forte-mode') as Mode : null) ?? 'system'
const initialColor = (typeof window !== 'undefined' ? localStorage.getItem('forte-color-theme') : null) ?? 'slate'
const initialMode = (typeof window !== 'undefined' ? localStorage.getItem('lunarfront-mode') as Mode : null) ?? 'system'
const initialColor = (typeof window !== 'undefined' ? localStorage.getItem('lunarfront-color-theme') : null) ?? 'slate'
if (typeof window !== 'undefined') {
apply(initialMode, initialColor)
@@ -34,14 +34,14 @@ export const useThemeStore = create<ThemeState>((set) => {
colorTheme: initialColor,
setMode: (mode) => {
localStorage.setItem('forte-mode', mode)
localStorage.setItem('lunarfront-mode', mode)
const colorTheme = useThemeStore.getState().colorTheme
apply(mode, colorTheme)
set({ mode })
},
setColorTheme: (name) => {
localStorage.setItem('forte-color-theme', name)
localStorage.setItem('lunarfront-color-theme', name)
const mode = useThemeStore.getState().mode
apply(mode, name)
set({ colorTheme: name })

View File

@@ -7,7 +7,7 @@ export interface RepairTicket {
customerName: string
customerPhone: string | null
inventoryUnitId: string | null
instrumentDescription: string | null
itemDescription: string | null
serialNumber: string | null
conditionIn: 'excellent' | 'good' | 'fair' | 'poor' | null
conditionInNotes: string | null
@@ -55,7 +55,7 @@ export interface RepairBatch {
dueDate: string | null
completedDate: string | null
deliveredDate: string | null
instrumentCount: number
itemCount: number
receivedCount: number
estimatedTotal: string | null
actualTotal: string | null
@@ -79,7 +79,7 @@ export interface RepairNote {
export interface RepairServiceTemplate {
id: string
name: string
instrumentType: string | null
itemCategory: string | null
size: string | null
description: string | null
itemType: 'labor' | 'part' | 'flat_rate' | 'misc'

View File

@@ -16,17 +16,17 @@ const pages: WikiPage[] = [
title: 'Getting Started',
category: 'General',
content: `
# Getting Started with Forte
# Getting Started with LunarFront
Welcome to Forte — your music store management platform.
Welcome to LunarFront — your small business management platform.
## Signing In
1. Open Forte in your browser
1. Open LunarFront 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.
If you don't have an account, ask your admin to create one for you.
## Navigation
@@ -34,13 +34,13 @@ 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
- **Repairs** — track instrument repair tickets
- **Repairs** — track repair tickets
- **Repair Batches** — manage bulk school repair jobs
- **Help** — you're here!
## Need Help?
If you can't find what you're looking for, contact your store manager or system administrator.
If you can't find what you're looking for, contact your admin or system administrator.
`.trim(),
},
{
@@ -89,7 +89,7 @@ Every account gets a unique 6-digit number automatically. This number appears in
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.
A **member** is an individual person associated with an account. This could be a parent, a child, a student, a staff member — anyone who takes lessons, uses services, or needs to be tracked.
## Adding a Member
@@ -136,7 +136,7 @@ Payment methods are cards on file for an account. These are used for recurring b
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).
**Note:** Card details are stored securely with the payment processor — LunarFront only keeps a reference and display info (last 4 digits, brand).
## Default Payment Method
@@ -154,7 +154,7 @@ If a payment method was migrated from an old system, it may show a "Needs Update
content: `
# Tax Exemptions
Schools, churches, and resellers may be exempt from sales tax. Forte tracks tax exemption certificates per account.
Schools, churches, and resellers may be exempt from sales tax. LunarFront tracks tax exemption certificates per account.
## Adding a Tax Exemption
@@ -191,7 +191,7 @@ All approvals and revocations are logged with who did it and when.
content: `
# Identity Documents
You can store identity documents (driver's license, passport, school ID) for any member. This is useful for verifying identity during instrument pickups, rentals, or trade-ins.
You can store identity documents (driver's license, passport, school ID) for any member. This is useful for verifying identity during pickups, rentals, or trade-ins.
## Adding an ID
@@ -225,7 +225,7 @@ If a member has multiple IDs, mark one as **Primary** — this is the one shown
content: `
# Users & Roles
Forte uses a permission-based access control system. **Permissions** are specific actions (like "view accounts" or "edit inventory"). **Roles** are named groups of permissions that you assign to users.
LunarFront uses a permission-based access control system. **Permissions** are specific actions (like "view accounts" or "edit inventory"). **Roles** are named groups of permissions that you assign to users.
## Managing Users
@@ -306,16 +306,16 @@ Your preferences are saved in your browser and persist across sessions.
content: `
# Repairs
The Repairs module tracks instrument repair tickets from intake through completion. It supports walk-in customers, account-linked repairs, and bulk school batch jobs.
The Repairs module tracks repair tickets from intake through completion. It supports walk-in customers, account-linked repairs, and bulk school batch jobs.
## Creating a Repair Ticket
1. Go to **Repairs** in the sidebar
2. Click **New Repair**
3. Search for an existing account or enter customer details manually for walk-ins
4. Describe the instrument and the problem
4. Describe the item and the problem
5. Optionally add line items for the estimate (use templates for common services)
6. Add intake photos to document the instrument's condition
6. Add intake photos to document the item's condition
7. Click **Create Ticket**
## Ticket Status Flow
@@ -323,16 +323,16 @@ The Repairs module tracks instrument repair tickets from intake through completi
Each ticket moves through these stages:
- **New** — ticket just created, not yet examined
- **In Transit** — instrument being transported to the shop (for school pickups or shipped instruments)
- **Intake** — instrument received, condition documented
- **Diagnosing** — technician examining the instrument
- **In Transit** — item being transported to the shop (for pickups or shipped items)
- **Intake** — item received, condition documented
- **Diagnosing** — technician examining the item
- **Pending Approval** — estimate provided, waiting for customer OK
- **Approved** — customer authorized the work
- **In Progress** — actively being repaired
- **Pending Parts** — waiting on parts order
- **Ready** — repair complete, awaiting pickup
- **Picked Up** — customer collected the instrument
- **Delivered** — instrument returned via delivery (for school batches)
- **Picked Up** — customer collected the item
- **Delivered** — item returned via delivery (for school batches)
Click the status buttons on the ticket detail page to advance through the workflow. You can also click steps on the progress bar.
@@ -340,7 +340,7 @@ Click the status buttons on the ticket detail page to advance through the workfl
The ticket detail has four tabs:
- **Details** — customer info, instrument, condition, costs. Click **Edit** to modify.
- **Details** — customer info, item, condition, costs. Click **Edit** to modify.
- **Line Items** — labor, parts, flat-rate services, and misc charges. Use the template picker for common repairs.
- **Notes** — running journal of notes. Choose **Internal** (staff only) or **Customer Visible**. You can attach photos to notes.
- **Photos & Docs** — photos organized by repair phase (intake, in progress, completed) plus a documents section for signed approvals, quotes, and receipts.
@@ -372,7 +372,7 @@ Templates are pre-defined common repairs (e.g. "Bow Rehair — Violin — 4/4")
2. Click **New Template**
3. Fill in:
- **Name** — e.g. "Bow Rehair", "String Change", "Valve Overhaul"
- **Instrument Type** — e.g. "Violin", "Guitar", "Trumpet"
- **Item Category** — e.g. "Violin", "Guitar", "Trumpet"
- **Size** — e.g. "4/4", "3/4", "Full"
- **Type** — Labor, Part, Flat Rate, or Misc
- **Default Price** — the customer-facing price
@@ -400,19 +400,19 @@ When creating a ticket or adding line items, type in the **Quick Add from Templa
content: `
# Repair Batches
Batches group multiple repair tickets under one job — typically for schools bringing in many instruments at once.
Batches group multiple repair tickets under one job — typically for schools bringing in many items at once.
## Creating a Batch
1. Go to **Repair Batches** in the sidebar
2. Click **New Batch**
3. Select the school's account
4. Enter contact info, instrument count, and any notes
4. Enter contact info, item count, and any notes
5. Click **Create Batch**
## Adding Tickets to a Batch
When creating new repair tickets, select the batch in the form. Each instrument gets its own ticket linked to the batch.
When creating new repair tickets, select the batch in the form. Each item gets its own ticket linked to the batch.
## Batch Approval
@@ -426,10 +426,10 @@ Only admins can approve or reject batches.
## Batch Status
- **Intake** — receiving instruments
- **Intake** — receiving items
- **In Progress** — work underway
- **Completed** — all repairs done
- **Delivered** — instruments returned to school
- **Delivered** — items returned to customer
## Filtering
@@ -468,7 +468,7 @@ Photos appear inline in the note entry. Click to view full size.
The Photos & Docs tab organizes photos into categories:
- **Intake Photos** — document instrument condition when received
- **Intake Photos** — document item condition when received
- **Work in Progress** — during the repair
- **Completed** — final result after repair
- **Documents** — signed approvals, quotes, receipts (accepts PDFs)