Files
dezky/apps/portal/data/workspace.ts
T
Ronni Baslund aee8f13899 feat(mail): tenant alias and distribution-list management via Stalwart
Customer-admin Mail settings backed by Stalwart JMAP: per-tenant aliases
(extra addresses routing to a mailbox) and distribution lists (one address
fanning out to many recipients). Adds StalwartClient x:Alias/x:MailingList
methods, a tenant-scoped MailController/MailService, the portal Mail settings
page and its proxy routes, and the mailboxAddress field on TenantUserDoc.
Removes the old mock mail data now that the page reads live data.
2026-06-07 00:16:30 +02:00

178 lines
18 KiB
TypeScript

// 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 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 },
]
// 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 },
]
// 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