Merge pull request #372 from Coffey-Labs/refactor/lib-domain-folders

Group the admin and calendar modules, and stop calling screenshots docs
This commit is contained in:
jcoffey
2026-09-15 22:53:34 -07:00
committed by GitHub
72 changed files with 108 additions and 108 deletions
+3 -3
View File
@@ -36,9 +36,9 @@ settings included, belongs to Stalwart, so the container is disposable.
| | | | | |
| --- | --- | | --- | --- |
| **Inbox & conversation (dark)** ![Inbox, dark theme](docs/screenshots/inbox-dark.jpg) | **Inbox & conversation (light)** ![Inbox, light theme](docs/screenshots/inbox-light.jpg) | | **Inbox & conversation (dark)** ![Inbox, dark theme](screenshots/inbox-dark.jpg) | **Inbox & conversation (light)** ![Inbox, light theme](screenshots/inbox-light.jpg) |
| **Composer** ![Composer](docs/screenshots/compose.jpg) | **Calendar** ![Calendar](docs/screenshots/calendar.jpg) | | **Composer** ![Composer](screenshots/compose.jpg) | **Calendar** ![Calendar](screenshots/calendar.jpg) |
| **Contacts** ![Contacts](docs/screenshots/contacts.jpg) | **Sieve filter builder** ![Filters](docs/screenshots/filters.jpg) | | **Contacts** ![Contacts](screenshots/contacts.jpg) | **Sieve filter builder** ![Filters](screenshots/filters.jpg) |
Taken against the built-in mock with sample data. More, including the phone Taken against the built-in mock with sample data. More, including the phone
layout, on [ihasmail.org](https://ihasmail.org/#screenshots). layout, on [ihasmail.org](https://ihasmail.org/#screenshots).

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

@@ -5,8 +5,8 @@
* images already use rather than whatever a window happens to be. * images already use rather than whatever a window happens to be.
* *
* npm run dev:mock # in another terminal * npm run dev:mock # in another terminal
* node docs/screenshots.mjs docs/screenshots * node scripts/screenshots.mjs screenshots
* node docs/screenshots-light.mjs docs/screenshots * node scripts/screenshots-light.mjs screenshots
* *
* Restart the mock before a run. The filters shot creates rules, so a second * Restart the mock before a run. The filters shot creates rules, so a second
* run against the same mock shows them twice. * run against the same mock shows them twice.
@@ -30,7 +30,7 @@
* flip --bg to #f6f8fa. The compositor simply does not repaint everything a * flip --bg to #f6f8fa. The compositor simply does not repaint everything a
* CSS-variable change touches while metrics are overridden. Launching Chrome * CSS-variable change touches while metrics are overridden. Launching Chrome
* at --window-size and never calling setDeviceMetricsOverride renders it * at --window-size and never calling setDeviceMetricsOverride renders it
* correctly, which is what docs/screenshots-light.mjs does. * correctly, which is what scripts/screenshots-light.mjs does.
* *
* assertTheme() stays either way: without it this script wrote a dark * assertTheme() stays either way: without it this script wrote a dark
* screenshot under a light caption and reported success, and that is how the * screenshot under a light caption and reported success, and that is how the
@@ -226,7 +226,7 @@ try {
await evaluate(`(() => { const c = [...document.querySelectorAll('button')].find(b => /close|discard/i.test(b.getAttribute('aria-label')||'')); if (c) c.click(); })()`); await evaluate(`(() => { const c = [...document.querySelectorAll('button')].find(b => /close|discard/i.test(b.getAttribute('aria-label')||'')); if (c) c.click(); })()`);
await sleep(800); await sleep(800);
// (inbox-light is captured by docs/screenshots-light.mjs -- see the header) // (inbox-light is captured by scripts/screenshots-light.mjs -- see the header)
// --- calendar --- // --- calendar ---
+1 -1
View File
@@ -7,7 +7,7 @@
*/ */
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { describeRule as describeSieve } from "../sieve"; import { describeRule as describeSieve } from "../sieve";
import { describeRule as describeRecurrence, weekdayOptions } from "../recurrence"; import { describeRule as describeRecurrence, weekdayOptions } from "../calendar/recurrence";
import { setUiLanguageForFormatting } from "../datetime"; import { setUiLanguageForFormatting } from "../datetime";
import { setCatalog } from "../i18n"; import { setCatalog } from "../i18n";
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { ADMIN_BASELINE, adminSections, can, dashboardCards, canGrantRole, generatePassword, hasAdministration, outranks, permissionSet, resolveRoles, type RoleDef } from "@/lib/adminAccess"; import { ADMIN_BASELINE, adminSections, can, dashboardCards, canGrantRole, generatePassword, hasAdministration, outranks, permissionSet, resolveRoles, type RoleDef } from "@/lib/admin/adminAccess";
const set = (...p: string[]) => permissionSet(p); const set = (...p: string[]) => permissionSet(p);
const everything = set(...ADMIN_BASELINE, "sysTenantGet", "jmapEmailGet", "impersonate"); const everything = set(...ADMIN_BASELINE, "sysTenantGet", "jmapEmailGet", "impersonate");
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { client, JmapMethodError } from "@/jmap/client"; import { client, JmapMethodError } from "@/jmap/client";
import { balancedColumns, countObjects, isRefused, loadMetrics, summarizeMetrics, type MetricRecord } from "@/lib/adminDashboard"; import { balancedColumns, countObjects, isRefused, loadMetrics, summarizeMetrics, type MetricRecord } from "@/lib/admin/adminDashboard";
const counter = (metric: string, count: number, timestamp = "2026-09-15T14:00:00Z"): MetricRecord => ({ "@type": "Counter", metric, count, timestamp }); const counter = (metric: string, count: number, timestamp = "2026-09-15T14:00:00Z"): MetricRecord => ({ "@type": "Counter", metric, count, timestamp });
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { client } from "@/jmap/client"; import { client } from "@/jmap/client";
import { aliasList, describeDirectoryError, DirectoryError, hasPassword, passwordPatch, queryAccounts, quotasWithDisk } from "@/lib/adminDirectory"; import { aliasList, describeDirectoryError, DirectoryError, hasPassword, passwordPatch, queryAccounts, quotasWithDisk } from "@/lib/admin/adminDirectory";
describe("setting a password", () => { describe("setting a password", () => {
it("writes into the existing password credential, keeping its place", () => { it("writes into the existing password credential, keeping its place", () => {
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { describeLinked, dkimAlgorithm, looksLikeDomain, normalizeDomain, parseZoneFile } from "@/lib/adminDomains"; import { describeLinked, dkimAlgorithm, looksLikeDomain, normalizeDomain, parseZoneFile } from "@/lib/admin/adminDomains";
/** /**
* Written the way Stalwart's BIND serializer writes it (dns-update's * Written the way Stalwart's BIND serializer writes it (dns-update's
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { client } from "@/jmap/client"; import { client } from "@/jmap/client";
import { countMembers, createGroup, destroyGroup, groupRoleKey, groupRolesFromKey, membershipPatch } from "@/lib/adminGroups"; import { countMembers, createGroup, destroyGroup, groupRoleKey, groupRolesFromKey, membershipPatch } from "@/lib/admin/adminGroups";
describe("group membership", () => { describe("group membership", () => {
it("is a patch to each member, one pointer each, so no other membership moves", () => { it("is a patch to each member, one pointer each, so no other membership moves", () => {
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { client } from "@/jmap/client"; import { client } from "@/jmap/client";
import { createList, parseAddresses, queryLists, recipientsPatch } from "@/lib/adminLists"; import { createList, parseAddresses, queryLists, recipientsPatch } from "@/lib/admin/adminLists";
describe("a mailing list's recipients", () => { describe("a mailing list's recipients", () => {
it("are saved as what was added and removed, one pointer each", () => { it("are saved as what was added and removed, one pointer each", () => {
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { permissionSet } from "@/lib/adminAccess"; import { permissionSet } from "@/lib/admin/adminAccess";
import { canBuildOn, effectivePermissions, inherited, roleOutranks, setPatch, type DirectoryRole } from "@/lib/adminRoles"; import { canBuildOn, effectivePermissions, inherited, roleOutranks, setPatch, type DirectoryRole } from "@/lib/admin/adminRoles";
const flags = (...n: string[]) => Object.fromEntries(n.map((x) => [x, true])); const flags = (...n: string[]) => Object.fromEntries(n.map((x) => [x, true]));
const roles = new Map<string, DirectoryRole>([ const roles = new Map<string, DirectoryRole>([
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { client } from "@/jmap/client"; import { client } from "@/jmap/client";
import { countTenantMembers, drawableLogo, quotasPatch, setDomainTenant } from "@/lib/adminTenants"; import { countTenantMembers, drawableLogo, quotasPatch, setDomainTenant } from "@/lib/admin/adminTenants";
describe("a tenant's limits", () => { describe("a tenant's limits", () => {
it("change one pointer each, leaving the quotas ihasmail does not offer alone", () => { it("change one pointer each, leaving the quotas ihasmail does not offer alone", () => {
@@ -1,6 +1,6 @@
import { client } from "@/jmap/client"; import { client } from "@/jmap/client";
import { t } from "@/lib/i18n"; import { t } from "@/lib/i18n";
import type { PermissionsMode, RoleDef, UserRoles } from "@/lib/adminAccess"; import type { PermissionsMode, RoleDef, UserRoles } from "@/lib/admin/adminAccess";
/** /**
* Stalwart 0.16's directory, over the ordinary JMAP proxy. * Stalwart 0.16's directory, over the ordinary JMAP proxy.
@@ -1,6 +1,6 @@
import { client } from "@/jmap/client"; import { client } from "@/jmap/client";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
import { DirectoryError } from "@/lib/adminDirectory"; import { DirectoryError } from "@/lib/admin/adminDirectory";
/** /**
* Stalwart 0.16's domains, over the same proxy as accounts. * Stalwart 0.16's domains, over the same proxy as accounts.
@@ -1,7 +1,7 @@
import { client } from "@/jmap/client"; import { client } from "@/jmap/client";
import { t } from "@/lib/i18n"; import { t } from "@/lib/i18n";
import type { PermissionsMode, UserRoles } from "@/lib/adminAccess"; import type { PermissionsMode, UserRoles } from "@/lib/admin/adminAccess";
import { DirectoryError, DISK_QUOTA, queryAccounts, type EmailAlias } from "@/lib/adminDirectory"; import { DirectoryError, DISK_QUOTA, queryAccounts, type EmailAlias } from "@/lib/admin/adminDirectory";
/** /**
* Groups, from Stalwart 0.16's directory. * Groups, from Stalwart 0.16's directory.
@@ -1,6 +1,6 @@
import { client } from "@/jmap/client"; import { client } from "@/jmap/client";
import { t } from "@/lib/i18n"; import { t } from "@/lib/i18n";
import { DirectoryError, type EmailAlias } from "@/lib/adminDirectory"; import { DirectoryError, type EmailAlias } from "@/lib/admin/adminDirectory";
/** /**
* Mailing lists, from Stalwart 0.16's directory. * Mailing lists, from Stalwart 0.16's directory.
@@ -1,8 +1,8 @@
import { apiFetch, client } from "@/jmap/client"; import { apiFetch, client } from "@/jmap/client";
import { t } from "@/lib/i18n"; import { t } from "@/lib/i18n";
import type { Permissions, RoleDef } from "@/lib/adminAccess"; import type { Permissions, RoleDef } from "@/lib/admin/adminAccess";
import { DirectoryError } from "@/lib/adminDirectory"; import { DirectoryError } from "@/lib/admin/adminDirectory";
import { DomainError } from "@/lib/adminDomains"; import { DomainError } from "@/lib/admin/adminDomains";
import type { PermissionInfo } from "@/lib/permissionLabels"; import type { PermissionInfo } from "@/lib/permissionLabels";
/** /**
@@ -1,7 +1,7 @@
import { client } from "@/jmap/client"; import { client } from "@/jmap/client";
import { t } from "@/lib/i18n"; import { t } from "@/lib/i18n";
import { DirectoryError } from "@/lib/adminDirectory"; import { DirectoryError } from "@/lib/admin/adminDirectory";
import { DomainError } from "@/lib/adminDomains"; import { DomainError } from "@/lib/admin/adminDomains";
/** /**
* Tenants, from Stalwart 0.16's directory. * Tenants, from Stalwart 0.16's directory.
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { appointmentDraft, nextHalfHour } from "@/lib/appointment"; import { appointmentDraft, nextHalfHour } from "@/lib/calendar/appointment";
import type { Email, EmailBodyPart } from "@/jmap/types"; import type { Email, EmailBodyPart } from "@/jmap/types";
/** /**
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { availabilityWindow } from "@/lib/availabilityWindow"; import { availabilityWindow } from "@/lib/calendar/availabilityWindow";
const at = (s: string) => new Date(s); const at = (s: string) => new Date(s);
const hours = (w: { ticks: { time: Date }[] }) => w.ticks.map((t) => `${t.time.getDate()}@${t.time.getHours()}`); const hours = (w: { ticks: { time: Date }[] }) => w.ticks.map((t) => `${t.time.getDate()}@${t.time.getHours()}`);
@@ -13,7 +13,7 @@ import {
dayDelta, dayDelta,
resizePatch, resizePatch,
SNAP_MINUTES, SNAP_MINUTES,
} from "@/lib/eventDrag"; } from "@/lib/calendar/eventDrag";
import { BIRTHDAY_ID_PREFIX } from "@/lib/birthdays"; import { BIRTHDAY_ID_PREFIX } from "@/lib/birthdays";
import type { CalendarEvent } from "@/jmap/types"; import type { CalendarEvent } from "@/jmap/types";
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { looksLikeCalendar, parseIcs, parseIcsDuration, parseDateValue, parseLine, unescapeText, unfold } from "@/lib/ics"; import { looksLikeCalendar, parseIcs, parseIcsDuration, parseDateValue, parseLine, unescapeText, unfold } from "@/lib/calendar/ics";
const cal = (body: string) => `BEGIN:VCALENDAR\r\nVERSION:2.0\r\n${body}\r\nEND:VCALENDAR\r\n`; const cal = (body: string) => `BEGIN:VCALENDAR\r\nVERSION:2.0\r\n${body}\r\nEND:VCALENDAR\r\n`;
const event = (props: string) => `BEGIN:VEVENT\r\n${props}\r\nEND:VEVENT`; const event = (props: string) => `BEGIN:VEVENT\r\n${props}\r\nEND:VEVENT`;
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { toIcs, parseIcs } from "@/lib/ics"; import { toIcs, parseIcs } from "@/lib/calendar/ics";
import type { JSCalendarEvent } from "@/jmap/types"; import type { JSCalendarEvent } from "@/jmap/types";
/* /*
@@ -1,9 +1,9 @@
import type { Email, EmailAddress } from "@/jmap/types"; import type { Email, EmailAddress } from "@/jmap/types";
import { useCalendar, type EventDraft } from "@/store/calendar"; import { useCalendar, type EventDraft } from "@/store/calendar";
import { useMail } from "@/store/mail"; import { useMail } from "@/store/mail";
import { uniqueAddresses } from "./address"; import { uniqueAddresses } from "../address";
import { toLocalDateOnly } from "./dates"; import { toLocalDateOnly } from "../dates";
import { htmlToText } from "./text"; import { htmlToText } from "../text";
/** /**
* How much of a message body is copied into an event description. * How much of a message body is copied into an event description.
@@ -10,8 +10,8 @@
* question given an event and a gesture, what are the new start and end * question given an event and a gesture, what are the new start and end
* and the caller decides whether it is allowed to save that. * and the caller decides whether it is allowed to save that.
*/ */
import { addMinutes } from "./dates"; import { addMinutes } from "../dates";
import { isBirthdayEvent } from "./birthdays"; import { isBirthdayEvent } from "../birthdays";
import type { CalendarEvent } from "@/jmap/types"; import type { CalendarEvent } from "@/jmap/types";
/** /**
@@ -1,5 +1,5 @@
import type { JSCalendarRecurrenceRule, JSCalendarNDay } from "@/jmap/types"; import type { JSCalendarRecurrenceRule, JSCalendarNDay } from "@/jmap/types";
import { formatList, weekdayName, weekdayNames } from "./datetime"; import { formatList, weekdayName, weekdayNames } from "../datetime";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
/** /**
+1 -1
View File
@@ -5,7 +5,7 @@ import { toUTCDate, toLocalDateTime, zonedToDate, parseDuration, DAY_MS, browser
import { t } from "@/lib/i18n"; import { t } from "@/lib/i18n";
import { useContacts } from "./contacts"; import { useContacts } from "./contacts";
import { BIRTHDAY_CALENDAR_ID, birthdaysInRange, isBirthdayEvent, type Birthday } from "@/lib/birthdays"; import { BIRTHDAY_CALENDAR_ID, birthdaysInRange, isBirthdayEvent, type Birthday } from "@/lib/birthdays";
import { looksLikeCalendar, parseIcs, toIcs, type IcsEvent } from "@/lib/ics"; import { looksLikeCalendar, parseIcs, toIcs, type IcsEvent } from "@/lib/calendar/ics";
import { withBase } from "@/lib/basePath"; import { withBase } from "@/lib/basePath";
import { settings, useSettings } from "./settings"; import { settings, useSettings } from "./settings";
import { useSession } from "./session"; import { useSession } from "./session";
+1 -1
View File
@@ -22,7 +22,7 @@ import { formatSize } from "@/lib/format";
import { collectShare } from "@/lib/shareTarget"; import { collectShare } from "@/lib/shareTarget";
import { TranslateBoundary } from "@/ui/TranslateBoundary"; import { TranslateBoundary } from "@/ui/TranslateBoundary";
import { t } from "@/lib/i18n"; import { t } from "@/lib/i18n";
import { hasAdministration } from "@/lib/adminAccess"; import { hasAdministration } from "@/lib/admin/adminAccess";
import { usePermissions } from "./admin/usePermissions"; import { usePermissions } from "./admin/usePermissions";
import { AdminNav } from "./admin/AdminNav"; import { AdminNav } from "./admin/AdminNav";
+2 -2
View File
@@ -7,7 +7,7 @@ import {
generatePassword, generatePassword,
outranks, outranks,
type UserRoles, type UserRoles,
} from "@/lib/adminAccess"; } from "@/lib/admin/adminAccess";
import { import {
aliasList, aliasList,
createAccount, createAccount,
@@ -20,7 +20,7 @@ import {
DISK_QUOTA, DISK_QUOTA,
type DirectoryAccount, type DirectoryAccount,
type EmailAlias, type EmailAlias,
} from "@/lib/adminDirectory"; } from "@/lib/admin/adminDirectory";
import { formatSize } from "@/lib/format"; import { formatSize } from "@/lib/format";
import { t, tNode } from "@/lib/i18n"; import { t, tNode } from "@/lib/i18n";
import { Link } from "wouter"; import { Link } from "wouter";
+3 -3
View File
@@ -3,7 +3,7 @@ import { useLocation } from "wouter";
import { ChevronLeft, ChevronRight, Search, UserPlus, Users } from "lucide-react"; import { ChevronLeft, ChevronRight, Search, UserPlus, Users } from "lucide-react";
import { useSession } from "@/store/session"; import { useSession } from "@/store/session";
import { STALWART_CAP } from "@/jmap/client"; import { STALWART_CAP } from "@/jmap/client";
import { can, type RoleDef } from "@/lib/adminAccess"; import { can, type RoleDef } from "@/lib/admin/adminAccess";
import { import {
describeDirectoryError, describeDirectoryError,
getAccounts, getAccounts,
@@ -14,14 +14,14 @@ import {
DISK_QUOTA, DISK_QUOTA,
type DirectoryAccount, type DirectoryAccount,
type DirectoryDomain, type DirectoryDomain,
} from "@/lib/adminDirectory"; } from "@/lib/admin/adminDirectory";
import { formatSize } from "@/lib/format"; import { formatSize } from "@/lib/format";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
import { Avatar, Empty, Spinner } from "@/ui/misc"; import { Avatar, Empty, Spinner } from "@/ui/misc";
import { usePermissions } from "./usePermissions"; import { usePermissions } from "./usePermissions";
import { isSelf, roleName, type DirectoryContext } from "./directoryContext"; import { isSelf, roleName, type DirectoryContext } from "./directoryContext";
import { AccountSheet } from "./AccountSheet"; import { AccountSheet } from "./AccountSheet";
import { listTenantNames } from "@/lib/adminTenants"; import { listTenantNames } from "@/lib/admin/adminTenants";
const PAGE_SIZE = 50; const PAGE_SIZE = 50;
+2 -2
View File
@@ -1,8 +1,8 @@
import { useEffect, useState, type ReactNode } from "react"; import { useEffect, useState, type ReactNode } from "react";
import { Link } from "wouter"; import { Link } from "wouter";
import { ArrowDownToLine, ArrowUpFromLine, ExternalLink, Globe, Hourglass, LayoutDashboard, MemoryStick, RefreshCw, Users } from "lucide-react"; import { ArrowDownToLine, ArrowUpFromLine, ExternalLink, Globe, Hourglass, LayoutDashboard, MemoryStick, RefreshCw, Users } from "lucide-react";
import { adminSections, dashboardCards, type DashboardCard } from "@/lib/adminAccess"; import { adminSections, dashboardCards, type DashboardCard } from "@/lib/admin/adminAccess";
import { balancedColumns, countObjects, DASHBOARD_WINDOW_MS, isRefused, loadMetrics, summarizeMetrics, type MessageStats } from "@/lib/adminDashboard"; import { balancedColumns, countObjects, DASHBOARD_WINDOW_MS, isRefused, loadMetrics, summarizeMetrics, type MessageStats } from "@/lib/admin/adminDashboard";
import { formatDayMonthTime, resolvedLocale } from "@/lib/datetime"; import { formatDayMonthTime, resolvedLocale } from "@/lib/datetime";
import { formatSize } from "@/lib/format"; import { formatSize } from "@/lib/format";
import { t } from "@/lib/i18n"; import { t } from "@/lib/i18n";
+1 -1
View File
@@ -1,7 +1,7 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { Link, useLocation } from "wouter"; import { Link, useLocation } from "wouter";
import { Building2, Globe, LayoutDashboard, List, ShieldCheck, User, UsersRound } from "lucide-react"; import { Building2, Globe, LayoutDashboard, List, ShieldCheck, User, UsersRound } from "lucide-react";
import { adminSections, type AdminSection } from "@/lib/adminAccess"; import { adminSections, type AdminSection } from "@/lib/admin/adminAccess";
import { t } from "@/lib/i18n"; import { t } from "@/lib/i18n";
import { usePermissions } from "./usePermissions"; import { usePermissions } from "./usePermissions";
+1 -1
View File
@@ -1,6 +1,6 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { Redirect } from "wouter"; import { Redirect } from "wouter";
import { adminSections, type AdminSection } from "@/lib/adminAccess"; import { adminSections, type AdminSection } from "@/lib/admin/adminAccess";
import { AccountsAdmin } from "./AccountsAdmin"; import { AccountsAdmin } from "./AccountsAdmin";
import { AdminDashboard } from "./AdminDashboard"; import { AdminDashboard } from "./AdminDashboard";
import { DomainsAdmin } from "./DomainsAdmin"; import { DomainsAdmin } from "./DomainsAdmin";
+3 -3
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Copy, Globe, Plus, Trash2, X } from "lucide-react"; import { Copy, Globe, Plus, Trash2, X } from "lucide-react";
import { can } from "@/lib/adminAccess"; import { can } from "@/lib/admin/adminAccess";
import { describeDirectoryError } from "@/lib/adminDirectory"; import { describeDirectoryError } from "@/lib/admin/adminDirectory";
import { import {
createDomain, createDomain,
describeLinked, describeLinked,
@@ -18,7 +18,7 @@ import {
type DirectoryDomainFull, type DirectoryDomainFull,
type DkimKey, type DkimKey,
type Managed, type Managed,
} from "@/lib/adminDomains"; } from "@/lib/admin/adminDomains";
import { formatFullDate } from "@/lib/format"; import { formatFullDate } from "@/lib/format";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
import { Dialog } from "@/ui/dialog"; import { Dialog } from "@/ui/dialog";
+3 -3
View File
@@ -1,9 +1,9 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import { ChevronLeft, ChevronRight, Globe, Plus, Search } from "lucide-react"; import { ChevronLeft, ChevronRight, Globe, Plus, Search } from "lucide-react";
import { can } from "@/lib/adminAccess"; import { can } from "@/lib/admin/adminAccess";
import { describeDirectoryError } from "@/lib/adminDirectory"; import { describeDirectoryError } from "@/lib/admin/adminDirectory";
import { countAccounts, getDomains, namesOf, queryDomains, type DirectoryDomainFull } from "@/lib/adminDomains"; import { countAccounts, getDomains, namesOf, queryDomains, type DirectoryDomainFull } from "@/lib/admin/adminDomains";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
import { Empty, Spinner } from "@/ui/misc"; import { Empty, Spinner } from "@/ui/misc";
import { usePermissions } from "./usePermissions"; import { usePermissions } from "./usePermissions";
+3 -3
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Search, Trash2, UserMinus, UserPlus, X } from "lucide-react"; import { Search, Trash2, UserMinus, UserPlus, X } from "lucide-react";
import { can, canGrantRole } from "@/lib/adminAccess"; import { can, canGrantRole } from "@/lib/admin/adminAccess";
import { aliasList, describeDirectoryError, quotasWithDisk, updateAccount, DISK_QUOTA, type EmailAlias } from "@/lib/adminDirectory"; import { aliasList, describeDirectoryError, quotasWithDisk, updateAccount, DISK_QUOTA, type EmailAlias } from "@/lib/admin/adminDirectory";
import { import {
createGroup, createGroup,
destroyGroup, destroyGroup,
@@ -12,7 +12,7 @@ import {
setMembership, setMembership,
type DirectoryGroup, type DirectoryGroup,
type GroupMember, type GroupMember,
} from "@/lib/adminGroups"; } from "@/lib/admin/adminGroups";
import { formatSize } from "@/lib/format"; import { formatSize } from "@/lib/format";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
import { Avatar, Spinner } from "@/ui/misc"; import { Avatar, Spinner } from "@/ui/misc";
+3 -3
View File
@@ -3,9 +3,9 @@ import { useLocation } from "wouter";
import { ChevronLeft, ChevronRight, Plus, Search, UsersRound } from "lucide-react"; import { ChevronLeft, ChevronRight, Plus, Search, UsersRound } from "lucide-react";
import { useSession } from "@/store/session"; import { useSession } from "@/store/session";
import { STALWART_CAP } from "@/jmap/client"; import { STALWART_CAP } from "@/jmap/client";
import { can, type RoleDef } from "@/lib/adminAccess"; import { can, type RoleDef } from "@/lib/admin/adminAccess";
import { describeDirectoryError, listDomains, listRoles, type DirectoryDomain } from "@/lib/adminDirectory"; import { describeDirectoryError, listDomains, listRoles, type DirectoryDomain } from "@/lib/admin/adminDirectory";
import { countMembers, getGroups, queryGroups, type DirectoryGroup } from "@/lib/adminGroups"; import { countMembers, getGroups, queryGroups, type DirectoryGroup } from "@/lib/admin/adminGroups";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
import { Avatar, Empty, Spinner } from "@/ui/misc"; import { Avatar, Empty, Spinner } from "@/ui/misc";
import { usePermissions } from "./usePermissions"; import { usePermissions } from "./usePermissions";
+3 -3
View File
@@ -1,8 +1,8 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Plus, Search, Trash2, X } from "lucide-react"; import { Plus, Search, Trash2, X } from "lucide-react";
import { can } from "@/lib/adminAccess"; import { can } from "@/lib/admin/adminAccess";
import { aliasList, describeDirectoryError, type EmailAlias } from "@/lib/adminDirectory"; import { aliasList, describeDirectoryError, type EmailAlias } from "@/lib/admin/adminDirectory";
import { createList, destroyList, parseAddresses, recipientsPatch, updateList, type DirectoryList } from "@/lib/adminLists"; import { createList, destroyList, parseAddresses, recipientsPatch, updateList, type DirectoryList } from "@/lib/admin/adminLists";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
import { Dialog } from "@/ui/dialog"; import { Dialog } from "@/ui/dialog";
import { toast } from "@/ui/toast"; import { toast } from "@/ui/toast";
+3 -3
View File
@@ -1,9 +1,9 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import { ChevronLeft, ChevronRight, List, Plus, Search } from "lucide-react"; import { ChevronLeft, ChevronRight, List, Plus, Search } from "lucide-react";
import { can } from "@/lib/adminAccess"; import { can } from "@/lib/admin/adminAccess";
import { describeDirectoryError, listDomains, type DirectoryDomain } from "@/lib/adminDirectory"; import { describeDirectoryError, listDomains, type DirectoryDomain } from "@/lib/admin/adminDirectory";
import { getLists, queryLists, type DirectoryList } from "@/lib/adminLists"; import { getLists, queryLists, type DirectoryList } from "@/lib/admin/adminLists";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
import { Empty, Spinner } from "@/ui/misc"; import { Empty, Spinner } from "@/ui/misc";
import { useSession } from "@/store/session"; import { useSession } from "@/store/session";
+4 -4
View File
@@ -1,8 +1,8 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Lock, Search, Trash2, X } from "lucide-react"; import { Lock, Search, Trash2, X } from "lucide-react";
import { can, canGrantRole } from "@/lib/adminAccess"; import { can, canGrantRole } from "@/lib/admin/adminAccess";
import { describeDirectoryError } from "@/lib/adminDirectory"; import { describeDirectoryError } from "@/lib/admin/adminDirectory";
import { describeLinked, DomainError } from "@/lib/adminDomains"; import { describeLinked, DomainError } from "@/lib/admin/adminDomains";
import { import {
canBuildOn, canBuildOn,
createRole, createRole,
@@ -15,7 +15,7 @@ import {
type DirectoryRole, type DirectoryRole,
type PermissionState, type PermissionState,
type RoleDefaults, type RoleDefaults,
} from "@/lib/adminRoles"; } from "@/lib/admin/adminRoles";
import type { PermissionEntry } from "@/lib/permissionLabels"; import type { PermissionEntry } from "@/lib/permissionLabels";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
import { Dialog } from "@/ui/dialog"; import { Dialog } from "@/ui/dialog";
+3 -3
View File
@@ -1,9 +1,9 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import { Plus, Search, ShieldCheck } from "lucide-react"; import { Plus, Search, ShieldCheck } from "lucide-react";
import { can } from "@/lib/adminAccess"; import { can } from "@/lib/admin/adminAccess";
import { describeDirectoryError } from "@/lib/adminDirectory"; import { describeDirectoryError } from "@/lib/admin/adminDirectory";
import { effectivePermissions, listAllRoles, loadPermissionList, loadRoleDefaults, type DirectoryRole, type RoleDefaults } from "@/lib/adminRoles"; import { effectivePermissions, listAllRoles, loadPermissionList, loadRoleDefaults, type DirectoryRole, type RoleDefaults } from "@/lib/admin/adminRoles";
import { describePermissions, loadPermissionCatalog, type PermissionEntry } from "@/lib/permissionLabels"; import { describePermissions, loadPermissionCatalog, type PermissionEntry } from "@/lib/permissionLabels";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
import { Empty, Spinner } from "@/ui/misc"; import { Empty, Spinner } from "@/ui/misc";
+4 -4
View File
@@ -1,8 +1,8 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Globe, Plus, Trash2, X } from "lucide-react"; import { Globe, Plus, Trash2, X } from "lucide-react";
import { can, canGrantRole, type RoleDef } from "@/lib/adminAccess"; import { can, canGrantRole, type RoleDef } from "@/lib/admin/adminAccess";
import { describeDirectoryError } from "@/lib/adminDirectory"; import { describeDirectoryError } from "@/lib/admin/adminDirectory";
import { describeLinked, DomainError } from "@/lib/adminDomains"; import { describeLinked, DomainError } from "@/lib/admin/adminDomains";
import { import {
countTenantMembers, countTenantMembers,
createTenant, createTenant,
@@ -19,7 +19,7 @@ import {
type TenantMemberKind, type TenantMemberKind,
type TenantQuota, type TenantQuota,
type TenantRoles, type TenantRoles,
} from "@/lib/adminTenants"; } from "@/lib/admin/adminTenants";
import { formatSize } from "@/lib/format"; import { formatSize } from "@/lib/format";
import { proxiedImageUrl } from "@/lib/html"; import { proxiedImageUrl } from "@/lib/html";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
+3 -3
View File
@@ -1,9 +1,9 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import { Building2, ChevronLeft, ChevronRight, Plus, Search } from "lucide-react"; import { Building2, ChevronLeft, ChevronRight, Plus, Search } from "lucide-react";
import { can, type RoleDef } from "@/lib/adminAccess"; import { can, type RoleDef } from "@/lib/admin/adminAccess";
import { describeDirectoryError, listRoles } from "@/lib/adminDirectory"; import { describeDirectoryError, listRoles } from "@/lib/admin/adminDirectory";
import { drawableLogo, getTenants, queryTenants, type DirectoryTenant } from "@/lib/adminTenants"; import { drawableLogo, getTenants, queryTenants, type DirectoryTenant } from "@/lib/admin/adminTenants";
import { formatSize } from "@/lib/format"; import { formatSize } from "@/lib/format";
import { proxiedImageUrl } from "@/lib/html"; import { proxiedImageUrl } from "@/lib/html";
import { plural, t } from "@/lib/i18n"; import { plural, t } from "@/lib/i18n";
@@ -5,7 +5,7 @@ import { Router } from "wouter";
import { memoryLocation } from "wouter/memory-location"; import { memoryLocation } from "wouter/memory-location";
import { useSession } from "@/store/session"; import { useSession } from "@/store/session";
import type { JmapSession } from "@/jmap/types"; import type { JmapSession } from "@/jmap/types";
import type { DirectoryAccount } from "@/lib/adminDirectory"; import type { DirectoryAccount } from "@/lib/admin/adminDirectory";
import { AccountSheet } from "../AccountSheet"; import { AccountSheet } from "../AccountSheet";
import type { DirectoryContext } from "../directoryContext"; import type { DirectoryContext } from "../directoryContext";
@@ -6,7 +6,7 @@ import { memoryLocation } from "wouter/memory-location";
import { JmapMethodError } from "@/jmap/client"; import { JmapMethodError } from "@/jmap/client";
import { useSession } from "@/store/session"; import { useSession } from "@/store/session";
import type { JmapSession } from "@/jmap/types"; import type { JmapSession } from "@/jmap/types";
import type { MetricRecord } from "@/lib/adminDashboard"; import type { MetricRecord } from "@/lib/admin/adminDashboard";
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
@@ -15,8 +15,8 @@ const feeds = vi.hoisted(() => ({
metrics: (): Promise<MetricRecord[]> => Promise.resolve([]), metrics: (): Promise<MetricRecord[]> => Promise.resolve([]),
})); }));
vi.mock("@/lib/adminDashboard", async (original) => ({ vi.mock("@/lib/admin/adminDashboard", async (original) => ({
...(await original<typeof import("@/lib/adminDashboard")>()), ...(await original<typeof import("@/lib/admin/adminDashboard")>()),
countObjects: vi.fn(async (object: string) => feeds.counts[object]), countObjects: vi.fn(async (object: string) => feeds.counts[object]),
loadMetrics: vi.fn(() => feeds.metrics()), loadMetrics: vi.fn(() => feeds.metrics()),
})); }));
@@ -17,8 +17,8 @@ const domain = {
dnsZoneFile: 'example.com. IN MX 10 mail.example.com.\nexample.com. IN TXT "v=spf1 mx -all"\n', dnsZoneFile: 'example.com. IN MX 10 mail.example.com.\nexample.com. IN TXT "v=spf1 mx -all"\n',
}; };
vi.mock("@/lib/adminDomains", async (original) => ({ vi.mock("@/lib/admin/adminDomains", async (original) => ({
...(await original<typeof import("@/lib/adminDomains")>()), ...(await original<typeof import("@/lib/admin/adminDomains")>()),
getDomains: vi.fn(async () => [domain]), getDomains: vi.fn(async () => [domain]),
listDkimKeys: vi.fn(async () => [{ id: "k1", "@type": "Dkim1Ed25519Sha256", selector: "v1-ed25519", stage: "active" }]), listDkimKeys: vi.fn(async () => [{ id: "k1", "@type": "Dkim1Ed25519Sha256", selector: "v1-ed25519", stage: "active" }]),
})); }));
@@ -3,7 +3,7 @@ import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useSession } from "@/store/session"; import { useSession } from "@/store/session";
import type { JmapSession } from "@/jmap/types"; import type { JmapSession } from "@/jmap/types";
import type { DirectoryGroup } from "@/lib/adminGroups"; import type { DirectoryGroup } from "@/lib/admin/adminGroups";
import type { DirectoryContext } from "../directoryContext"; import type { DirectoryContext } from "../directoryContext";
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
@@ -17,8 +17,8 @@ const api = vi.hoisted(() => ({
destroyGroup: vi.fn(async () => {}), destroyGroup: vi.fn(async () => {}),
})); }));
vi.mock("@/lib/adminGroups", async (original) => ({ vi.mock("@/lib/admin/adminGroups", async (original) => ({
...(await original<typeof import("@/lib/adminGroups")>()), ...(await original<typeof import("@/lib/admin/adminGroups")>()),
listMembers: vi.fn(async () => ({ members: api.members, total: api.members.length })), listMembers: vi.fn(async () => ({ members: api.members, total: api.members.length })),
searchUsers: vi.fn(async () => []), searchUsers: vi.fn(async () => []),
setMembership: api.setMembership, setMembership: api.setMembership,
@@ -3,13 +3,13 @@ import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useSession } from "@/store/session"; import { useSession } from "@/store/session";
import type { JmapSession } from "@/jmap/types"; import type { JmapSession } from "@/jmap/types";
import type { DirectoryList } from "@/lib/adminLists"; import type { DirectoryList } from "@/lib/admin/adminLists";
import type { DirectoryContext } from "../directoryContext"; import type { DirectoryContext } from "../directoryContext";
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const api = vi.hoisted(() => ({ updateList: vi.fn(async () => {}) })); const api = vi.hoisted(() => ({ updateList: vi.fn(async () => {}) }));
vi.mock("@/lib/adminLists", async (original) => ({ ...(await original<typeof import("@/lib/adminLists")>()), updateList: api.updateList })); vi.mock("@/lib/admin/adminLists", async (original) => ({ ...(await original<typeof import("@/lib/admin/adminLists")>()), updateList: api.updateList }));
const { ListSheet } = await import("../ListSheet"); const { ListSheet } = await import("../ListSheet");
@@ -3,13 +3,13 @@ import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useSession } from "@/store/session"; import { useSession } from "@/store/session";
import type { JmapSession } from "@/jmap/types"; import type { JmapSession } from "@/jmap/types";
import type { DirectoryRole } from "@/lib/adminRoles"; import type { DirectoryRole } from "@/lib/admin/adminRoles";
import type { PermissionEntry } from "@/lib/permissionLabels"; import type { PermissionEntry } from "@/lib/permissionLabels";
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const api = vi.hoisted(() => ({ updateRole: vi.fn(async () => {}) })); const api = vi.hoisted(() => ({ updateRole: vi.fn(async () => {}) }));
vi.mock("@/lib/adminRoles", async (original) => ({ ...(await original<typeof import("@/lib/adminRoles")>()), updateRole: api.updateRole })); vi.mock("@/lib/admin/adminRoles", async (original) => ({ ...(await original<typeof import("@/lib/admin/adminRoles")>()), updateRole: api.updateRole }));
const { RoleSheet } = await import("../RoleSheet"); const { RoleSheet } = await import("../RoleSheet");
@@ -3,7 +3,7 @@ import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useSession } from "@/store/session"; import { useSession } from "@/store/session";
import type { JmapSession } from "@/jmap/types"; import type { JmapSession } from "@/jmap/types";
import type { DirectoryTenant } from "@/lib/adminTenants"; import type { DirectoryTenant } from "@/lib/admin/adminTenants";
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
@@ -13,8 +13,8 @@ const api = vi.hoisted(() => ({
updateTenant: vi.fn(async () => {}), updateTenant: vi.fn(async () => {}),
setDomainTenant: vi.fn(async () => {}), setDomainTenant: vi.fn(async () => {}),
})); }));
vi.mock("@/lib/adminTenants", async (original) => ({ vi.mock("@/lib/admin/adminTenants", async (original) => ({
...(await original<typeof import("@/lib/adminTenants")>()), ...(await original<typeof import("@/lib/admin/adminTenants")>()),
countTenantMembers: vi.fn(async () => api.counts), countTenantMembers: vi.fn(async () => api.counts),
tenantDomains: vi.fn(async () => ({ inTenant: [{ id: "d3", name: "old-brand.example" }], unassigned: [{ id: "d4", name: "spare.example" }] })), tenantDomains: vi.fn(async () => ({ inTenant: [{ id: "d3", name: "old-brand.example" }], unassigned: [{ id: "d4", name: "spare.example" }] })),
updateTenant: api.updateTenant, updateTenant: api.updateTenant,
@@ -9,8 +9,8 @@ import type { JmapSession } from "@/jmap/types";
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const api = vi.hoisted(() => ({ queryTenants: vi.fn(async () => ({ ids: ["t1"], total: 1 })) })); const api = vi.hoisted(() => ({ queryTenants: vi.fn(async () => ({ ids: ["t1"], total: 1 })) }));
vi.mock("@/lib/adminTenants", async (original) => ({ vi.mock("@/lib/admin/adminTenants", async (original) => ({
...(await original<typeof import("@/lib/adminTenants")>()), ...(await original<typeof import("@/lib/admin/adminTenants")>()),
queryTenants: api.queryTenants, queryTenants: api.queryTenants,
getTenants: vi.fn(async () => [{ id: "t1", name: "Acme Corp", quotas: {}, usedDiskQuota: 0 }]), getTenants: vi.fn(async () => [{ id: "t1", name: "Acme Corp", quotas: {}, usedDiskQuota: 0 }]),
})); }));
+2 -2
View File
@@ -1,5 +1,5 @@
import type { RoleDef } from "@/lib/adminAccess"; import type { RoleDef } from "@/lib/admin/adminAccess";
import type { DirectoryAccount, DirectoryDomain } from "@/lib/adminDirectory"; import type { DirectoryAccount, DirectoryDomain } from "@/lib/admin/adminDirectory";
import { t } from "@/lib/i18n"; import { t } from "@/lib/i18n";
export interface DirectoryContext { export interface DirectoryContext {
+1 -1
View File
@@ -1,6 +1,6 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { useSession } from "@/store/session"; import { useSession } from "@/store/session";
import { permissionSet, type Permissions } from "@/lib/adminAccess"; import { permissionSet, type Permissions } from "@/lib/admin/adminAccess";
/** /**
* The signed-in account's permissions, as a set, stable between renders. * The signed-in account's permissions, as a set, stable between renders.
+1 -1
View File
@@ -15,7 +15,7 @@ import type { Anchor } from "@/ui/popover";
import { CalendarContextMenu, eventColor, type CalendarContext } from "./CalendarContextMenu"; import { CalendarContextMenu, eventColor, type CalendarContext } from "./CalendarContextMenu";
import { toast } from "@/ui/toast"; import { toast } from "@/ui/toast";
import { askEditScope, droppedMessage, runScoped } from "./scope"; import { askEditScope, droppedMessage, runScoped } from "./scope";
import { canDragEvent, dayDelta, moveByDaysPatch, movePatch, pixelsToMinutes, resizePatch, snap, type DragPatch } from "@/lib/eventDrag"; import { canDragEvent, dayDelta, moveByDaysPatch, movePatch, pixelsToMinutes, resizePatch, snap, type DragPatch } from "@/lib/calendar/eventDrag";
import { t as translate } from "@/lib/i18n"; import { t as translate } from "@/lib/i18n";
type View = "month" | "week" | "day" | "agenda"; type View = "month" | "week" | "day" | "agenda";
+2 -2
View File
@@ -13,9 +13,9 @@ import { RecipientInput } from "../compose/RecipientInput";
import { DateField, DateTimeField } from "@/ui/datefield"; import { DateField, DateTimeField } from "@/ui/datefield";
import { browserTimeZone, dateToZonedLocal, formatDuration, fromInputDateTime, listTimeZones, parseDuration, toInputDateTime, toLocalDateOnly, zonedToDate, DAY_MS, humanDuration } from "@/lib/dates"; import { browserTimeZone, dateToZonedLocal, formatDuration, fromInputDateTime, listTimeZones, parseDuration, toInputDateTime, toLocalDateOnly, zonedToDate, DAY_MS, humanDuration } from "@/lib/dates";
import { formatClock, formatNumericDate, formatWeekday, formatWeekdayDate } from "@/lib/datetime"; import { formatClock, formatNumericDate, formatWeekday, formatWeekdayDate } from "@/lib/datetime";
import { WEEKDAY_KEYS, weekdayOptions, describeRule, presetFor, ruleFromPreset, type RecurrencePreset } from "@/lib/recurrence"; import { WEEKDAY_KEYS, weekdayOptions, describeRule, presetFor, ruleFromPreset, type RecurrencePreset } from "@/lib/calendar/recurrence";
import { newKey } from "@/lib/contacts"; import { newKey } from "@/lib/contacts";
import { availabilityWindow } from "@/lib/availabilityWindow"; import { availabilityWindow } from "@/lib/calendar/availabilityWindow";
import { askEditScope, droppedMessage, runScoped } from "./scope"; import { askEditScope, droppedMessage, runScoped } from "./scope";
import { plural, t as translate } from "@/lib/i18n"; import { plural, t as translate } from "@/lib/i18n";
+1 -1
View File
@@ -6,7 +6,7 @@ import { confirmDialog } from "@/ui/dialog";
import { toast } from "@/ui/toast"; import { toast } from "@/ui/toast";
import { askDeleteScope, runScoped } from "./scope"; import { askDeleteScope, runScoped } from "./scope";
import { formatTimeRange, humanDuration, parseDuration } from "@/lib/dates"; import { formatTimeRange, humanDuration, parseDuration } from "@/lib/dates";
import { describeRule } from "@/lib/recurrence"; import { describeRule } from "@/lib/calendar/recurrence";
import { useCompose } from "@/store/compose"; import { useCompose } from "@/store/compose";
import { useSettings } from "@/store/settings"; import { useSettings } from "@/store/settings";
import { categoryOf, eventColor } from "./CalendarContextMenu"; import { categoryOf, eventColor } from "./CalendarContextMenu";
+1 -1
View File
@@ -16,7 +16,7 @@ import { rowClick } from "@/lib/listSelection";
import { MenuItem, MenuSep, MenuTitle, Popover, useMenu } from "@/ui/popover"; import { MenuItem, MenuSep, MenuTitle, Popover, useMenu } from "@/ui/popover";
import { useCompose } from "@/store/compose"; import { useCompose } from "@/store/compose";
import { useCalendar } from "@/store/calendar"; import { useCalendar } from "@/store/calendar";
import { startAppointment } from "@/lib/appointment"; import { startAppointment } from "@/lib/calendar/appointment";
import { toast } from "@/ui/toast"; import { toast } from "@/ui/toast";
import { haptic, usePullToRefresh, useTouchRow, PULL_TRIGGER } from "@/lib/touch"; import { haptic, usePullToRefresh, useTouchRow, PULL_TRIGGER } from "@/lib/touch";
import { describeSwipe, type SwipeAction, type SwipeDescriptor, type SwipeIcon } from "@/lib/swipe"; import { describeSwipe, type SwipeAction, type SwipeDescriptor, type SwipeIcon } from "@/lib/swipe";
+1 -1
View File
@@ -8,7 +8,7 @@ import { useSettings } from "@/store/settings";
import { draftFromMailto, useCompose } from "@/store/compose"; import { draftFromMailto, useCompose } from "@/store/compose";
import { useContacts } from "@/store/contacts"; import { useContacts } from "@/store/contacts";
import { useCalendar } from "@/store/calendar"; import { useCalendar } from "@/store/calendar";
import { startAppointment } from "@/lib/appointment"; import { startAppointment } from "@/lib/calendar/appointment";
import { client } from "@/jmap/client"; import { client } from "@/jmap/client";
import { SignatureBanner } from "./SignatureBanner"; import { SignatureBanner } from "./SignatureBanner";
import { useSignature } from "@/lib/smime/useSignature"; import { useSignature } from "@/lib/smime/useSignature";