mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
The demo user becomes Nora Lindqvist, engineering lead at Meridian Labs, in the week of a product launch. The fixtures tell one story across mail, contacts, calendar, tasks and files, and exercise the features the screenshots and the public demo should show: - a second, mail-only login (her personal domain) and the shared Support mailbox delegated to the work account, so the account switcher, the shared section and the merged inbox have real content - raw headers the way Stalwart writes them: the sender check fails on the phishing mail, newsletters carry a one-click unsubscribe, one mail asks for a read receipt, and spam scores show in the details - an iMIP invitation with a served .ics, an event waiting for the answer on the calendar, and RSVP patches applied in the demo client - @mentions as mailto links in a received mail, a verification code twelve minutes old, nested folders, named tags on the mail, contact groups, a scheduled message and tasks switched on - every attachment and file backed by real bytes under public/demo, generated by `scripts/demo-assets/generate.mjs`; contact photos are bundled portraits instead of requests to randomuser.me - folder counts computed from the mail instead of hand-kept numbers Demo login seeds the tags, templates and merged-inbox settings once per device and switches the cross-account gate on for the session; "Reset" restores them. `bulwark-demo-simulate=off` in localStorage stops the simulated incoming mail for screenshot runs.
119 lines
5.5 KiB
TypeScript
119 lines
5.5 KiB
TypeScript
import { cloneFixtures } from './demo-utils';
|
|
import { createDemoMailboxes, createDemoSupportMailboxes, createDemoPersonalMailboxes, SUPPORT_ACCOUNT_ID, SUPPORT_ACCOUNT_NAME } from './fixtures/mailboxes';
|
|
import { createDemoEmails } from './fixtures/emails';
|
|
import { createDemoSupportEmails } from './fixtures/support';
|
|
import { createDemoPersonalEmails, createDemoPersonalIdentities } from './fixtures/personal';
|
|
import { createDemoContacts, createDemoAddressBooks } from './fixtures/contacts';
|
|
import { createDemoCalendars, createDemoCalendarEvents } from './fixtures/calendars';
|
|
import { createDemoCalendarTasks } from './fixtures/tasks';
|
|
import { createDemoIdentities } from './fixtures/identities';
|
|
import { createDemoSieveScripts, createDemoSieveCapabilities, createDemoSieveContent } from './fixtures/filters';
|
|
import { createDemoFileNodes } from './fixtures/files';
|
|
import { createDemoVacationResponse } from './fixtures/vacation';
|
|
import { NORA, NORA_PERSONAL } from './fixtures/people';
|
|
|
|
import type { Email, EmailAddress, Mailbox, ContactCard, AddressBook, Calendar, CalendarEvent, CalendarTask, Identity, VacationResponse, FileNode, SharedAccount } from '@/lib/jmap/types';
|
|
import type { SieveScript, SieveCapabilities } from '@/lib/jmap/sieve-types';
|
|
|
|
/**
|
|
* The demo signs in to two accounts. "work" is the primary login with mail,
|
|
* contacts, calendars, files and filters, plus the shared Support mailbox
|
|
* delegated to it. "personal" is a mail-only second login, so the account
|
|
* switcher and the merged inbox have more than one source.
|
|
*/
|
|
export type DemoProfile = 'work' | 'personal';
|
|
|
|
export interface DemoData {
|
|
profile: DemoProfile;
|
|
/** JMAP account id of the login's primary account. */
|
|
accountId: string;
|
|
username: string;
|
|
/** The persona's own address in this account, used as From on new mail. */
|
|
self: EmailAddress;
|
|
mailboxes: Mailbox[];
|
|
/** Mailboxes of accounts shared with this login, keyed by their owner. */
|
|
sharedMailboxes: Record<string, { name: string; mailboxes: Mailbox[] }>;
|
|
emails: Email[];
|
|
contacts: ContactCard[];
|
|
addressBooks: AddressBook[];
|
|
calendars: Calendar[];
|
|
calendarEvents: CalendarEvent[];
|
|
calendarTasks: CalendarTask[];
|
|
identities: Identity[];
|
|
sieveScripts: SieveScript[];
|
|
sieveCapabilities: SieveCapabilities;
|
|
sieveContent: Record<string, string>;
|
|
fileNodes: FileNode[];
|
|
vacationResponse: VacationResponse;
|
|
quota: { used: number; total: number };
|
|
/** Ids of drafts that are scheduled to go out, with their send time. */
|
|
scheduled: Array<{ emailId: string; identityId: string; sendAt: string }>;
|
|
}
|
|
|
|
export const DEMO_WORK_ACCOUNT_ID = 'demo-account';
|
|
export const DEMO_PERSONAL_ACCOUNT_ID = 'demo-personal';
|
|
|
|
/** What `getSharedAccounts()` reports for a profile. */
|
|
export function demoSharedAccounts(profile: DemoProfile): SharedAccount[] {
|
|
if (profile === 'personal') {
|
|
return [{ id: DEMO_PERSONAL_ACCOUNT_ID, name: 'Personal', isPrimary: true, capabilities: { mail: true, sieve: false, calendars: false, contacts: false, filenode: false } }];
|
|
}
|
|
return [
|
|
{ id: DEMO_WORK_ACCOUNT_ID, name: 'Meridian Labs', isPrimary: true, capabilities: { mail: true, sieve: true, calendars: true, contacts: true, filenode: true } },
|
|
{ id: SUPPORT_ACCOUNT_ID, name: SUPPORT_ACCOUNT_NAME, isPrimary: false, capabilities: { mail: true, sieve: false, calendars: false, contacts: false, filenode: false } },
|
|
];
|
|
}
|
|
|
|
/** Return a fresh deep-cloned copy of all demo data for one profile. */
|
|
export function getDemoData(profile: DemoProfile = 'work'): DemoData {
|
|
if (profile === 'personal') {
|
|
return cloneFixtures({
|
|
profile,
|
|
accountId: DEMO_PERSONAL_ACCOUNT_ID,
|
|
username: NORA_PERSONAL.email,
|
|
self: { name: NORA_PERSONAL.name, email: NORA_PERSONAL.email },
|
|
mailboxes: createDemoPersonalMailboxes(),
|
|
sharedMailboxes: {},
|
|
emails: createDemoPersonalEmails(),
|
|
contacts: [],
|
|
addressBooks: [],
|
|
calendars: [],
|
|
calendarEvents: [],
|
|
calendarTasks: [],
|
|
identities: createDemoPersonalIdentities(),
|
|
sieveScripts: [],
|
|
sieveCapabilities: createDemoSieveCapabilities(),
|
|
sieveContent: {},
|
|
fileNodes: [],
|
|
vacationResponse: { id: 'singleton', isEnabled: false, fromDate: null, toDate: null, subject: '', textBody: '', htmlBody: null },
|
|
quota: { used: 1_814_000_000, total: 10_737_418_240 },
|
|
scheduled: [],
|
|
});
|
|
}
|
|
const scheduledAt = new Date();
|
|
scheduledAt.setHours(12, 0, 0, 0);
|
|
if (scheduledAt.getTime() < Date.now()) scheduledAt.setDate(scheduledAt.getDate() + 1);
|
|
return cloneFixtures({
|
|
profile,
|
|
accountId: DEMO_WORK_ACCOUNT_ID,
|
|
username: NORA.email,
|
|
self: { name: NORA.name, email: NORA.email },
|
|
mailboxes: createDemoMailboxes(),
|
|
sharedMailboxes: { [SUPPORT_ACCOUNT_ID]: { name: SUPPORT_ACCOUNT_NAME, mailboxes: createDemoSupportMailboxes() } },
|
|
emails: [...createDemoEmails(), ...createDemoSupportEmails()],
|
|
contacts: createDemoContacts(),
|
|
addressBooks: createDemoAddressBooks(),
|
|
calendars: createDemoCalendars(),
|
|
calendarEvents: createDemoCalendarEvents(),
|
|
calendarTasks: createDemoCalendarTasks(),
|
|
identities: createDemoIdentities(),
|
|
sieveScripts: createDemoSieveScripts(),
|
|
sieveCapabilities: createDemoSieveCapabilities(),
|
|
sieveContent: createDemoSieveContent(),
|
|
fileNodes: createDemoFileNodes(),
|
|
vacationResponse: createDemoVacationResponse(),
|
|
quota: { used: 6_442_450_944, total: 21_474_836_480 },
|
|
scheduled: [{ emailId: 'demo-email-scheduled-prep', identityId: 'demo-identity-primary', sendAt: scheduledAt.toISOString() }],
|
|
});
|
|
}
|