feat(admin): per-port email/Documenso/branding/reminder settings + invitations
Centralizes everything operators need to configure into the admin panel,
each setting per-port with env fallback.
New admin pages
- /admin landing page linking to every admin section as a card
- /admin/email FROM name+address, reply-to, signature/footer HTML,
optional SMTP host/port/user/pass override
- /admin/documenso API URL+key override, EOI Documenso template ID,
default EOI pathway (documenso-template vs inapp),
"Test connection" button
- /admin/branding logo URL, primary color, app name, email
header/footer HTML
- /admin/reminders port-level defaults for new interests +
port-wide daily-digest delivery window
- /admin/invitations send / list / resend / revoke CRM invitations
Per-user reminder digest
- /notifications/preferences gains a Reminder digest card:
immediate / daily / weekly / off, with HH:MM, day-of-week,
IANA timezone fields. Stored in user_profiles.preferences.reminders.
Plumbing
- port-config.ts typed accessors (getPortEmailConfig, getPortDocumensoConfig,
getPortBrandingConfig, getPortReminderConfig) — settings → env fallback.
- sendEmail accepts optional portId; resolves From/SMTP from settings
when supplied.
- documensoFetch + downloadSignedPdf accept optional portId; each public
function takes it through. checkDocumensoHealth() backs the test button.
- crm-invite.service gains listCrmInvites / revokeCrmInvite / resendCrmInvite
with audit-log entries (revoke_invite, resend_invite added to AuditAction).
- AdminLandingPage card grid + shared SettingsFormCard component to remove
per-page form boilerplate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:21:54 +02:00
|
|
|
import Link from 'next/link';
|
|
|
|
|
import {
|
|
|
|
|
Bell,
|
|
|
|
|
Briefcase,
|
|
|
|
|
Database,
|
|
|
|
|
FileText,
|
|
|
|
|
HardDrive,
|
|
|
|
|
Key,
|
|
|
|
|
LayoutDashboard,
|
|
|
|
|
Mail,
|
|
|
|
|
Palette,
|
|
|
|
|
ScrollText,
|
|
|
|
|
Settings,
|
|
|
|
|
Shield,
|
|
|
|
|
Sliders,
|
|
|
|
|
Tag,
|
|
|
|
|
Upload,
|
|
|
|
|
Users,
|
|
|
|
|
Webhook,
|
|
|
|
|
} from 'lucide-react';
|
|
|
|
|
|
|
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
feat(mobile): swap admin page headers to PageHeader
Mechanical sweep replacing the plain h1+p header markup with the
mobile-aware PageHeader primitive across 12 admin pages: index,
backup, branding, documenso, email, import, invitations, monitoring,
onboarding, reminders, reports, webhooks. Webhooks "Add Webhook"
button preserved via the actions slot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:57:52 +02:00
|
|
|
import { PageHeader } from '@/components/shared/page-header';
|
feat(admin): per-port email/Documenso/branding/reminder settings + invitations
Centralizes everything operators need to configure into the admin panel,
each setting per-port with env fallback.
New admin pages
- /admin landing page linking to every admin section as a card
- /admin/email FROM name+address, reply-to, signature/footer HTML,
optional SMTP host/port/user/pass override
- /admin/documenso API URL+key override, EOI Documenso template ID,
default EOI pathway (documenso-template vs inapp),
"Test connection" button
- /admin/branding logo URL, primary color, app name, email
header/footer HTML
- /admin/reminders port-level defaults for new interests +
port-wide daily-digest delivery window
- /admin/invitations send / list / resend / revoke CRM invitations
Per-user reminder digest
- /notifications/preferences gains a Reminder digest card:
immediate / daily / weekly / off, with HH:MM, day-of-week,
IANA timezone fields. Stored in user_profiles.preferences.reminders.
Plumbing
- port-config.ts typed accessors (getPortEmailConfig, getPortDocumensoConfig,
getPortBrandingConfig, getPortReminderConfig) — settings → env fallback.
- sendEmail accepts optional portId; resolves From/SMTP from settings
when supplied.
- documensoFetch + downloadSignedPdf accept optional portId; each public
function takes it through. checkDocumensoHealth() backs the test button.
- crm-invite.service gains listCrmInvites / revokeCrmInvite / resendCrmInvite
with audit-log entries (revoke_invite, resend_invite added to AuditAction).
- AdminLandingPage card grid + shared SettingsFormCard component to remove
per-page form boilerplate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:21:54 +02:00
|
|
|
|
|
|
|
|
interface AdminSection {
|
|
|
|
|
href: string;
|
|
|
|
|
label: string;
|
|
|
|
|
description: string;
|
|
|
|
|
icon: typeof Settings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SECTIONS: AdminSection[] = [
|
|
|
|
|
{
|
|
|
|
|
href: 'users',
|
|
|
|
|
label: 'Users',
|
|
|
|
|
description: 'CRM accounts, role assignments, and per-user residential access toggles.',
|
|
|
|
|
icon: Users,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'invitations',
|
|
|
|
|
label: 'Invitations',
|
|
|
|
|
description: 'Send invitations, track pending invites, and resend or revoke them.',
|
|
|
|
|
icon: Mail,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'roles',
|
|
|
|
|
label: 'Roles & Permissions',
|
|
|
|
|
description: 'Default permission sets and per-port role overrides.',
|
|
|
|
|
icon: Shield,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'audit',
|
|
|
|
|
label: 'Audit Log',
|
|
|
|
|
description: 'Searchable log of every authenticated mutation in the system.',
|
|
|
|
|
icon: ScrollText,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'email',
|
|
|
|
|
label: 'Email Settings',
|
|
|
|
|
description: 'From address, signatures, and per-port SMTP overrides.',
|
|
|
|
|
icon: Mail,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'documenso',
|
|
|
|
|
label: 'Documenso & EOI',
|
|
|
|
|
description: 'API credentials, EOI template, and default in-app vs Documenso pathway.',
|
|
|
|
|
icon: FileText,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'reminders',
|
|
|
|
|
label: 'Reminders',
|
|
|
|
|
description: 'Default reminder behaviour and the daily-digest delivery window.',
|
|
|
|
|
icon: Bell,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'branding',
|
|
|
|
|
label: 'Branding',
|
|
|
|
|
description: 'App name, logo, primary color, and email header/footer HTML.',
|
|
|
|
|
icon: Palette,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'settings',
|
|
|
|
|
label: 'System Settings',
|
|
|
|
|
description: 'Generic key/value configuration store for advanced flags.',
|
|
|
|
|
icon: Settings,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'webhooks',
|
|
|
|
|
label: 'Webhooks',
|
|
|
|
|
description: 'Outgoing webhook subscriptions, secrets, and delivery log.',
|
|
|
|
|
icon: Webhook,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'forms',
|
|
|
|
|
label: 'Forms',
|
|
|
|
|
description: 'Form templates used by client-facing inquiry and intake flows.',
|
|
|
|
|
icon: Sliders,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'templates',
|
|
|
|
|
label: 'Document Templates',
|
|
|
|
|
description: 'PDF + email templates with merge-field placeholders.',
|
|
|
|
|
icon: FileText,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'tags',
|
|
|
|
|
label: 'Tags',
|
|
|
|
|
description: 'Color-coded tags applied to clients, yachts, companies, and interests.',
|
|
|
|
|
icon: Tag,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'custom-fields',
|
|
|
|
|
label: 'Custom Fields',
|
|
|
|
|
description: 'Tenant-defined fields for clients, yachts, and reservations.',
|
|
|
|
|
icon: Key,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'reports',
|
|
|
|
|
label: 'Reports',
|
|
|
|
|
description: 'Saved analytics views and ad-hoc query results.',
|
|
|
|
|
icon: LayoutDashboard,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'monitoring',
|
|
|
|
|
label: 'Queue Monitoring',
|
|
|
|
|
description: 'BullMQ queue health, throughput, and retry diagnostics.',
|
|
|
|
|
icon: Database,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'import',
|
|
|
|
|
label: 'Bulk Import',
|
|
|
|
|
description: 'CSV-driven imports for clients, yachts, and reservations.',
|
|
|
|
|
icon: Upload,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'backup',
|
|
|
|
|
label: 'Backup & Restore',
|
|
|
|
|
description: 'Database snapshots and on-demand exports.',
|
|
|
|
|
icon: HardDrive,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'ports',
|
|
|
|
|
label: 'Ports',
|
|
|
|
|
description: 'Manage the marinas/ports this installation serves.',
|
|
|
|
|
icon: Briefcase,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
href: 'onboarding',
|
|
|
|
|
label: 'Onboarding',
|
|
|
|
|
description: 'Initial-setup wizard for fresh ports.',
|
|
|
|
|
icon: LayoutDashboard,
|
|
|
|
|
},
|
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view
Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema +
service skeletons committed in PRs 1-3.
PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source),
date-range picker (today/7d/30d/90d), CSV+PNG export per card.
PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard
right-rail, three-tab page (active/dismissed/resolved), socket-driven
invalidation. Bell lazy-loads list on popover open to keep cold pages
fast in non-dashboard routes.
PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count
surfaces in tab label.
PR7 Interests-by-berth tab on berth detail — replaces the stub.
PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow
banner on detail w/ Merge / Not-a-duplicate, transactional merge
consolidates receipts and archives the source.
PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in
its own (scanner) group with no dashboard chrome, dynamic per-port
manifest, OpenAI + Claude provider abstraction, admin OCR settings
page (port-level + super-admin global default w/ opt-in fallback),
test-connection endpoint, manual-entry fallback when no key is
configured. Verify form always shown before save — no ghost rows.
PR10 Audit log read view — swap to tsvector full-text search on the
existing GIN index, cursor pagination, filters for entity/action/user
/date range, batched actor-email resolution.
PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional
real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine
socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip
cleanly without their gate envs so CI stays green.
Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
|
|
|
{
|
|
|
|
|
href: 'ocr',
|
|
|
|
|
label: 'Receipt OCR',
|
|
|
|
|
description: 'Configure the AI provider used by the mobile receipt scanner.',
|
|
|
|
|
icon: ScrollText,
|
|
|
|
|
},
|
feat(admin): per-port email/Documenso/branding/reminder settings + invitations
Centralizes everything operators need to configure into the admin panel,
each setting per-port with env fallback.
New admin pages
- /admin landing page linking to every admin section as a card
- /admin/email FROM name+address, reply-to, signature/footer HTML,
optional SMTP host/port/user/pass override
- /admin/documenso API URL+key override, EOI Documenso template ID,
default EOI pathway (documenso-template vs inapp),
"Test connection" button
- /admin/branding logo URL, primary color, app name, email
header/footer HTML
- /admin/reminders port-level defaults for new interests +
port-wide daily-digest delivery window
- /admin/invitations send / list / resend / revoke CRM invitations
Per-user reminder digest
- /notifications/preferences gains a Reminder digest card:
immediate / daily / weekly / off, with HH:MM, day-of-week,
IANA timezone fields. Stored in user_profiles.preferences.reminders.
Plumbing
- port-config.ts typed accessors (getPortEmailConfig, getPortDocumensoConfig,
getPortBrandingConfig, getPortReminderConfig) — settings → env fallback.
- sendEmail accepts optional portId; resolves From/SMTP from settings
when supplied.
- documensoFetch + downloadSignedPdf accept optional portId; each public
function takes it through. checkDocumensoHealth() backs the test button.
- crm-invite.service gains listCrmInvites / revokeCrmInvite / resendCrmInvite
with audit-log entries (revoke_invite, resend_invite added to AuditAction).
- AdminLandingPage card grid + shared SettingsFormCard component to remove
per-page form boilerplate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:21:54 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export default async function AdminLandingPage({
|
|
|
|
|
params,
|
|
|
|
|
}: {
|
|
|
|
|
params: Promise<{ portSlug: string }>;
|
|
|
|
|
}) {
|
|
|
|
|
const { portSlug } = await params;
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
feat(mobile): swap admin page headers to PageHeader
Mechanical sweep replacing the plain h1+p header markup with the
mobile-aware PageHeader primitive across 12 admin pages: index,
backup, branding, documenso, email, import, invitations, monitoring,
onboarding, reminders, reports, webhooks. Webhooks "Add Webhook"
button preserved via the actions slot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:57:52 +02:00
|
|
|
<PageHeader
|
|
|
|
|
title="Administration"
|
|
|
|
|
description="Per-port configuration and system administration. Each card below opens a dedicated settings page."
|
|
|
|
|
/>
|
feat(admin): per-port email/Documenso/branding/reminder settings + invitations
Centralizes everything operators need to configure into the admin panel,
each setting per-port with env fallback.
New admin pages
- /admin landing page linking to every admin section as a card
- /admin/email FROM name+address, reply-to, signature/footer HTML,
optional SMTP host/port/user/pass override
- /admin/documenso API URL+key override, EOI Documenso template ID,
default EOI pathway (documenso-template vs inapp),
"Test connection" button
- /admin/branding logo URL, primary color, app name, email
header/footer HTML
- /admin/reminders port-level defaults for new interests +
port-wide daily-digest delivery window
- /admin/invitations send / list / resend / revoke CRM invitations
Per-user reminder digest
- /notifications/preferences gains a Reminder digest card:
immediate / daily / weekly / off, with HH:MM, day-of-week,
IANA timezone fields. Stored in user_profiles.preferences.reminders.
Plumbing
- port-config.ts typed accessors (getPortEmailConfig, getPortDocumensoConfig,
getPortBrandingConfig, getPortReminderConfig) — settings → env fallback.
- sendEmail accepts optional portId; resolves From/SMTP from settings
when supplied.
- documensoFetch + downloadSignedPdf accept optional portId; each public
function takes it through. checkDocumensoHealth() backs the test button.
- crm-invite.service gains listCrmInvites / revokeCrmInvite / resendCrmInvite
with audit-log entries (revoke_invite, resend_invite added to AuditAction).
- AdminLandingPage card grid + shared SettingsFormCard component to remove
per-page form boilerplate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:21:54 +02:00
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
|
|
|
{SECTIONS.map((s) => {
|
|
|
|
|
const Icon = s.icon;
|
|
|
|
|
return (
|
|
|
|
|
<Link
|
|
|
|
|
key={s.href}
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
|
href={`/${portSlug}/admin/${s.href}` as any}
|
|
|
|
|
className="block group"
|
|
|
|
|
>
|
|
|
|
|
<Card className="h-full transition-colors group-hover:border-primary/50 group-hover:bg-muted/30">
|
|
|
|
|
<CardHeader className="flex flex-row items-start gap-3 space-y-0 pb-2">
|
|
|
|
|
<Icon className="h-5 w-5 mt-0.5 text-muted-foreground group-hover:text-primary" />
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
<CardTitle className="text-base">{s.label}</CardTitle>
|
|
|
|
|
</div>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<CardDescription>{s.description}</CardDescription>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</Link>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|