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
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// 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 },
|
||||
]
|
||||
@@ -0,0 +1,108 @@
|
||||
// Fixtures for the end-user dashboard, profile, devices, security, etc.
|
||||
|
||||
export const currentUser = {
|
||||
name: 'Anne Hansen',
|
||||
email: 'anne@baslund.dk',
|
||||
role: 'Customer admin',
|
||||
title: 'CEO',
|
||||
department: 'Leadership',
|
||||
manager: '—',
|
||||
timezone: 'Europe/Copenhagen',
|
||||
language: 'da-DK',
|
||||
joined: '2024-09-12',
|
||||
}
|
||||
|
||||
// App tiles for the end-user dashboard. Source design has 4 fixed tiles with
|
||||
// a short monospace badge line below the name (no separate hrefs row).
|
||||
export const appTiles = [
|
||||
{ key: 'mail', name: 'Mail', badge: '12 unread' },
|
||||
{ key: 'drev', name: 'Drev', badge: '5 shared' },
|
||||
{ key: 'moder', name: 'Møder', badge: '3 today' },
|
||||
{ key: 'chat', name: 'Chat', badge: '2 mentions' },
|
||||
]
|
||||
|
||||
// Single-time meeting rows. Source design uses a single time + a relative
|
||||
// "in X" countdown + a "with" line.
|
||||
export const todayAgenda = [
|
||||
{ id: 't-1', time: '10:30', title: 'Sprint planning', with: 'Engineering · 6 people', in: '14 min' },
|
||||
{ id: 't-2', time: '13:00', title: 'Customer demo · Novo', with: 'Frederik + Anne', in: '2 h 44 min' },
|
||||
{ id: 't-3', time: '15:30', title: '1:1 with Mikkel', with: 'Mikkel Nørgaard', in: '5 h 14 min' },
|
||||
]
|
||||
|
||||
export const recentFiles = [
|
||||
{ id: 'f-1', name: 'Q3 board deck.key', path: 'Drev · /Board', updated: '12 min ago', size: '24 MB' },
|
||||
{ id: 'f-2', name: 'Pricing v3.xlsx', path: 'Drev · /Finance', updated: '1 h ago', size: '482 KB' },
|
||||
{ id: 'f-3', name: 'Brand guide.pdf', path: 'Drev · /Brand', updated: '3 h ago', size: '8.1 MB' },
|
||||
{ id: 'f-4', name: 'Customer interviews.docx', path: 'Drev · /Research', updated: 'Yesterday', size: '120 KB' },
|
||||
{ id: 'f-5', name: 'Roadmap 2026.fig', path: 'Drev · /Design', updated: '2 d ago', size: '34 MB' },
|
||||
]
|
||||
|
||||
// 6 hardcoded pending items — source design's exact list. Tone determines
|
||||
// the icon-tint + whether the CTA is primary (only 'bad' gets primary).
|
||||
export const needsAttention = [
|
||||
{ id: 't1', icon: 'shield', tone: 'bad', title: 'Complete MFA enrolment', hint: 'overdue · required by 1 Jun', cta: 'Set up', target: 'security' },
|
||||
{ id: 't2', icon: 'chat', tone: 'info', title: 'Sofie mentioned you in #design', hint: '2 h ago', cta: 'Reply', target: 'chat' },
|
||||
{ id: 't3', icon: 'folder', tone: 'info', title: 'Frederik shared "Q3 forecast.xlsx"', hint: 'yesterday', cta: 'Review', target: 'file' },
|
||||
{ id: 't4', icon: 'card', tone: 'warn', title: 'Approve expense report · 1.840 DKK', hint: '3 days', cta: 'Approve', target: 'expense' },
|
||||
{ id: 't5', icon: 'users', tone: 'info', title: 'Schedule 1:1 with Mikkel', hint: 'overdue by 5 days', cta: 'Schedule', target: 'meeting' },
|
||||
{ id: 't6', icon: 'file', tone: 'info', title: 'Take Q2 customer feedback survey', hint: 'open until Friday', cta: 'Open', target: 'survey' },
|
||||
] as const
|
||||
|
||||
// Devices match source `DEVICES` from platform-enduser.jsx · grouped into
|
||||
// laptop / phone / tablet ("desktop" in source maps to our `laptop` kind).
|
||||
export const devices = [
|
||||
{ id: 'd1', label: 'MacBook Pro · 14"', kind: 'laptop', os: 'macOS 14.4', app: 'Chrome 132', location: 'Copenhagen, DK', ip: '92.43.118.4', lastActive: 'now', current: true, trusted: false, stale: false },
|
||||
{ id: 'd2', label: 'iPhone 15 Pro', kind: 'phone', os: 'iOS 18.1', app: 'dezky Mail · 2.4', location: 'Copenhagen, DK', ip: '92.43.118.4', lastActive: '14 min ago', current: false, trusted: false, stale: false },
|
||||
{ id: 'd3', label: 'iPhone 15 Pro', kind: 'phone', os: 'iOS 18.1', app: 'dezky Drev · 2.1', location: 'Copenhagen, DK', ip: '92.43.118.4', lastActive: '14 min ago', current: false, trusted: false, stale: false },
|
||||
{ id: 'd4', label: 'MacBook Air · 13"', kind: 'laptop', os: 'macOS 14.4', app: 'Safari 17.4', location: 'Aarhus, DK', ip: '78.110.4.92', lastActive: '2 d ago', current: false, trusted: false, stale: false },
|
||||
{ id: 'd5', label: 'iPad Pro · 11"', kind: 'tablet', os: 'iPadOS 18', app: 'dezky · web', location: 'Copenhagen, DK', ip: '92.43.118.4', lastActive: '5 d ago', current: false, trusted: false, stale: false },
|
||||
{ id: 'd6', label: 'Linux workstation', kind: 'laptop', os: 'Ubuntu 24.04', app: 'Firefox 134', location: 'Copenhagen, DK', ip: '10.0.4.18', lastActive: '11 d ago', current: false, trusted: false, stale: true },
|
||||
]
|
||||
|
||||
// MFA methods mirror source `MFA_METHODS`. Source has TouchID as primary
|
||||
// (first webauthn), YubiKey, and 1Password TOTP.
|
||||
export const mfaMethods = [
|
||||
{ id: 'm1', kind: 'webauthn' as const, label: 'MacBook Pro · Touch ID', enrolledOn: '14 Jan 2026', lastUsed: '2 min ago', primary: true },
|
||||
{ id: 'm2', kind: 'webauthn' as const, label: 'YubiKey 5C · work', enrolledOn: '14 Jan 2026', lastUsed: '3 d ago', primary: false },
|
||||
{ id: 'm3', kind: 'totp' as const, label: '1Password', enrolledOn: '02 Feb 2026', lastUsed: '4 d ago', primary: false },
|
||||
]
|
||||
|
||||
export const recoveryCodes = [
|
||||
'whkz-86p4', 'jt2m-c98n', 'q4dx-r58y', 'b1nv-7gpx',
|
||||
'fm8w-22qr', 'k5xt-pa9c', 'd91j-8mwz', 'rv6t-3hyn',
|
||||
'snk2-9b8d', '7zfp-q4mv',
|
||||
]
|
||||
|
||||
// Sign-in history mirrors source `SIGNIN_HISTORY` — includes the failed
|
||||
// attempt from 203.0.113.4 used for the warning callout.
|
||||
export const signInHistory = [
|
||||
{ id: 'si-1', when: '14:02 today', ip: '92.43.118.4', location: 'Copenhagen, DK', ua: 'Chrome 132 · macOS', method: 'webauthn', result: 'ok' },
|
||||
{ id: 'si-2', when: '11:48 today', ip: '92.43.118.4', location: 'Copenhagen, DK', ua: 'dezky Mail · iOS', method: 'session', result: 'ok' },
|
||||
{ id: 'si-3', when: '09:21 today', ip: '92.43.118.4', location: 'Copenhagen, DK', ua: 'Chrome 132 · macOS', method: 'webauthn', result: 'ok' },
|
||||
{ id: 'si-4', when: 'Yesterday 22:14', ip: '78.110.4.92', location: 'Aarhus, DK', ua: 'Safari 17.4 · macOS', method: 'totp', result: 'ok' },
|
||||
{ id: 'si-5', when: 'Yesterday 18:02', ip: '203.0.113.4', location: 'Unknown', ua: 'Chrome 132 · Windows', method: 'password', result: 'failed', reason: 'Wrong password · 3 attempts' },
|
||||
{ id: 'si-6', when: '2 d ago 14:22', ip: '78.110.4.92', location: 'Aarhus, DK', ua: 'Safari 17.4 · macOS', method: 'webauthn', result: 'ok' },
|
||||
{ id: 'si-7', when: '4 d ago 09:08', ip: '92.43.118.4', location: 'Copenhagen, DK', ua: 'Firefox 134 · Linux', method: 'totp', result: 'ok' },
|
||||
]
|
||||
|
||||
// KB articles mirror source `KB_ARTICLES` exactly. `read` is the read-time
|
||||
// string ("4 min"), `popular` flags the items rendered in the Popular row.
|
||||
export const helpArticles = [
|
||||
{ id: 'kb1', title: 'Setting up MFA for your workspace', category: 'Getting started', read: '4 min', popular: true },
|
||||
{ id: 'kb2', title: 'Migrating from Microsoft 365 to Dezky', category: 'Migration', read: '12 min', popular: true },
|
||||
{ id: 'kb3', title: 'Configuring SAML SSO for external apps', category: 'Identity', read: '8 min', popular: false },
|
||||
{ id: 'kb4', title: 'Custom domain (CNAME) setup', category: 'Branding', read: '6 min', popular: false },
|
||||
{ id: 'kb5', title: 'Understanding storage quotas', category: 'Drev', read: '5 min', popular: false },
|
||||
{ id: 'kb6', title: 'OIOUBL invoicing for Danish customers', category: 'Billing', read: '7 min', popular: false },
|
||||
{ id: 'kb7', title: 'API tokens and webhooks', category: 'Developers', read: '10 min', popular: false },
|
||||
{ id: 'kb8', title: 'GDPR data export requests', category: 'Compliance', read: '5 min', popular: false },
|
||||
]
|
||||
|
||||
// Tickets mirror source `TICKETS`. Status uses the source's three-bucket
|
||||
// vocabulary ("awaiting customer" / "in progress" / "resolved").
|
||||
export const myTickets = [
|
||||
{ id: 'TKT-2841', title: 'Cannot share folder externally', status: 'awaiting customer', severity: 'P3', age: '2 d', last: '2 h ago', updates: 4 },
|
||||
{ id: 'TKT-2832', title: 'Mobile app missing meeting recordings', status: 'in progress', severity: 'P3', age: '5 d', last: '1 d ago', updates: 8 },
|
||||
{ id: 'TKT-2701', title: 'How do I configure SAML for Notion?', status: 'resolved', severity: 'P3', age: '12 d', last: '8 d ago', updates: 6 },
|
||||
{ id: 'TKT-2650', title: 'Bulk export users to Active Directory', status: 'resolved', severity: 'P3', age: '18 d', last: '14 d ago', updates: 11 },
|
||||
]
|
||||
@@ -0,0 +1,63 @@
|
||||
// Mock notifications shown in the topbar drawer. Tones map to icon tints.
|
||||
|
||||
export type NotifTone = 'info' | 'ok' | 'warn' | 'bad'
|
||||
|
||||
export interface PortalNotification {
|
||||
id: string
|
||||
tone: NotifTone
|
||||
title: string
|
||||
body: string
|
||||
when: string
|
||||
read: boolean
|
||||
}
|
||||
|
||||
export const notifications: PortalNotification[] = [
|
||||
{
|
||||
id: 'n-1',
|
||||
tone: 'warn',
|
||||
title: 'MFA reminder',
|
||||
body: 'Set up TOTP or a hardware key before 31 May.',
|
||||
when: '12 min ago',
|
||||
read: false,
|
||||
},
|
||||
{
|
||||
id: 'n-2',
|
||||
tone: 'info',
|
||||
title: 'Sofie mentioned you',
|
||||
body: 'In #design — “Did you see the new Stat card?”',
|
||||
when: '38 min ago',
|
||||
read: false,
|
||||
},
|
||||
{
|
||||
id: 'n-3',
|
||||
tone: 'ok',
|
||||
title: 'Invoice paid',
|
||||
body: 'May invoice 2026-001247 · 1.940 DKK · MobilePay',
|
||||
when: '2 hours ago',
|
||||
read: false,
|
||||
},
|
||||
{
|
||||
id: 'n-4',
|
||||
tone: 'info',
|
||||
title: 'Frederik shared a file',
|
||||
body: 'Q3 forecast.xlsx · in Drev / Finance',
|
||||
when: '4 hours ago',
|
||||
read: true,
|
||||
},
|
||||
{
|
||||
id: 'n-5',
|
||||
tone: 'warn',
|
||||
title: 'DMARC record missing',
|
||||
body: 'baslund.dk · add the recommended policy in Domains',
|
||||
when: 'Yesterday',
|
||||
read: true,
|
||||
},
|
||||
{
|
||||
id: 'n-6',
|
||||
tone: 'info',
|
||||
title: 'Mikkel updated retention',
|
||||
body: 'Mail retention policy changed to 7 years',
|
||||
when: '2 days ago',
|
||||
read: true,
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,256 @@
|
||||
// The "current workspace" for prototype purposes. Replaced by platform-api
|
||||
// lookup in production. Holds the brand identity that the BrandingScreen
|
||||
// lives in front of.
|
||||
|
||||
export interface Workspace {
|
||||
id: string
|
||||
name: string
|
||||
productName: string
|
||||
domain: string
|
||||
plan: 'starter' | 'business' | 'enterprise'
|
||||
seats: { used: number; total: number }
|
||||
storage: { usedGb: number; totalGb: number }
|
||||
mailFlow: { deliveredPct: number; trend: 'up' | 'down' | 'flat' }
|
||||
monthlySpendDkk: number
|
||||
status: 'active' | 'trial' | 'past_due' | 'suspended'
|
||||
}
|
||||
|
||||
export const workspace: Workspace = {
|
||||
id: 'ws-baslund',
|
||||
name: 'Baslund',
|
||||
productName: 'Baslund Workspace',
|
||||
domain: 'baslund.dk',
|
||||
plan: 'business',
|
||||
seats: { used: 11, total: 25 },
|
||||
storage: { usedGb: 142, totalGb: 500 },
|
||||
mailFlow: { deliveredPct: 99.4, trend: 'up' },
|
||||
monthlySpendDkk: 1940,
|
||||
status: 'active',
|
||||
}
|
||||
|
||||
export const sampleUsers = [
|
||||
{ id: 'u-1', name: 'Anne Hansen', email: 'anne@baslund.dk', role: 'owner', groups: ['Leadership', 'Finance'], mfa: 'totp', lastLogin: '2 min ago', status: 'active', storageMb: 8200, license: 'business' },
|
||||
{ id: 'u-2', name: 'Mikkel Sørensen', email: 'mikkel@baslund.dk', role: 'admin', groups: ['Engineering'], mfa: 'webauthn', lastLogin: '14 min ago', status: 'active', storageMb: 14210, license: 'business' },
|
||||
{ id: 'u-3', name: 'Sofie Lund', email: 'sofie@baslund.dk', role: 'admin', groups: ['Design'], mfa: 'totp', lastLogin: '1 hour ago', status: 'active', storageMb: 6804, license: 'business' },
|
||||
{ id: 'u-4', name: 'Frederik Holm', email: 'frederik@baslund.dk', role: 'user', groups: ['Finance'], mfa: 'totp', lastLogin: '3 hours ago', status: 'active', storageMb: 2401, license: 'business' },
|
||||
{ id: 'u-5', name: 'Caroline Bjerg', email: 'caroline@baslund.dk', role: 'user', groups: ['Sales'], mfa: 'none', lastLogin: 'Yesterday', status: 'active', storageMb: 1880, license: 'business' },
|
||||
{ id: 'u-6', name: 'Johan Olesen', email: 'johan@baslund.dk', role: 'user', groups: ['Engineering'], mfa: 'webauthn', lastLogin: '2 days ago', status: 'active', storageMb: 4250, license: 'business' },
|
||||
{ id: 'u-7', name: 'Maria Petersen', email: 'maria@baslund.dk', role: 'user', groups: ['Design'], mfa: 'totp', lastLogin: '5 days ago', status: 'active', storageMb: 3120, license: 'business' },
|
||||
{ id: 'u-8', name: 'Henrik Schmidt', email: 'henrik@baslund.dk', role: 'user', groups: ['Engineering'], mfa: 'totp', lastLogin: '1 week ago', status: 'active', storageMb: 9540, license: 'business' },
|
||||
{ id: 'u-9', name: 'Trine Madsen', email: 'trine@baslund.dk', role: 'user', groups: ['Sales'], mfa: 'none', lastLogin: 'never', status: 'invited', storageMb: 0, license: 'business' },
|
||||
{ id: 'u-10', name: 'Lars Engelbrecht', email: 'lars@baslund.dk', role: 'user', groups: ['Engineering'], mfa: 'totp', lastLogin: '12 days ago', status: 'suspended', storageMb: 5102, license: 'business' },
|
||||
{ id: 'u-11', name: 'Bo Christensen', email: 'bo@baslund.dk', role: 'user', groups: ['Sales'], mfa: 'totp', lastLogin: '3 hours ago', status: 'active', storageMb: 1242, license: 'starter' },
|
||||
]
|
||||
|
||||
export const sampleGroups = [
|
||||
{ id: 'g-1', name: 'Leadership', description: 'Owners + admins', members: 3, owner: 'Anne Hansen', resources: ['leadership@', 'Drev/Leadership'] },
|
||||
{ id: 'g-2', name: 'Engineering', description: 'Backend, frontend, infrastructure', members: 4, owner: 'Mikkel Sørensen', resources: ['eng@', 'Drev/Engineering', '#eng'] },
|
||||
{ id: 'g-3', name: 'Design', description: 'Brand, product, marketing visuals', members: 2, owner: 'Sofie Lund', resources: ['design@', 'Drev/Design', '#design'] },
|
||||
{ id: 'g-4', name: 'Finance', description: 'Bookkeeping, AP/AR, payroll', members: 2, owner: 'Frederik Holm', resources: ['finance@', 'Drev/Finance'] },
|
||||
{ id: 'g-5', name: 'Sales', description: 'Outbound + customer success', members: 3, owner: 'Bo Christensen', resources: ['sales@', 'Drev/Sales', '#sales'] },
|
||||
]
|
||||
|
||||
export const sampleDomains = [
|
||||
{
|
||||
id: 'd-1',
|
||||
domain: 'baslund.dk',
|
||||
primary: true,
|
||||
status: 'partial',
|
||||
records: [
|
||||
{ type: 'MX', status: 'ok', value: '10 mail.dezky.com' },
|
||||
{ type: 'SPF', status: 'warn', value: 'v=spf1 ~all', expected: 'v=spf1 include:_spf.dezky.com ~all' },
|
||||
{ type: 'DKIM', status: 'ok', value: 'k=rsa; p=MIGfMA0GCSq...' },
|
||||
{ type: 'DMARC', status: 'bad', value: '— not found —', expected: 'v=DMARC1; p=quarantine; rua=mailto:dmarc@baslund.dk' },
|
||||
],
|
||||
addedOn: '2025-12-04',
|
||||
},
|
||||
{
|
||||
id: 'd-2',
|
||||
domain: 'baslund.shop',
|
||||
primary: false,
|
||||
status: 'healthy',
|
||||
records: [
|
||||
{ type: 'MX', status: 'ok', value: '10 mail.dezky.com' },
|
||||
{ type: 'SPF', status: 'ok', value: 'v=spf1 include:_spf.dezky.com ~all' },
|
||||
{ type: 'DKIM', status: 'ok', value: 'k=rsa; p=MIGfMA0G...' },
|
||||
{ type: 'DMARC', status: 'ok', value: 'v=DMARC1; p=quarantine' },
|
||||
],
|
||||
addedOn: '2026-02-11',
|
||||
},
|
||||
{
|
||||
id: 'd-3',
|
||||
domain: 'baslund.io',
|
||||
primary: false,
|
||||
status: 'verifying',
|
||||
records: [
|
||||
{ type: 'TXT', status: 'warn', value: 'dezky-site-verification=…', hint: 'Awaiting propagation · ~10 min remaining' },
|
||||
],
|
||||
addedOn: '2026-05-22',
|
||||
},
|
||||
]
|
||||
|
||||
export const sampleInvoices = [
|
||||
{ id: 'inv-2026-001247', date: '2026-05-01', period: '2026-05', amount: 1940, status: 'paid', method: 'MobilePay' },
|
||||
{ id: 'inv-2026-001112', date: '2026-04-01', period: '2026-04', amount: 1940, status: 'paid', method: 'MobilePay' },
|
||||
{ id: 'inv-2026-000984', date: '2026-03-01', period: '2026-03', amount: 1940, status: 'paid', method: 'MobilePay' },
|
||||
{ id: 'inv-2026-000855', date: '2026-02-01', period: '2026-02', amount: 1940, status: 'paid', method: 'MobilePay' },
|
||||
{ id: 'inv-2026-000721', date: '2026-01-01', period: '2026-01', amount: 1820, status: 'paid', method: 'Card · VISA 4242' },
|
||||
]
|
||||
|
||||
// Audit events — strict port of project/platform-screens.jsx SAMPLE_AUDIT
|
||||
// (line 29). Same 8 rows, same wording, same tones. The AdminDashboard slices
|
||||
// the first 6, the SecurityScreen audit log uses all of them.
|
||||
export const sampleAudit = [
|
||||
{ id: 'a1', when: '14:02:11', actor: 'Anne Baslund', action: 'user.invited', target: 'magnus@dezky.com', ip: '92.43.118.4', tone: 'info' as const },
|
||||
{ id: 'a2', when: '13:48:02', actor: 'Mikkel Nørgaard', action: 'billing.plan_changed', target: 'Team → Business', ip: '92.43.118.4', tone: 'info' as const },
|
||||
{ id: 'a3', when: '13:21:55', actor: 'system', action: 'domain.dns_check', target: 'baslund.dk', ip: '—', tone: 'warn' as const },
|
||||
{ id: 'a4', when: '12:09:30', actor: 'Sofie Lindberg', action: 'user.suspended', target: 'tina@dezky.com', ip: '78.110.4.92', tone: 'warn' as const },
|
||||
{ id: 'a5', when: '11:44:00', actor: 'Anne Baslund', action: 'mfa.policy_updated', target: 'require for admins', ip: '92.43.118.4', tone: 'info' as const },
|
||||
{ id: 'a6', when: '10:12:08', actor: 'Lars Holm', action: 'session.terminated', target: 'iPad — Safari', ip: '78.110.4.10', tone: 'info' as const },
|
||||
{ id: 'a7', when: '09:55:41', actor: 'system', action: 'auth.failed_login', target: 'oliver@dezky.com', ip: '203.0.113.4', tone: 'bad' as const },
|
||||
{ id: 'a8', when: '09:30:00', actor: 'Anne Baslund', action: 'branding.color_set', target: '#D4FF3A', ip: '92.43.118.4', tone: 'info' as const },
|
||||
]
|
||||
|
||||
// Org-wide mail aliases — strict port of platform-admin.jsx ORG_ALIASES (line 9)
|
||||
export const orgAliases = [
|
||||
{ alias: 'info@dezky.com', dest: 'Distribution · Everyone', active: true, created: '14 Jan 2026' },
|
||||
{ alias: 'sales@dezky.com', dest: 'frederik@dezky.com', active: true, created: '22 Jan 2026' },
|
||||
{ alias: 'support@dezky.com', dest: 'sofie@dezky.com', active: true, created: '22 Jan 2026' },
|
||||
{ alias: 'no-reply@dezky.com', dest: '(discard)', active: true, created: '14 Jan 2026' },
|
||||
{ alias: 'careers@dezky.com', dest: 'anne@dezky.com', active: true, created: '04 Mar 2026' },
|
||||
{ alias: 'legal@dezky.com', dest: 'mikkel@dezky.com', active: false, created: '12 Apr 2026' },
|
||||
]
|
||||
|
||||
// Forwarding rules — strict port of platform-admin.jsx FORWARDING_RULES (line 18)
|
||||
export const forwardingRules = [
|
||||
{ name: 'Out-of-hours to on-call', match: 'support@dezky.com · 18:00–08:00 CET', fwd: 'oncall@dezky.com', enabled: true },
|
||||
{ name: 'Vendor invoices', match: 'subject: "invoice" · from: *@vendors', fwd: 'finance@dezky.com', enabled: true },
|
||||
{ name: 'Legal threads', match: 'cc: legal@*', fwd: 'mikkel@dezky.com', enabled: false },
|
||||
]
|
||||
|
||||
// Distribution lists — strict port of platform-admin.jsx DISTRIBUTION_LISTS (line 24)
|
||||
export const distributionLists = [
|
||||
{ name: 'Everyone', alias: 'everyone@dezky.com', members: 11, owner: 'Anne Baslund', moderation: 'open' as const, external: false },
|
||||
{ name: 'Engineering', alias: 'eng@dezky.com', members: 4, owner: 'Anne Baslund', moderation: 'closed' as const, external: false },
|
||||
{ name: 'Leadership', alias: 'leads@dezky.com', members: 3, owner: 'Anne Baslund', moderation: 'closed' as const, external: false },
|
||||
{ name: 'Customers VIP', alias: 'vip-customers@dezky.com', members: 0, owner: 'Frederik Madsen', moderation: 'closed' as const, external: true },
|
||||
]
|
||||
|
||||
// Anti-spam content filters — strict port of platform-admin.jsx ANTI_SPAM_FILTERS (line 31)
|
||||
export const antiSpamFilters = [
|
||||
{ name: 'Block executable attachments', match: 'attachment ext in (.exe, .scr, .bat, .cmd)', action: 'reject' as const, enabled: true },
|
||||
{ name: 'Quarantine cryptocurrency mail', match: 'body contains "wallet address"', action: 'quarantine' as const, enabled: true },
|
||||
{ name: 'Tag external mail', match: 'from outside @dezky.com', action: 'add tag' as const, enabled: true },
|
||||
]
|
||||
|
||||
// Full groups list — strict port of platform-admin.jsx GROUPS_FULL (line 64)
|
||||
export const groupsFull = [
|
||||
{ id: 'g_eng', name: 'Engineering', alias: 'engineering@dezky.com', members: 4, description: 'Product engineering team', created: '14 Jan 2026', owner: 'Anne Baslund' },
|
||||
{ id: 'g_des', name: 'Design', alias: 'design@dezky.com', members: 2, description: 'Brand and product designers', created: '02 Feb 2026', owner: 'Sofie Lindberg' },
|
||||
{ id: 'g_ops', name: 'Operations', alias: 'ops@dezky.com', members: 2, description: 'Operations and on-call', created: '14 Jan 2026', owner: 'Mikkel Nørgaard' },
|
||||
{ id: 'g_fin', name: 'Finance', alias: 'finance@dezky.com', members: 2, description: 'Finance and accounting', created: '22 Jan 2026', owner: 'Astrid Vinther' },
|
||||
{ id: 'g_sales', name: 'Sales', alias: 'sales@dezky.com', members: 2, description: 'Customer sales', created: '14 Jan 2026', owner: 'Frederik Madsen' },
|
||||
{ id: 'g_all', name: 'All-hands', alias: 'everyone@dezky.com', members: 11, description: 'Everyone in the workspace', created: '14 Jan 2026', owner: 'Anne Baslund' },
|
||||
]
|
||||
|
||||
// Source-fidelity users (project/platform-screens.jsx SAMPLE_USERS line 8) —
|
||||
// flat shape used by UsersScreen, BillingScreen seats math and StorageScreen
|
||||
// "top users" list. Kept in sync with the visual mock — these have `last`,
|
||||
// `group` (singular), `storage` (GB) rather than the multi-group `sampleUsers`
|
||||
// above.
|
||||
export const sampleUsersFlat = [
|
||||
{ id: 'u_1ph2', name: 'Anne Baslund', email: 'anne@dezky.com', role: 'Owner', status: 'active', last: '2 min ago', group: 'Engineering', storage: 12.4 },
|
||||
{ id: 'u_4kx9', name: 'Mikkel Nørgaard', email: 'mikkel@dezky.com', role: 'Admin', status: 'active', last: '14 min ago', group: 'Operations', storage: 8.1 },
|
||||
{ id: 'u_88aw', name: 'Sofie Lindberg', email: 'sofie@dezky.com', role: 'Admin', status: 'active', last: '1 h ago', group: 'Finance', storage: 3.2 },
|
||||
{ id: 'u_d12k', name: 'Lars Holm', email: 'lars@dezky.com', role: 'Member', status: 'active', last: '3 h ago', group: 'Engineering', storage: 22.7 },
|
||||
{ id: 'u_9rqo', name: 'Emma Skov', email: 'emma@dezky.com', role: 'Member', status: 'invited', last: '—', group: 'Design', storage: 0 },
|
||||
{ id: 'u_3vbn', name: 'Jonas Berg', email: 'jonas@dezky.com', role: 'Member', status: 'active', last: '2 d ago', group: 'Engineering', storage: 14.0 },
|
||||
{ id: 'u_g51e', name: 'Tina Falkenberg', email: 'tina@dezky.com', role: 'Member', status: 'suspended', last: '11 d ago', group: 'Finance', storage: 5.6 },
|
||||
{ id: 'u_kk7n', name: 'Oliver Schmidt', email: 'oliver@dezky.com', role: 'Member', status: 'active', last: '4 h ago', group: 'Design', storage: 9.3 },
|
||||
{ id: 'u_pp01', name: 'Frederik Madsen', email: 'frederik@dezky.com', role: 'Member', status: 'active', last: 'Yesterday', group: 'Sales', storage: 6.8 },
|
||||
{ id: 'u_qq22', name: 'Astrid Vinther', email: 'astrid@dezky.com', role: 'Admin', status: 'active', last: '32 min ago', group: 'Operations', storage: 4.1 },
|
||||
{ id: 'u_rr44', name: 'Magnus Eriksen', email: 'magnus@dezky.com', role: 'Member', status: 'invited', last: '—', group: 'Engineering', storage: 0 },
|
||||
{ id: 'u_tt55', name: 'Clara Bjerre', email: 'clara@dezky.com', role: 'Member', status: 'active', last: '5 d ago', group: 'Sales', storage: 2.0 },
|
||||
]
|
||||
|
||||
// Source-fidelity domains (platform-screens.jsx SAMPLE_DOMAINS line 23) — flat
|
||||
// shape with per-record-type status used by DomainsScreen / DomainCard.
|
||||
export const sampleDomainsFlat = [
|
||||
{ domain: 'dezky.com', status: 'ok' as const, mx: 'ok' as const, spf: 'ok' as const, dkim: 'ok' as const, dmarc: 'ok' as const, users: 11 },
|
||||
{ domain: 'dezky.io', status: 'ok' as const, mx: 'ok' as const, spf: 'ok' as const, dkim: 'ok' as const, dmarc: 'warn' as const, users: 0 },
|
||||
{ domain: 'baslund.dk', status: 'warn' as const, mx: 'ok' as const, spf: 'warn' as const, dkim: 'ok' as const, dmarc: 'bad' as const, users: 2 },
|
||||
]
|
||||
|
||||
// Meeting rooms — strict port of platform-collab.jsx MEETING_ROOMS (line 8)
|
||||
export const meetingRooms = [
|
||||
{ id: 'r_eng', name: 'Engineering standup', alias: 'eng-standup', type: 'recurring' as const, when: 'Daily · 09:30', owner: 'Mikkel Nørgaard', members: 4, recording: 'auto' as const, protected: false },
|
||||
{ id: 'r_lead', name: 'Leadership weekly', alias: 'leadership', type: 'recurring' as const, when: 'Mondays · 14:00', owner: 'Anne Baslund', members: 3, recording: 'manual' as const, protected: true },
|
||||
{ id: 'r_allh', name: 'All-hands', alias: 'allhands', type: 'recurring' as const, when: '2nd Friday · 15:00', owner: 'Anne Baslund', members: 11, recording: 'auto' as const, protected: false },
|
||||
{ id: 'r_inter', name: 'Interview room A', alias: 'interview-a', type: 'ad-hoc' as const, when: 'Persistent', owner: 'Sofie Lindberg', members: 2, recording: 'off' as const, protected: true },
|
||||
{ id: 'r_cust', name: 'Customer demos', alias: 'demo', type: 'shared' as const, when: 'On-demand', owner: 'Frederik Madsen', members: 2, recording: 'manual' as const, protected: false },
|
||||
{ id: 'r_focus', name: 'Focus · pair', alias: 'pair', type: 'ad-hoc' as const, when: 'Persistent', owner: 'Lars Holm', members: 1, recording: 'off' as const, protected: false },
|
||||
]
|
||||
|
||||
// Meeting recordings — strict port of platform-collab.jsx MEETING_RECORDINGS (line 17)
|
||||
export const meetingRecordings = [
|
||||
{ id: 'rec1', title: 'All-hands · May edition', date: '12 May 2026', dur: '52 min', size: '184 MB', host: 'Anne Baslund', views: 11, retention: '365 d', legal: false },
|
||||
{ id: 'rec2', title: 'Customer demo · Novo Holding', date: '11 May 2026', dur: '38 min', size: '142 MB', host: 'Frederik Madsen', views: 4, retention: '90 d', legal: false },
|
||||
{ id: 'rec3', title: 'Engineering · Sprint review 24', date: '09 May 2026', dur: '46 min', size: '168 MB', host: 'Mikkel Nørgaard', views: 6, retention: '365 d', legal: false },
|
||||
{ id: 'rec4', title: 'Board update · Q1 close', date: '02 May 2026', dur: '28 min', size: '102 MB', host: 'Anne Baslund', views: 3, retention: 'forever', legal: true },
|
||||
{ id: 'rec5', title: 'New hire onboarding · Magnus', date: '29 Apr 2026', dur: '24 min', size: '88 MB', host: 'Sofie Lindberg', views: 2, retention: '90 d', legal: false },
|
||||
{ id: 'rec6', title: 'Engineering · Sprint review 23', date: '25 Apr 2026', dur: '41 min', size: '154 MB', host: 'Mikkel Nørgaard', views: 5, retention: '365 d', legal: false },
|
||||
{ id: 'rec7', title: 'Customer call · Aalborg Logistik', date: '22 Apr 2026', dur: '34 min', size: '128 MB', host: 'Frederik Madsen', views: 3, retention: '90 d', legal: false },
|
||||
]
|
||||
|
||||
// Chat workspaces — strict port of platform-collab.jsx CHAT_WORKSPACES (line 27)
|
||||
export const chatWorkspaces = [
|
||||
{ id: 'cw_main', name: 'baslund · main', url: 'baslund.chat.dezky.com', members: 11, channels: 24, messages30d: 8420, status: 'active' as const, primary: true },
|
||||
{ id: 'cw_eng', name: 'engineering', url: 'eng.chat.dezky.com', members: 4, channels: 11, messages30d: 12480, status: 'active' as const, primary: false },
|
||||
]
|
||||
|
||||
// Chat channels — strict port of platform-collab.jsx CHAT_CHANNELS (line 32)
|
||||
export const chatChannels = [
|
||||
{ name: 'general', type: 'public' as const, members: 11, messages30d: 1840, owner: 'Anne Baslund', topic: 'Workspace-wide announcements' },
|
||||
{ name: 'engineering', type: 'public' as const, members: 4, messages30d: 3220, owner: 'Mikkel Nørgaard', topic: 'Eng standups, deploys, code questions' },
|
||||
{ name: 'design', type: 'public' as const, members: 2, messages30d: 480, owner: 'Sofie Lindberg', topic: 'Brand, product design, reviews' },
|
||||
{ name: 'sales-customers', type: 'private' as const, members: 3, messages30d: 720, owner: 'Frederik Madsen', topic: 'Customer accounts and pipeline' },
|
||||
{ name: 'random', type: 'public' as const, members: 9, messages30d: 1240, owner: 'Anne Baslund', topic: 'Off-topic' },
|
||||
{ name: 'incidents', type: 'public' as const, members: 5, messages30d: 84, owner: 'Mikkel Nørgaard', topic: 'Service alerts and incident response' },
|
||||
{ name: 'dezky-roadmap', type: 'private' as const, members: 4, messages30d: 410, owner: 'Anne Baslund', topic: 'Product planning, not for everyone' },
|
||||
{ name: 'finance', type: 'private' as const, members: 2, messages30d: 96, owner: 'Astrid Vinther', topic: 'Invoicing, accounting' },
|
||||
]
|
||||
|
||||
// Integrations marketplace — strict port of platform-collab.jsx INTEGRATIONS (line 43)
|
||||
export interface Integration {
|
||||
id: string
|
||||
name: string
|
||||
cat: 'Productivity' | 'Storage' | 'CRM' | 'Accounting' | 'Operations'
|
||||
desc: string
|
||||
connected: boolean
|
||||
users?: number
|
||||
kind: string
|
||||
icon: string
|
||||
color: string
|
||||
accent: string
|
||||
danish?: boolean
|
||||
}
|
||||
|
||||
export const integrations: Integration[] = [
|
||||
{ id: 'notion', name: 'Notion', cat: 'Productivity', desc: 'Docs and wikis. Sign in with dezky.', connected: true, users: 11, kind: 'SSO', icon: '◧', color: '#000000', accent: '#FFFFFF' },
|
||||
{ id: 'figma', name: 'Figma', cat: 'Productivity', desc: 'Design files with single sign-on.', connected: true, users: 6, kind: 'SSO', icon: 'F', color: '#F24E1E', accent: '#FFFFFF' },
|
||||
{ id: 'linear', name: 'Linear', cat: 'Productivity', desc: 'Issue tracking for engineering.', connected: true, users: 4, kind: 'SSO + provisioning', icon: 'L', color: '#5E6AD2', accent: '#FFFFFF' },
|
||||
{ id: 'github', name: 'GitHub', cat: 'Productivity', desc: 'Source control. SAML enforcement.', connected: false, kind: 'SSO', icon: '⌬', color: '#181717', accent: '#FFFFFF' },
|
||||
{ id: 'sketch', name: 'Sketch', cat: 'Productivity', desc: 'Design tool with team libraries.', connected: false, kind: 'SSO', icon: '◆', color: '#FDB300', accent: '#0A0A0A' },
|
||||
{ id: 'gdrive', name: 'Google Drive', cat: 'Storage', desc: 'Mount Google Drive folders inside Drev.', connected: false, kind: 'OAuth', icon: 'G', color: '#1A73E8', accent: '#FFFFFF' },
|
||||
{ id: 'dropbox', name: 'Dropbox', cat: 'Storage', desc: 'Mirror Dropbox into Drev. Read-only.', connected: false, kind: 'OAuth', icon: 'D', color: '#0061FF', accent: '#FFFFFF' },
|
||||
{ id: 'hubspot', name: 'HubSpot', cat: 'CRM', desc: 'Customer pipeline + email sync.', connected: true, users: 3, kind: 'API', icon: 'H', color: '#FF7A59', accent: '#FFFFFF' },
|
||||
{ id: 'pipedrive', name: 'Pipedrive', cat: 'CRM', desc: 'Sales CRM. Sign in with dezky.', connected: false, kind: 'SSO', icon: 'P', color: '#1A1A1A', accent: '#FFFFFF' },
|
||||
{ id: 'economic', name: 'e-conomic', cat: 'Accounting', desc: 'Sync invoices to e-conomic. Danish CVR aware.', connected: true, users: 2, kind: 'API', icon: 'e', color: '#0091CD', accent: '#FFFFFF', danish: true },
|
||||
{ id: 'billy', name: 'Billy', cat: 'Accounting', desc: 'Danish accounting platform. Auto-bookkeeping.', connected: false, kind: 'API', icon: 'B', color: '#0EBC81', accent: '#FFFFFF', danish: true },
|
||||
{ id: 'dinero', name: 'Dinero', cat: 'Accounting', desc: 'Send invoices, sync ledger.', connected: false, kind: 'API', icon: 'D', color: '#FFCB05', accent: '#0A0A0A', danish: true },
|
||||
{ id: 'pleo', name: 'Pleo', cat: 'Accounting', desc: 'Company cards. Auto-reconcile receipts.', connected: false, kind: 'API', icon: 'p', color: '#1B1B1B', accent: '#D4FF3A', danish: true },
|
||||
{ id: 'pagerduty', name: 'PagerDuty', cat: 'Operations', desc: 'Page on-call from dezky incidents.', connected: false, kind: 'Webhook', icon: 'P', color: '#06AC38', accent: '#FFFFFF' },
|
||||
{ id: 'slack', name: 'Slack', cat: 'Operations', desc: 'Mirror Chat to Slack (read-only).', connected: false, kind: 'OAuth', icon: 'S', color: '#4A154B', accent: '#FFFFFF' },
|
||||
]
|
||||
|
||||
export const integrationCategories = ['All', 'Productivity', 'Storage', 'CRM', 'Accounting', 'Operations'] as const
|
||||
Reference in New Issue
Block a user