2026-05-04 22:53:06 +02:00
import {
SettingsFormCard ,
type SettingFieldDef ,
} from '@/components/admin/shared/settings-form-card' ;
import { UmamiTestButton } from '@/components/admin/website-analytics/umami-test-button' ;
import { PageHeader } from '@/components/shared/page-header' ;
/ * *
2026-05-20 15:53:41 +02:00
* Per - port Umami credentials . Self - hosted Umami uses username + password →
* JWT bearer token ( https : //docs.umami.is/docs/api/authentication); the
* service POSTs to / api / auth / login and caches the JWT in - memory . Umami
* Cloud installations use a long - lived API key instead ; the optional field
* below covers that case . All credentials are port - scoped so different
* ports can point at different Umami instances .
2026-05-04 22:53:06 +02:00
* /
const FIELDS : SettingFieldDef [ ] = [
{
key : 'umami_api_url' ,
2026-05-20 15:53:41 +02:00
label : 'Umami URL' ,
2026-05-04 22:53:06 +02:00
description :
'Base URL of the Umami instance, e.g. https://analytics.portnimara.com (no trailing slash, no /api).' ,
type : 'string' ,
placeholder : 'https://analytics.portnimara.com' ,
defaultValue : '' ,
} ,
{
key : 'umami_username' ,
label : 'Username' ,
2026-05-20 15:53:41 +02:00
description : 'Umami login username (self-hosted).' ,
2026-05-04 22:53:06 +02:00
type : 'string' ,
placeholder : 'admin' ,
defaultValue : '' ,
} ,
{
key : 'umami_password' ,
label : 'Password' ,
2026-05-20 15:53:41 +02:00
description :
'Umami login password (self-hosted). Exchanged for a JWT via /api/auth/login on each port; the JWT is cached for 55 minutes. Stored AES-256-GCM at rest.' ,
2026-05-04 22:53:06 +02:00
type : 'password' ,
defaultValue : '' ,
} ,
{
key : 'umami_website_id' ,
label : 'Website ID' ,
description :
'UUID of this port’ s website inside Umami. Find it in Umami → Settings → Websites → Edit → Website ID.' ,
type : 'string' ,
placeholder : '00000000-0000-0000-0000-000000000000' ,
defaultValue : '' ,
} ,
2026-05-20 15:53:41 +02:00
{
key : 'umami_api_token' ,
chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
2026-05-23 00:52:59 +02:00
label : 'API key (Umami Cloud only - optional)' ,
2026-05-20 15:53:41 +02:00
description :
chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
2026-05-23 00:52:59 +02:00
'Only fill this if you use Umami Cloud, which uses a long-lived API key instead of username/password. Leave blank for self-hosted installs - the username + password above are used instead. Stored AES-256-GCM at rest.' ,
2026-05-20 15:53:41 +02:00
type : 'password' ,
defaultValue : '' ,
} ,
] ;
chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
2026-05-23 00:52:59 +02:00
// Tracking-pixel kill switch - opt-in per port. When enabled, outbound
2026-05-20 15:53:41 +02:00
// sales sends embed a 1× 1 pixel pointing at /api/public/email-pixel that
// records opens to `document_send_opens` and cross-posts to Umami.
const TRACKING_FIELDS : SettingFieldDef [ ] = [
{
key : 'email_open_tracking_enabled' ,
label : 'Track email opens' ,
description :
chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
2026-05-23 00:52:59 +02:00
'Embeds an invisible 1× 1 tracking pixel in outbound sales emails. Each open is recorded in the CRM and cross-posted to Umami as an "email-opened" event. Apple Mail privacy proxy will over-count; clients that block images will under-count - standard email-tracking caveats apply.' ,
2026-05-20 15:53:41 +02:00
type : 'boolean' ,
defaultValue : false ,
} ,
2026-05-04 22:53:06 +02:00
] ;
export default function WebsiteAnalyticsSettingsPage() {
return (
< div className = "space-y-6" >
< PageHeader
title = "Website analytics (Umami)"
description = "Connect this port to its Umami website to display traffic, top pages, referrers, and conversion data on the Website Analytics dashboard."
/ >
< SettingsFormCard
title = "Umami connection"
2026-05-20 15:53:41 +02:00
description = "Self-hosted Umami: enter URL + username + password + website ID. Umami Cloud: enter URL + API key (Cloud field at the bottom) + website ID. Each port can point at its own Umami instance, or share one instance with different website IDs."
2026-05-04 22:53:06 +02:00
fields = { FIELDS }
extra = { < UmamiTestButton / > }
/ >
2026-05-20 15:53:41 +02:00
< SettingsFormCard
title = "Email open tracking"
description = "Opt-in tracking for outbound sales emails. Disabled by default."
fields = { TRACKING_FIELDS }
/ >
2026-05-04 22:53:06 +02:00
< / div >
) ;
}