feat(admin): settings page with tabs, system checks and email test
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m26s

"Inhalte" becomes Admin -> Einstellungen, grouped into tabs so each
area stands on its own: Firma & Versand, Marke, Startseite, Seiten,
Social Media, Rechtstexte and System & Tests. Tabs are plain links
(?reiter=...), bookmarkable and usable without JavaScript; saving
returns to the same tab.

System & Tests brings everything that checks the running shop into one
place:
- integration overview (Stripe, Stripe webhook, Microsoft 365 mail,
  Microsoft sign-in, Turnstile, Google reviews, DHL, ApiCore): only
  whether each is set up and the names of missing variables, never a
  value; warns when Stripe runs on test keys or mixes test/live
- the email test, now with the IPv4 network checks for
  smtp.office365.com:587 and login.microsoftonline.com:443

/admin/inhalte and /admin/e-mail-test redirect to the new page; the
separate "E-Mail-Test" menu entry is gone. The tab parameter and the
saved group are validated against a fixed list before they are used in
a redirect. Docs point to the new location and cover the ENETUNREACH
(IPv6) case.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-27 00:07:34 +02:00
1 parent b5311fae83
commit de82ad0233
17 files changed
+654 -288

No files matched your search

+36 -3
View File
@@ -1243,6 +1243,11 @@ export const en: Dictionary = {
detailLabel: "Mail server message",
configLabel: "Settings used",
authModeOAuth: "Microsoft 365, OAuth 2.0 via the SSO app",
networkHeading: "Network (IPv4)",
networkSmtp: "smtp.office365.com, port 587",
networkEntra: "login.microsoftonline.com, port 443",
networkOk: "reachable",
networkFailed: "not reachable",
missingLabel: "Missing settings",
},
adminWithdrawalsPage: {
@@ -1416,8 +1421,37 @@ export const en: Dictionary = {
chartTooltip: (day, value) => `${day}: ${value}`,
},
adminContentPage: {
tabsLabel: "Settings sections",
tabCompany: "Company & shipping",
tabBrand: "Brand",
tabHome: "Home page",
tabPages: "Pages",
tabSocial: "Social media",
tabLegal: "Legal texts",
tabSystem: "System & tests",
systemIntro: "Checks whether the shop can work with all connected services. Values from the Kubernetes secret are never shown here, only whether they are set.",
emailTestHeading: "Email delivery",
integrationsHeading: "Integrations",
integrationsIntro: "Which services are set up. If something is missing, the names of the missing values are listed — to be added to the Kubernetes secret or config map (see docs/EINRICHTUNG.md).",
statusConfigured: "Set up",
statusNotConfigured: "Not set up",
statusMissing: (names) => `Missing: ${names}`,
statusRequired: "Required",
statusOptional: "Optional",
warningStripeTest: "Test keys active — orders look successful, but no money is collected.",
warningStripeMixed: "Secret and publishable key come from different modes (test/live) — checkout will not work like this.",
integrationNames: {
stripe: "Stripe (payments)",
stripeWebhook: "Stripe webhook (payment confirmation)",
mail: "Email delivery (Microsoft 365)",
sso: "Sign in with Microsoft",
turnstile: "Cloudflare Turnstile (CAPTCHA)",
googleReviews: "Google reviews",
dhl: "DHL (shipping labels)",
apicore: "ApiCore sync",
},
backToDashboard: "Back to dashboard",
title: "Content",
title: "Settings",
intro:
"These texts appear on the website. An empty field does not mean \"blank on the page\": the original text keeps showing. Only fill in what you want to change.",
sectionImages: "Images",
@@ -1484,8 +1518,7 @@ export const en: Dictionary = {
navVouchers: "Vouchers",
navReviews: "Reviews",
navAuditLog: "Audit log",
navEmailTest: "Email test",
navContent: "Content",
navContent: "Settings",
navAriaLabel: "Admin navigation",
mobileNavAriaLabel: "Mobile admin navigation",
openMenu: "Open menu",