feat(konto): settings page for security and privacy
Adds /konto/einstellungen, which gathers two-factor authentication, "sign out on all devices", the Erntepost consent, the data export (Art. 15 GDPR) and account deletion (Art. 17 GDPR) in one place. The account overview now links there through a "Settings" card instead of listing these blocks inline. Server actions are unchanged apart from redirecting failures and revalidating the new page. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
3bc0693e4d
commit
3107a02588
10 files changed
+215
-93
No files matched your search
@@ -22,7 +22,7 @@ export async function deleteOwnAccountAction(formData: FormData): Promise<void>
|
|||||||
if (!customer) return fail("/konto", "Bitte melde dich an.");
|
if (!customer) return fail("/konto", "Bitte melde dich an.");
|
||||||
|
|
||||||
if (customer.role === "ADMIN") {
|
if (customer.role === "ADMIN") {
|
||||||
return fail("/konto", "Admin-Konten können nicht selbst gelöscht werden. Bitte kontaktiere einen anderen Admin.");
|
return fail("/konto/einstellungen", "Admin-Konten können nicht selbst gelöscht werden. Bitte kontaktiere einen anderen Admin.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const ip = await getClientIp();
|
const ip = await getClientIp();
|
||||||
@@ -33,13 +33,13 @@ export async function deleteOwnAccountAction(formData: FormData): Promise<void>
|
|||||||
ACCOUNT_PASSWORD_CONFIRM_WINDOW_MS
|
ACCOUNT_PASSWORD_CONFIRM_WINDOW_MS
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return fail("/konto", "Zu viele Versuche. Bitte versuche es später erneut.");
|
return fail("/konto/einstellungen", "Zu viele Versuche. Bitte versuche es später erneut.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const password = String(formData.get("password") ?? "");
|
const password = String(formData.get("password") ?? "");
|
||||||
const passwordValid = await verifyPassword(password, customer.passwordHash);
|
const passwordValid = await verifyPassword(password, customer.passwordHash);
|
||||||
if (!passwordValid) {
|
if (!passwordValid) {
|
||||||
return fail("/konto", "Passwort ist falsch.");
|
return fail("/konto/einstellungen", "Passwort ist falsch.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { subject, html, text } = await securityAlertEmail(await getLocale(), "accountDeleted");
|
const { subject, html, text } = await securityAlertEmail(await getLocale(), "accountDeleted");
|
||||||
|
|||||||
@@ -29,6 +29,6 @@ export async function toggleNewsletterAction(): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
revalidatePath("/konto");
|
revalidatePath("/konto/einstellungen");
|
||||||
await setFlash("success", "Deine Erntepost-Einstellung wurde gespeichert.");
|
await setFlash("success", "Deine Erntepost-Einstellung wurde gespeichert.");
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { Container, SectionHeading, Button, LinkButton } from "@/components/ui";
|
||||||
|
import { BackLink } from "@/components/back-link";
|
||||||
|
import { ShieldIcon } from "@/components/account-ui";
|
||||||
|
import { ConfirmForm } from "@/components/confirm-form";
|
||||||
|
import { getLocale } from "@/i18n/locale";
|
||||||
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
|
import { getCurrentCustomer } from "@/lib/auth";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { toggleNewsletterAction, logoutAllDevicesAction, deleteOwnAccountAction } from "../actions";
|
||||||
|
|
||||||
|
export const metadata: Metadata = { title: "Einstellungen" };
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buendelt alles, was das Konto selbst betrifft statt einer Bestellung:
|
||||||
|
* Sicherheit (Zwei-Faktor, Sitzungen) und Datenschutz (Einwilligung in die
|
||||||
|
* Erntepost, Auskunft nach Art. 15, Loeschung nach Art. 17 DSGVO). Die
|
||||||
|
* Aktionen selbst leben unveraendert in ../actions — diese Seite ist nur
|
||||||
|
* ihr gemeinsamer Ort.
|
||||||
|
*/
|
||||||
|
export default async function EinstellungenPage() {
|
||||||
|
const t = getDictionary(await getLocale());
|
||||||
|
|
||||||
|
const customer = await getCurrentCustomer();
|
||||||
|
if (!customer) redirect("/konto");
|
||||||
|
|
||||||
|
const newsletter = await prisma.newsletterSubscriber.findUnique({
|
||||||
|
where: { customerId: customer.id },
|
||||||
|
});
|
||||||
|
const isSubscribed = Boolean(newsletter && newsletter.unsubscribedAt === null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="py-20">
|
||||||
|
<Container className="max-w-2xl">
|
||||||
|
<BackLink href="/konto" label={t.account.settings.backLabel} />
|
||||||
|
<SectionHeading level="h1" title={t.account.settings.title} subtitle={t.account.settings.subtitle} />
|
||||||
|
|
||||||
|
<section aria-labelledby="einstellungen-sicherheit" className="mt-12">
|
||||||
|
<h2 id="einstellungen-sicherheit" className="text-2xl text-heading mb-4">
|
||||||
|
{t.account.settings.securityHeading}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="border border-ink/10 p-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
||||||
|
<div>
|
||||||
|
<h3 className="flex items-center gap-2 text-lg text-heading">
|
||||||
|
<ShieldIcon />
|
||||||
|
{t.account.settings.twoFactorHeading}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1 text-sm text-ink/70">
|
||||||
|
{customer.twoFactorEnabled
|
||||||
|
? t.account.settings.twoFactorActiveDescription
|
||||||
|
: t.account.settings.twoFactorInactiveDescription}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<LinkButton
|
||||||
|
href="/konto/zwei-faktor"
|
||||||
|
variant={customer.twoFactorEnabled ? "secondary" : "primary"}
|
||||||
|
className="flex-shrink-0"
|
||||||
|
>
|
||||||
|
{customer.twoFactorEnabled ? t.account.settings.twoFactorManage : t.account.settings.twoFactorSetup}
|
||||||
|
</LinkButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 border border-ink/10 p-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg text-heading">{t.account.logoutAllDevicesHeading}</h3>
|
||||||
|
<p className="mt-1 text-sm text-ink/70">{t.account.logoutAllDevicesDescription}</p>
|
||||||
|
</div>
|
||||||
|
<form action={logoutAllDevicesAction} className="flex-shrink-0">
|
||||||
|
<Button type="submit" variant="secondary">
|
||||||
|
{t.account.settings.logoutAllDevicesButton}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section aria-labelledby="einstellungen-datenschutz" className="mt-12">
|
||||||
|
<h2 id="einstellungen-datenschutz" className="text-2xl text-heading mb-4">
|
||||||
|
{t.account.privacy.heading}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="border border-ink/10 p-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg text-heading">{t.account.newsletterHeading}</h3>
|
||||||
|
<p className="mt-1 text-sm text-ink/70">
|
||||||
|
{isSubscribed ? t.account.newsletterSubscribed : t.account.newsletterNotSubscribed}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<form action={toggleNewsletterAction} className="flex-shrink-0">
|
||||||
|
<Button type="submit" variant="secondary">
|
||||||
|
{isSubscribed ? t.account.unsubscribe : t.account.subscribe}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 border border-ink/10 p-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg text-heading">{t.account.privacy.downloadDataHeading}</h3>
|
||||||
|
<p className="mt-1 text-sm text-ink/70">{t.account.privacy.downloadDataDescription}</p>
|
||||||
|
</div>
|
||||||
|
<LinkButton href="/api/konto/datenexport" variant="secondary" className="flex-shrink-0">
|
||||||
|
{t.account.privacy.downloadDataButton}
|
||||||
|
</LinkButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 border border-red-200 bg-red-50/40 p-6">
|
||||||
|
<h3 className="text-lg text-red-700">{t.account.privacy.deleteAccountHeading}</h3>
|
||||||
|
{customer.role === "ADMIN" ? (
|
||||||
|
<p className="mt-1 text-sm text-ink/70">{t.account.privacy.deleteAccountAdminBlocked}</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p className="mt-1 text-sm text-ink/70">{t.account.privacy.deleteAccountDescription}</p>
|
||||||
|
<ConfirmForm
|
||||||
|
action={deleteOwnAccountAction}
|
||||||
|
confirmMessage={t.account.privacy.deleteAccountConfirm}
|
||||||
|
className="mt-4 flex flex-wrap items-end gap-3"
|
||||||
|
>
|
||||||
|
<label className="block text-sm">
|
||||||
|
<span className="text-ink/70">{t.account.privacy.deleteAccountPasswordLabel}</span>
|
||||||
|
<input
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
autoComplete="current-password"
|
||||||
|
className="mt-1 w-full border border-ink/20 bg-background px-3 py-2 focus:outline-none focus:border-red-400"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<Button type="submit" variant="secondary" className="border-red-300 text-red-700 hover:bg-red-100">
|
||||||
|
{t.account.privacy.deleteAccountButton}
|
||||||
|
</Button>
|
||||||
|
</ConfirmForm>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import type { Metadata } from "next";
|
|||||||
import { noIndexPrivate } from "@/lib/seo";
|
import { noIndexPrivate } from "@/lib/seo";
|
||||||
|
|
||||||
// Der Kontobereich enthaelt ausschliesslich personenbezogene Seiten
|
// Der Kontobereich enthaelt ausschliesslich personenbezogene Seiten
|
||||||
// (Bestellungen, Adressen, Wunschliste, Zwei-Faktor-Einrichtung) sowie die
|
// (Bestellungen, Adressen, Wunschliste, Einstellungen, Zwei-Faktor-Einrichtung) sowie die
|
||||||
// Anmeldung selbst. Nichts davon gehoert in den Suchindex.
|
// Anmeldung selbst. Nichts davon gehoert in den Suchindex.
|
||||||
//
|
//
|
||||||
// Gesetzt einmal hier im Layout statt in jeder Unterseite: /konto hat aktuell
|
// Gesetzt einmal hier im Layout statt in jeder Unterseite: /konto hat aktuell
|
||||||
|
|||||||
+7
-80
@@ -1,23 +1,18 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Container, SectionHeading, Button, LinkButton } from "@/components/ui";
|
import { Container, SectionHeading, Button } from "@/components/ui";
|
||||||
import { AccountNavCard, OrderIcon, AddressIcon, HeartIcon, ShieldIcon } from "@/components/account-ui";
|
import { AccountNavCard, OrderIcon, AddressIcon, HeartIcon, ShieldIcon, SettingsIcon } from "@/components/account-ui";
|
||||||
import { TurnstileField } from "@/components/turnstile-widget";
|
import { TurnstileField } from "@/components/turnstile-widget";
|
||||||
import { PasswordField } from "@/components/password-field";
|
import { PasswordField } from "@/components/password-field";
|
||||||
import { ConfirmForm } from "@/components/confirm-form";
|
|
||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
import { getDictionary } from "@/i18n/get-dictionary";
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
import { getCurrentCustomer } from "@/lib/auth";
|
import { getCurrentCustomer } from "@/lib/auth";
|
||||||
import { isEntraConfigured } from "@/lib/entra";
|
import { isEntraConfigured } from "@/lib/entra";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import {
|
import {
|
||||||
registerAction,
|
registerAction,
|
||||||
loginAction,
|
loginAction,
|
||||||
logoutAction,
|
logoutAction,
|
||||||
toggleNewsletterAction,
|
|
||||||
logoutAllDevicesAction,
|
|
||||||
resendVerificationEmailAction,
|
resendVerificationEmailAction,
|
||||||
deleteOwnAccountAction,
|
|
||||||
} from "./actions";
|
} from "./actions";
|
||||||
|
|
||||||
export const metadata: Metadata = { title: "Mein Konto" };
|
export const metadata: Metadata = { title: "Mein Konto" };
|
||||||
@@ -111,11 +106,6 @@ export default async function KontoPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newsletter = await prisma.newsletterSubscriber.findUnique({
|
|
||||||
where: { customerId: customer.id },
|
|
||||||
});
|
|
||||||
const isSubscribed = Boolean(newsletter && newsletter.unsubscribedAt === null);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<Container className="max-w-2xl">
|
<Container className="max-w-2xl">
|
||||||
@@ -155,10 +145,10 @@ export default async function KontoPage() {
|
|||||||
description={t.account.navWishlist.description}
|
description={t.account.navWishlist.description}
|
||||||
/>
|
/>
|
||||||
<AccountNavCard
|
<AccountNavCard
|
||||||
href="/konto/zwei-faktor"
|
href="/konto/einstellungen"
|
||||||
icon={<ShieldIcon />}
|
icon={<SettingsIcon />}
|
||||||
title={t.account.navSecurity.title}
|
title={t.account.navSettings.title}
|
||||||
description={`${t.account.navSecurity.twoFactorLabel}: ${customer.twoFactorEnabled ? t.account.navSecurity.twoFactorActive : t.account.navSecurity.twoFactorInactive}`}
|
description={`${t.account.navSettings.description} · ${t.account.navSettings.twoFactorLabel}: ${customer.twoFactorEnabled ? t.account.navSettings.twoFactorActive : t.account.navSettings.twoFactorInactive}`}
|
||||||
/>
|
/>
|
||||||
{customer.role === "ADMIN" ? (
|
{customer.role === "ADMIN" ? (
|
||||||
<AccountNavCard
|
<AccountNavCard
|
||||||
@@ -172,74 +162,11 @@ export default async function KontoPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-10 border border-ink/10 p-6 flex items-center justify-between gap-4">
|
<form action={logoutAction} className="mt-10">
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl text-heading">{t.account.newsletterHeading}</h2>
|
|
||||||
<p className="mt-1 text-sm text-ink/70">
|
|
||||||
{isSubscribed ? t.account.newsletterSubscribed : t.account.newsletterNotSubscribed}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<form action={toggleNewsletterAction}>
|
|
||||||
<Button type="submit" variant="secondary">
|
|
||||||
{isSubscribed ? t.account.unsubscribe : t.account.subscribe}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 border border-ink/10 p-6 flex items-center justify-between gap-4">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl text-heading">{t.account.logoutAllDevicesHeading}</h2>
|
|
||||||
<p className="mt-1 text-sm text-ink/70">
|
|
||||||
{t.account.logoutAllDevicesDescription}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<form action={logoutAllDevicesAction}>
|
|
||||||
<Button type="submit" variant="secondary">
|
|
||||||
{t.account.logout}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form action={logoutAction} className="mt-6 mb-10">
|
|
||||||
<Button type="submit" variant="ghost">
|
<Button type="submit" variant="ghost">
|
||||||
{t.account.logout}
|
{t.account.logout}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h2 className="text-2xl text-heading mb-4">{t.account.privacy.heading}</h2>
|
|
||||||
<div className="border border-ink/10 p-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg text-heading">{t.account.privacy.downloadDataHeading}</h3>
|
|
||||||
<p className="mt-1 text-sm text-ink/70">{t.account.privacy.downloadDataDescription}</p>
|
|
||||||
</div>
|
|
||||||
<LinkButton href="/api/konto/datenexport" variant="secondary" className="flex-shrink-0">
|
|
||||||
{t.account.privacy.downloadDataButton}
|
|
||||||
</LinkButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 border border-red-200 bg-red-50/40 p-6">
|
|
||||||
<h3 className="text-lg text-red-700">{t.account.privacy.deleteAccountHeading}</h3>
|
|
||||||
<p className="mt-1 text-sm text-ink/70">{t.account.privacy.deleteAccountDescription}</p>
|
|
||||||
<ConfirmForm
|
|
||||||
action={deleteOwnAccountAction}
|
|
||||||
confirmMessage={t.account.privacy.deleteAccountConfirm}
|
|
||||||
className="mt-4 flex flex-wrap items-end gap-3"
|
|
||||||
>
|
|
||||||
<label className="block text-sm">
|
|
||||||
<span className="text-ink/70">{t.account.privacy.deleteAccountPasswordLabel}</span>
|
|
||||||
<input
|
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
autoComplete="current-password"
|
|
||||||
className="mt-1 w-full border border-ink/20 bg-background px-3 py-2 focus:outline-none focus:border-red-400"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<Button type="submit" variant="secondary" className="border-red-300 text-red-700 hover:bg-red-100">
|
|
||||||
{t.account.privacy.deleteAccountButton}
|
|
||||||
</Button>
|
|
||||||
</ConfirmForm>
|
|
||||||
</div>
|
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default async function ZweiFaktorPage() {
|
|||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<Container className="max-w-lg">
|
<Container className="max-w-lg">
|
||||||
<FormPanel>
|
<FormPanel>
|
||||||
<BackLink href="/konto" label={t.twoFactorPage.backLabel} />
|
<BackLink href="/konto/einstellungen" label={t.twoFactorPage.backLabel} />
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
title={t.twoFactorPage.title}
|
title={t.twoFactorPage.title}
|
||||||
subtitle={t.twoFactorPage.subtitle}
|
subtitle={t.twoFactorPage.subtitle}
|
||||||
|
|||||||
@@ -265,6 +265,16 @@ export function HeartIcon() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SettingsIcon() {
|
||||||
|
return (
|
||||||
|
<svg {...ICON_PROPS}>
|
||||||
|
<path d="M4 7h10M18 7h2M4 17h2M10 17h10" />
|
||||||
|
<circle cx="16" cy="7" r="2" />
|
||||||
|
<circle cx="8" cy="17" r="2" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function ShieldIcon() {
|
export function ShieldIcon() {
|
||||||
return (
|
return (
|
||||||
<svg {...ICON_PROPS}>
|
<svg {...ICON_PROPS}>
|
||||||
|
|||||||
@@ -732,7 +732,7 @@ export const de: Dictionary = {
|
|||||||
twoFactorPage: {
|
twoFactorPage: {
|
||||||
title: "Zwei-Faktor-Authentifizierung",
|
title: "Zwei-Faktor-Authentifizierung",
|
||||||
subtitle: "Schützt dein Konto zusätzlich zum Passwort mit einem Code aus einer Authenticator-App.",
|
subtitle: "Schützt dein Konto zusätzlich zum Passwort mit einem Code aus einer Authenticator-App.",
|
||||||
backLabel: "Zurück zur Übersicht",
|
backLabel: "Zurück zu den Einstellungen",
|
||||||
activeStatus: (count) => `Zwei-Faktor-Authentifizierung ist aktiv. Noch ${count} Backup-Code${count === 1 ? "" : "s"} verfügbar.`,
|
activeStatus: (count) => `Zwei-Faktor-Authentifizierung ist aktiv. Noch ${count} Backup-Code${count === 1 ? "" : "s"} verfügbar.`,
|
||||||
disableHeading: "Deaktivieren",
|
disableHeading: "Deaktivieren",
|
||||||
passwordConfirmLabel: "Passwort zur Bestätigung",
|
passwordConfirmLabel: "Passwort zur Bestätigung",
|
||||||
@@ -1631,8 +1631,9 @@ export const de: Dictionary = {
|
|||||||
navOrders: { title: "Bestellungen", description: "Bestellhistorie, Versandstatus und Rechnungen" },
|
navOrders: { title: "Bestellungen", description: "Bestellhistorie, Versandstatus und Rechnungen" },
|
||||||
navAddresses: { title: "Adressen", description: "Lieferadressen verwalten" },
|
navAddresses: { title: "Adressen", description: "Lieferadressen verwalten" },
|
||||||
navWishlist: { title: "Wunschliste", description: "Gemerkte Produkte ansehen" },
|
navWishlist: { title: "Wunschliste", description: "Gemerkte Produkte ansehen" },
|
||||||
navSecurity: {
|
navSettings: {
|
||||||
title: "Sicherheit",
|
title: "Einstellungen",
|
||||||
|
description: "Sicherheit und Datenschutz",
|
||||||
twoFactorLabel: "Zwei-Faktor-Authentifizierung",
|
twoFactorLabel: "Zwei-Faktor-Authentifizierung",
|
||||||
twoFactorActive: "aktiv",
|
twoFactorActive: "aktiv",
|
||||||
twoFactorInactive: "nicht aktiv",
|
twoFactorInactive: "nicht aktiv",
|
||||||
@@ -1646,6 +1647,18 @@ export const de: Dictionary = {
|
|||||||
logoutAllDevicesHeading: "Alle Geräte abmelden",
|
logoutAllDevicesHeading: "Alle Geräte abmelden",
|
||||||
logoutAllDevicesDescription: "Beendet alle aktiven Sitzungen, auch dieses Gerät. Du musst dich danach neu anmelden.",
|
logoutAllDevicesDescription: "Beendet alle aktiven Sitzungen, auch dieses Gerät. Du musst dich danach neu anmelden.",
|
||||||
logout: "Abmelden",
|
logout: "Abmelden",
|
||||||
|
settings: {
|
||||||
|
title: "Einstellungen",
|
||||||
|
subtitle: "Sicherheit deines Kontos und deine Daten an einem Ort.",
|
||||||
|
backLabel: "Zurück zu meinem Konto",
|
||||||
|
securityHeading: "Sicherheit",
|
||||||
|
twoFactorHeading: "Zwei-Faktor-Authentifizierung",
|
||||||
|
twoFactorActiveDescription: "Aktiv. Bei der Anmeldung fragen wir zusätzlich einen Code aus deiner Authenticator-App ab.",
|
||||||
|
twoFactorInactiveDescription: "Nicht aktiv. Wir empfehlen, dein Konto zusätzlich mit einem Code aus einer Authenticator-App zu schützen.",
|
||||||
|
twoFactorManage: "Verwalten",
|
||||||
|
twoFactorSetup: "Einrichten",
|
||||||
|
logoutAllDevicesButton: "Überall abmelden",
|
||||||
|
},
|
||||||
privacy: {
|
privacy: {
|
||||||
heading: "Datenschutz",
|
heading: "Datenschutz",
|
||||||
downloadDataHeading: "Meine Daten herunterladen",
|
downloadDataHeading: "Meine Daten herunterladen",
|
||||||
|
|||||||
@@ -708,7 +708,7 @@ export const en: Dictionary = {
|
|||||||
twoFactorPage: {
|
twoFactorPage: {
|
||||||
title: "Two-Factor Authentication",
|
title: "Two-Factor Authentication",
|
||||||
subtitle: "Protects your account beyond just a password, with a code from an authenticator app.",
|
subtitle: "Protects your account beyond just a password, with a code from an authenticator app.",
|
||||||
backLabel: "Back to overview",
|
backLabel: "Back to settings",
|
||||||
activeStatus: (count) => `Two-factor authentication is active. ${count} backup code${count === 1 ? "" : "s"} remaining.`,
|
activeStatus: (count) => `Two-factor authentication is active. ${count} backup code${count === 1 ? "" : "s"} remaining.`,
|
||||||
disableHeading: "Disable",
|
disableHeading: "Disable",
|
||||||
passwordConfirmLabel: "Password to confirm",
|
passwordConfirmLabel: "Password to confirm",
|
||||||
@@ -1605,8 +1605,9 @@ export const en: Dictionary = {
|
|||||||
navOrders: { title: "Orders", description: "Order history, shipping status, and invoices" },
|
navOrders: { title: "Orders", description: "Order history, shipping status, and invoices" },
|
||||||
navAddresses: { title: "Addresses", description: "Manage delivery addresses" },
|
navAddresses: { title: "Addresses", description: "Manage delivery addresses" },
|
||||||
navWishlist: { title: "Wishlist", description: "View saved products" },
|
navWishlist: { title: "Wishlist", description: "View saved products" },
|
||||||
navSecurity: {
|
navSettings: {
|
||||||
title: "Security",
|
title: "Settings",
|
||||||
|
description: "Security and privacy",
|
||||||
twoFactorLabel: "Two-factor authentication",
|
twoFactorLabel: "Two-factor authentication",
|
||||||
twoFactorActive: "active",
|
twoFactorActive: "active",
|
||||||
twoFactorInactive: "not active",
|
twoFactorInactive: "not active",
|
||||||
@@ -1620,6 +1621,18 @@ export const en: Dictionary = {
|
|||||||
logoutAllDevicesHeading: "Log out all devices",
|
logoutAllDevicesHeading: "Log out all devices",
|
||||||
logoutAllDevicesDescription: "Ends all active sessions, including this device. You'll need to sign in again.",
|
logoutAllDevicesDescription: "Ends all active sessions, including this device. You'll need to sign in again.",
|
||||||
logout: "Log out",
|
logout: "Log out",
|
||||||
|
settings: {
|
||||||
|
title: "Settings",
|
||||||
|
subtitle: "Your account security and your data in one place.",
|
||||||
|
backLabel: "Back to my account",
|
||||||
|
securityHeading: "Security",
|
||||||
|
twoFactorHeading: "Two-factor authentication",
|
||||||
|
twoFactorActiveDescription: "Active. When you sign in, we also ask for a code from your authenticator app.",
|
||||||
|
twoFactorInactiveDescription: "Not active. We recommend protecting your account with an additional code from an authenticator app.",
|
||||||
|
twoFactorManage: "Manage",
|
||||||
|
twoFactorSetup: "Set up",
|
||||||
|
logoutAllDevicesButton: "Sign out everywhere",
|
||||||
|
},
|
||||||
privacy: {
|
privacy: {
|
||||||
heading: "Privacy",
|
heading: "Privacy",
|
||||||
downloadDataHeading: "Download my data",
|
downloadDataHeading: "Download my data",
|
||||||
|
|||||||
@@ -1167,7 +1167,13 @@ export interface Dictionary {
|
|||||||
navOrders: { title: string; description: string };
|
navOrders: { title: string; description: string };
|
||||||
navAddresses: { title: string; description: string };
|
navAddresses: { title: string; description: string };
|
||||||
navWishlist: { title: string; description: string };
|
navWishlist: { title: string; description: string };
|
||||||
navSecurity: { title: string; twoFactorLabel: string; twoFactorActive: string; twoFactorInactive: string };
|
navSettings: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
twoFactorLabel: string;
|
||||||
|
twoFactorActive: string;
|
||||||
|
twoFactorInactive: string;
|
||||||
|
};
|
||||||
navAdmin: { title: string; description: string };
|
navAdmin: { title: string; description: string };
|
||||||
newsletterHeading: string;
|
newsletterHeading: string;
|
||||||
newsletterSubscribed: string;
|
newsletterSubscribed: string;
|
||||||
@@ -1177,6 +1183,18 @@ export interface Dictionary {
|
|||||||
logoutAllDevicesHeading: string;
|
logoutAllDevicesHeading: string;
|
||||||
logoutAllDevicesDescription: string;
|
logoutAllDevicesDescription: string;
|
||||||
logout: string;
|
logout: string;
|
||||||
|
settings: {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
backLabel: string;
|
||||||
|
securityHeading: string;
|
||||||
|
twoFactorHeading: string;
|
||||||
|
twoFactorActiveDescription: string;
|
||||||
|
twoFactorInactiveDescription: string;
|
||||||
|
twoFactorManage: string;
|
||||||
|
twoFactorSetup: string;
|
||||||
|
logoutAllDevicesButton: string;
|
||||||
|
};
|
||||||
privacy: {
|
privacy: {
|
||||||
heading: string;
|
heading: string;
|
||||||
downloadDataHeading: string;
|
downloadDataHeading: string;
|
||||||
|
|||||||
Reference in new issue
Block a user