Files
dezky/apps/portal/data/customers.ts
T
Ronni Baslund 0bd4e5498e feat: portal redesign, pricing catalog, partner-staff invites
- portal: new admin/ and partner/ surfaces with full component library
  (AppLauncher, Avatar, Badge, Card, Modal, Tabs, etc.), composables,
  layouts, partner-routing middleware, and supporting server APIs
- pricing: Price schema/module with operator CRUD, pricing.vue catalog UI,
  Subscription extended with cycle/currency/perSeatAmount/seats snapshots
  for stable MRR aggregation
- partner staff: User.partnerId, invite-partner-user DTO and flow,
  /partners/:slug/users endpoints, InvitePartnerUserModal, shared
  dezky-partner-staff Authentik group
- /me: partner-aware endpoint returning user + partner context so portal
  can route between end-user and partner-admin surfaces
- tenant: seats field for portfolio displays and future MRR calculations
- operator: pricing page, signed-out page, useMe/useToast composables,
  ToastStack
2026-05-28 20:00:33 +02:00

101 lines
8.3 KiB
TypeScript

// Partner-admin portfolio fixtures. The partner (NordicMSP) manages 8 customer
// orgs. Numbers seeded to match partner-screens.jsx (the canonical design
// source) line for line: same customer set, same MRR, seats, status, mark.
export type CustomerStatus = 'healthy' | 'attention' | 'past_due' | 'trial' | 'suspended'
export interface CustomerOrg {
id: string
name: string
domain: string
plan: 'starter' | 'business' | 'enterprise'
planLabel: 'Starter' | 'Business' | 'Enterprise'
seats: { used: number; total: number }
health: number
status: CustomerStatus
mrrDkk: number
brandColor: string
industry: string
createdOn: string
since: string
}
export const partner = {
id: 'p-nordicmsp',
name: 'NordicMSP',
domain: 'nordicmsp.dk',
contact: 'Anne Baslund',
email: 'partners@nordicmsp.dk',
marginPct: 20,
customers: 8,
brandColor: '#3F6BFF',
founded: '2024',
}
// Customer set mirrors partner-screens.jsx line 16-25 exactly.
// Health values derived from status + seat utilization (lower for past-due / attention).
export const customers: CustomerOrg[] = [
{ id: 'c-acme', name: 'Acme Workspace', domain: 'acme.dk', plan: 'business', planLabel: 'Business', seats: { used: 24, total: 50 }, health: 88, status: 'healthy', mrrDkk: 4840, brandColor: '#3F6BFF', industry: 'SaaS', createdOn: '2026-02-04', since: 'Feb 2026' },
{ id: 'c-bygherre', name: 'Bygherre Cloud', domain: 'bygherre.dk', plan: 'business', planLabel: 'Business', seats: { used: 12, total: 15 }, health: 38, status: 'past_due', mrrDkk: 2940, brandColor: '#E89A1F', industry: 'Construction', createdOn: '2026-03-12', since: 'Mar 2026' },
{ id: 'c-vester', name: 'Vester Foods', domain: 'vesterfoods.dk', plan: 'starter', planLabel: 'Starter', seats: { used: 8, total: 10 }, health: 82, status: 'healthy', mrrDkk: 980, brandColor: '#5B8C5A', industry: 'Food', createdOn: '2026-04-08', since: 'Apr 2026' },
{ id: 'c-aalborg', name: 'Aalborg Logistik', domain: 'aalborg-log.dk', plan: 'enterprise', planLabel: 'Enterprise', seats: { used: 87, total: 100 }, health: 78, status: 'healthy', mrrDkk: 14500, brandColor: '#0A0A0A', industry: 'Logistics', createdOn: '2025-09-04', since: 'Sep 2025' },
{ id: 'c-norrebro', name: 'Nørrebro Studio', domain: 'nbstudio.dk', plan: 'business', planLabel: 'Business', seats: { used: 6, total: 15 }, health: 68, status: 'trial', mrrDkk: 0, brandColor: '#FF6B4A', industry: 'Creative', createdOn: '2026-05-12', since: '12 May 2026' },
{ id: 'c-vsk', name: 'Vestsjælland Kommune', domain: 'vsk.dk', plan: 'enterprise', planLabel: 'Enterprise', seats: { used: 142, total: 200 }, health: 91, status: 'healthy', mrrDkk: 28400, brandColor: '#5B3F7A', industry: 'Public sector', createdOn: '2024-11-20', since: 'Nov 2024' },
{ id: 'c-broson', name: 'Bro & Søn ApS', domain: 'broson.dk', plan: 'starter', planLabel: 'Starter', seats: { used: 4, total: 10 }, health: 86, status: 'healthy', mrrDkk: 490, brandColor: '#3D3D38', industry: 'Retail', createdOn: '2025-06-15', since: 'Jun 2025' },
{ id: 'c-henriksen', name: 'Henriksen Revision', domain: 'h-revision.dk', plan: 'business', planLabel: 'Business', seats: { used: 18, total: 25 }, health: 58, status: 'attention', mrrDkk: 3600, brandColor: '#B85C38', industry: 'Accounting', createdOn: '2026-01-08', since: 'Jan 2026' },
]
// PARTNER_TEAM mirrors partner-screens.jsx line 27-32.
export const partnerTeam = [
{ id: 'pt-anne', name: 'Anne Baslund', email: 'anne@nordicmsp.dk', role: 'Partner admin', access: 'all', mfa: 'totp', lastSeen: '2 min ago' },
{ id: 'pt-mikkel', name: 'Mikkel Nørgaard', email: 'mikkel@nordicmsp.dk', role: 'Sales', access: 'specific', mfa: 'totp', lastSeen: '12 min ago' },
{ id: 'pt-sofie', name: 'Sofie Lindberg', email: 'sofie@nordicmsp.dk', role: 'Support', access: 'all', mfa: 'webauthn', lastSeen: '1 h ago' },
{ id: 'pt-oliver', name: 'Oliver Schmidt', email: 'oliver@nordicmsp.dk', role: 'Support', access: 'specific', mfa: 'totp', lastSeen: '4 h ago' },
]
// Customer invoices · partner-screens.jsx line 770-776.
export const partnerInvoices = [
{ id: 'INV-0521', customer: 'Acme Workspace', number: 'INV-0521', date: '01 May 2026', amount: 4840, status: 'paid' },
{ id: 'INV-0522', customer: 'Bygherre Cloud', number: 'INV-0522', date: '01 May 2026', amount: 2940, status: 'past_due' },
{ id: 'INV-0523', customer: 'Vester Foods', number: 'INV-0523', date: '01 May 2026', amount: 980, status: 'paid' },
{ id: 'INV-0524', customer: 'Aalborg Logistik', number: 'INV-0524', date: '01 May 2026', amount: 14500, status: 'paid' },
{ id: 'INV-0525', customer: 'Vestsjælland K.', number: 'INV-0525', date: '01 May 2026', amount: 28400, status: 'paid' },
{ id: 'INV-0526', customer: 'Bro & Søn ApS', number: 'INV-0526', date: '01 May 2026', amount: 490, status: 'paid' },
{ id: 'INV-0527', customer: 'Henriksen Revision', number: 'INV-0527', date: '01 May 2026', amount: 3600, status: 'sent' },
]
// 90-day MRR sparkline · matches the synthetic generator at partner-screens.jsx:198.
// Deterministic seeded values (no Math.random calls each render).
export const partnerMrrSparkline = [
38580, 38980, 39520, 40180, 40720, 41080, 41420, 41820, 42100, 42460,
42820, 43240, 43620, 43980, 44320, 44660, 44960, 45280, 45680, 46020,
46300, 46580, 46880, 47220, 47540, 47820, 48160, 48420, 48760, 49080,
49420, 49680, 50020, 50360, 50640, 50920, 51180, 51460, 51720, 51980,
52220, 52480, 52720, 52960, 53180, 53420, 53620, 53860, 54080, 54260,
54440, 54620, 54780, 54940, 55080, 55180, 55280, 55360, 55440, 55510,
55570, 55610, 55650, 55670, 55690, 55700, 55710, 55720, 55730, 55740,
55745, 55748, 55750, 55750, 55750, 55750, 55750, 55750, 55750, 55750,
55750, 55750, 55750, 55750, 55750, 55750, 55750, 55750, 55750, 55750,
]
// Recent partner activity · partner-screens.jsx line 332-336.
export const partnerActivity = [
{ id: 'pa-1', when: '14:02', cust: 'Acme Workspace', who: 'Anne Baslund', action: 'invited 3 users', tone: 'info' as const },
{ id: 'pa-2', when: '12:18', cust: 'Bygherre Cloud', who: 'system', action: 'invoice marked past-due', tone: 'bad' as const },
{ id: 'pa-3', when: '11:44', cust: 'Aalborg Logistik', who: 'Sofie Lindberg', action: 'upgraded to Enterprise', tone: 'ok' as const },
{ id: 'pa-4', when: '10:08', cust: 'Nørrebro Studio', who: 'NordicMSP', action: 'created new customer org', tone: 'info' as const },
{ id: 'pa-5', when: '09:34', cust: 'Henriksen Revision', who: 'system', action: 'DNS health alert · SPF', tone: 'warn' as const },
]
// Partner audit · platform-partner-depth.jsx line 8-17.
export const partnerAudit = [
{ id: 'pa1', when: '15:02', actor: 'Anne Baslund', customer: 'Acme Workspace', action: 'user.invited', target: 'magnus@acme.dk', tone: 'info' as const },
{ id: 'pa2', when: '13:48', actor: 'Mikkel Nørgaard', customer: 'Bygherre Cloud', action: 'billing.followup_sent', target: 'INV-0522 · past-due', tone: 'warn' as const },
{ id: 'pa3', when: '11:21', actor: 'Anne Baslund', customer: 'Acme Workspace', action: 'partner.entered_customer', target: 'reason: Q2 review', tone: 'info' as const },
{ id: 'pa4', when: '10:08', actor: 'Sofie Lindberg', customer: 'Aalborg Logistik', action: 'tenant.plan_changed', target: 'Business → Enterprise', tone: 'ok' as const },
{ id: 'pa5', when: '09:44', actor: 'Anne Baslund', customer: '—', action: 'partner.customer_created', target: 'Lyngby Bilcenter', tone: 'info' as const },
{ id: 'pa6', when: 'Yest', actor: 'Sofie Lindberg', customer: 'Henriksen Revision', action: 'support.ticket_created', target: 'DNS SPF missing', tone: 'warn' as const },
{ id: 'pa7', when: 'Yest', actor: 'Anne Baslund', customer: 'Acme Workspace', action: 'branding.published', target: 'accent #3F6BFF', tone: 'info' as const },
{ id: 'pa8', when: '2 d', actor: 'Mikkel Nørgaard', customer: 'Vester Foods', action: 'tenant.exit', target: 'session: 14 min', tone: 'info' as const },
]