diff --git a/packages/admin/.tanstack/tmp/57794a11-f0abc49f852aebe0077e70caa4e1d479 b/packages/admin/.tanstack/tmp/57794a11-f0abc49f852aebe0077e70caa4e1d479 new file mode 100644 index 0000000..ac5f67e --- /dev/null +++ b/packages/admin/.tanstack/tmp/57794a11-f0abc49f852aebe0077e70caa4e1d479 @@ -0,0 +1,323 @@ +/* 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() diff --git a/packages/admin/src/api/members.ts b/packages/admin/src/api/members.ts index 0626ce5..be1847e 100644 --- a/packages/admin/src/api/members.ts +++ b/packages/admin/src/api/members.ts @@ -3,9 +3,15 @@ import { api } from '@/lib/api-client' import type { Member } from '@/types/account' import type { PaginatedResponse, PaginationInput } from '@forte/shared/schemas' +interface MemberWithAccount extends Member { + accountName: string | null +} + export const memberKeys = { all: (accountId: string) => ['accounts', accountId, 'members'] as const, list: (accountId: string, params: PaginationInput) => [...memberKeys.all(accountId), params] as const, + globalAll: ['members'] as const, + globalList: (params: PaginationInput) => ['members', 'list', params] as const, } export function memberListOptions(accountId: string, params: PaginationInput) { @@ -15,6 +21,13 @@ export function memberListOptions(accountId: string, params: PaginationInput) { }) } +export function globalMemberListOptions(params: PaginationInput) { + return queryOptions({ + queryKey: memberKeys.globalList(params), + queryFn: () => api.get>('/v1/members', params), + }) +} + export const memberMutations = { create: (accountId: string, data: Record) => api.post(`/v1/accounts/${accountId}/members`, data), @@ -24,4 +37,9 @@ export const memberMutations = { delete: (id: string) => api.del(`/v1/members/${id}`), + + move: (id: string, accountId?: string) => + api.post(`/v1/members/${id}/move`, accountId ? { accountId } : {}), } + +export type { MemberWithAccount } diff --git a/packages/admin/src/app.css b/packages/admin/src/app.css index c483157..3d73043 100644 --- a/packages/admin/src/app.css +++ b/packages/admin/src/app.css @@ -2,31 +2,26 @@ @variant dark (&:is(.dark, .dark *)); -@theme { - /* Defaults — overridden at runtime by theme store */ - --color-background: hsl(210 20% 97%); - --color-foreground: hsl(222 47% 11%); - --color-card: hsl(0 0% 100%); - --color-card-foreground: hsl(222 47% 11%); - --color-popover: hsl(0 0% 100%); - --color-popover-foreground: hsl(222 47% 11%); - --color-primary: hsl(215 25% 27%); - --color-primary-foreground: hsl(210 40% 98%); - --color-secondary: hsl(210 40% 94%); - --color-secondary-foreground: hsl(222 47% 11%); - --color-muted: hsl(210 40% 94%); - --color-muted-foreground: hsl(215 16% 47%); - --color-accent: hsl(210 40% 94%); - --color-accent-foreground: hsl(222 47% 11%); - --color-destructive: hsl(0 72% 51%); - --color-destructive-foreground: hsl(210 40% 98%); - --color-border: hsl(214 32% 89%); - --color-input: hsl(214 32% 89%); - --color-ring: hsl(215 25% 27%); - --radius: 0.5rem; -} - @theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-primary: var(--sidebar-primary); @@ -35,9 +30,29 @@ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); + --radius: 0.5rem; } :root { + --background: hsl(210 20% 97%); + --foreground: hsl(222 47% 11%); + --card: hsl(0 0% 100%); + --card-foreground: hsl(222 47% 11%); + --popover: hsl(0 0% 100%); + --popover-foreground: hsl(222 47% 11%); + --primary: hsl(215 25% 27%); + --primary-foreground: hsl(210 40% 98%); + --secondary: hsl(210 40% 94%); + --secondary-foreground: hsl(222 47% 11%); + --muted: hsl(210 40% 94%); + --muted-foreground: hsl(215 16% 47%); + --accent: hsl(210 40% 94%); + --accent-foreground: hsl(222 47% 11%); + --destructive: hsl(0 72% 51%); + --destructive-foreground: hsl(210 40% 98%); + --border: hsl(214 32% 89%); + --input: hsl(214 32% 89%); + --ring: hsl(215 25% 27%); --sidebar: hsl(210 25% 95%); --sidebar-foreground: hsl(215 16% 47%); --sidebar-primary: hsl(215 25% 27%); @@ -55,6 +70,8 @@ body { system-ui, -apple-system, sans-serif; + background-color: var(--background); + color: var(--foreground); } .login-input { diff --git a/packages/admin/src/components/accounts/account-form.tsx b/packages/admin/src/components/accounts/account-form.tsx index 72f4132..a9faca1 100644 --- a/packages/admin/src/components/accounts/account-form.tsx +++ b/packages/admin/src/components/accounts/account-form.tsx @@ -1,29 +1,33 @@ import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { AccountCreateSchema } from '@forte/shared/schemas' -import type { AccountCreateInput } from '@forte/shared/schemas' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Separator } from '@/components/ui/separator' import type { Account } from '@/types/account' interface AccountFormProps { defaultValues?: Partial - onSubmit: (data: AccountCreateInput) => void + onSubmit: (data: Record) => void loading?: boolean + includeFirstMember?: boolean } -export function AccountForm({ defaultValues, onSubmit, loading }: AccountFormProps) { +export function AccountForm({ defaultValues, onSubmit, loading, includeFirstMember }: AccountFormProps) { + const optionalNameSchema = AccountCreateSchema.extend({ name: AccountCreateSchema.shape.name.optional() }) + const schema = includeFirstMember ? optionalNameSchema : AccountCreateSchema + const { register, handleSubmit, setValue, watch, formState: { errors }, - } = useForm({ - resolver: zodResolver(AccountCreateSchema), + } = useForm({ + resolver: zodResolver(schema), defaultValues: { name: defaultValues?.name ?? '', email: defaultValues?.email ?? undefined, @@ -36,12 +40,31 @@ export function AccountForm({ defaultValues, onSubmit, loading }: AccountFormPro const billingMode = watch('billingMode') + function handleFormSubmit(accountData: Record) { + if (includeFirstMember) { + // Grab member fields from the native form + const form = document.getElementById('account-form') as HTMLFormElement + const formData = new FormData(form) + onSubmit({ + ...accountData, + memberFirstName: formData.get('memberFirstName') || undefined, + memberLastName: formData.get('memberLastName') || undefined, + memberEmail: formData.get('memberEmail') || undefined, + memberPhone: formData.get('memberPhone') || undefined, + memberDateOfBirth: formData.get('memberDateOfBirth') || undefined, + memberIsMinor: formData.get('memberIsMinor') === 'on' || undefined, + }) + } else { + onSubmit(accountData) + } + } + return ( -
+
- - - {errors.name &&

{errors.name.message}

} + + + {errors.name && !includeFirstMember &&

{String(errors.name.message)}

}
@@ -88,6 +111,41 @@ export function AccountForm({ defaultValues, onSubmit, loading }: AccountFormPro