feat(ui): Formular-Karten, Backup-Codes als Kacheln, Gutschein zum Kopieren
- Gemeinsame FormPanel-Karte fuer Passwort- und Zwei-Faktor-Seiten - Zwei-Faktor-Code: grosses, zentriertes Ziffernfeld - Backup-Codes als nummerierte Kacheln statt langer Liste - Newsletter-Bestaetigung: Willkommensrabatt als Gutschein mit gestricheltem Rand und Kopier-Knopf (bestaetigt "Kopiert") Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
76be1292ed
commit
ee854bd77b
12 files changed
+130
-23
No files matched your search
@@ -1,5 +1,5 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Container, SectionHeading, Button } from "@/components/ui";
|
import { Container, SectionHeading, Button, FormPanel } from "@/components/ui";
|
||||||
import { BackLink } from "@/components/back-link";
|
import { BackLink } from "@/components/back-link";
|
||||||
import { TurnstileField } from "@/components/turnstile-widget";
|
import { TurnstileField } from "@/components/turnstile-widget";
|
||||||
import { requestPasswordResetAction } from "../actions";
|
import { requestPasswordResetAction } from "../actions";
|
||||||
@@ -14,7 +14,8 @@ export default async function PasswortVergessenPage(props: PageProps<"/konto/pas
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<Container className="max-w-md">
|
<Container className="max-w-lg">
|
||||||
|
<FormPanel>
|
||||||
<BackLink href="/konto" label={t.passwortVergessenPage.backLabel} />
|
<BackLink href="/konto" label={t.passwortVergessenPage.backLabel} />
|
||||||
<SectionHeading eyebrow={t.account.eyebrow} title={t.passwortVergessenPage.title} />
|
<SectionHeading eyebrow={t.account.eyebrow} title={t.passwortVergessenPage.title} />
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ export default async function PasswortVergessenPage(props: PageProps<"/konto/pas
|
|||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</FormPanel>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Container, SectionHeading, Button } from "@/components/ui";
|
import { Container, SectionHeading, Button, FormPanel } from "@/components/ui";
|
||||||
import { BackLink } from "@/components/back-link";
|
import { BackLink } from "@/components/back-link";
|
||||||
import { PasswordField } from "@/components/password-field";
|
import { PasswordField } from "@/components/password-field";
|
||||||
import { TurnstileField } from "@/components/turnstile-widget";
|
import { TurnstileField } from "@/components/turnstile-widget";
|
||||||
@@ -17,7 +17,8 @@ export default async function PasswortZuruecksetzenPage(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<Container className="max-w-md">
|
<Container className="max-w-lg">
|
||||||
|
<FormPanel>
|
||||||
<BackLink href="/konto" label={t.passwortZuruecksetzenPage.backLabel} />
|
<BackLink href="/konto" label={t.passwortZuruecksetzenPage.backLabel} />
|
||||||
<SectionHeading eyebrow={t.account.eyebrow} title={t.passwortZuruecksetzenPage.title} />
|
<SectionHeading eyebrow={t.account.eyebrow} title={t.passwortZuruecksetzenPage.title} />
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ export default async function PasswortZuruecksetzenPage(
|
|||||||
<Button type="submit">{t.passwortZuruecksetzenPage.save}</Button>
|
<Button type="submit">{t.passwortZuruecksetzenPage.save}</Button>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
</FormPanel>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Container, SectionHeading, Button } from "@/components/ui";
|
import { Container, SectionHeading, Button, FormPanel } from "@/components/ui";
|
||||||
import { BackLink } from "@/components/back-link";
|
import { BackLink } from "@/components/back-link";
|
||||||
import { TurnstileField } from "@/components/turnstile-widget";
|
import { TurnstileField } from "@/components/turnstile-widget";
|
||||||
import { getTwoFactorChallengeCustomerId } from "@/lib/auth";
|
import { getTwoFactorChallengeCustomerId } from "@/lib/auth";
|
||||||
@@ -19,7 +19,8 @@ export default async function ZweiFaktorCodePage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<Container className="max-w-md">
|
<Container className="max-w-lg">
|
||||||
|
<FormPanel>
|
||||||
<BackLink href="/konto" label={t.twoFactorCodePage.cancelLabel} />
|
<BackLink href="/konto" label={t.twoFactorCodePage.cancelLabel} />
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow={t.twoFactorCodePage.eyebrow}
|
eyebrow={t.twoFactorCodePage.eyebrow}
|
||||||
@@ -38,7 +39,7 @@ export default async function ZweiFaktorCodePage() {
|
|||||||
autoComplete="one-time-code"
|
autoComplete="one-time-code"
|
||||||
autoFocus
|
autoFocus
|
||||||
required
|
required
|
||||||
className="mt-1 w-full border border-ink/20 bg-background px-4 py-3 tracking-widest focus:outline-none focus:border-forest"
|
className="mt-2 w-full border border-ink/20 bg-background px-4 py-4 text-center font-serif text-3xl tracking-[0.4em] tabular-nums focus:outline-none focus:border-forest"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
{process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY ? (
|
{process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY ? (
|
||||||
@@ -46,6 +47,7 @@ export default async function ZweiFaktorCodePage() {
|
|||||||
) : null}
|
) : null}
|
||||||
<Button type="submit">{t.twoFactorCodePage.confirm}</Button>
|
<Button type="submit">{t.twoFactorCodePage.confirm}</Button>
|
||||||
</form>
|
</form>
|
||||||
|
</FormPanel>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Container, SectionHeading, Button } from "@/components/ui";
|
import { Container, SectionHeading, Button, FormPanel } from "@/components/ui";
|
||||||
import { getCurrentCustomer, getTwoFactorBackupCodesDisplay } from "@/lib/auth";
|
import { getCurrentCustomer, getTwoFactorBackupCodesDisplay } from "@/lib/auth";
|
||||||
import { dismissBackupCodesAction } from "../../actions";
|
import { dismissBackupCodesAction } from "../../actions";
|
||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
@@ -19,20 +19,25 @@ export default async function ZweiFaktorBackupCodesPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<Container className="max-w-md">
|
<Container className="max-w-lg">
|
||||||
|
<FormPanel>
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow={t.twoFactorBackupCodesPage.eyebrow}
|
eyebrow={t.twoFactorBackupCodesPage.eyebrow}
|
||||||
title={t.twoFactorBackupCodesPage.title}
|
title={t.twoFactorBackupCodesPage.title}
|
||||||
subtitle={t.twoFactorBackupCodesPage.subtitle}
|
subtitle={t.twoFactorBackupCodesPage.subtitle}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ul className="mt-8 border border-ink/10 divide-y divide-ink/10 font-mono text-sm">
|
<ol className="mt-8 grid grid-cols-2 gap-2">
|
||||||
{codes.map((code) => (
|
{codes.map((code, i) => (
|
||||||
<li key={code} className="px-4 py-3">
|
<li
|
||||||
|
key={code}
|
||||||
|
className="flex items-center gap-3 border border-ink/10 bg-cream/50 px-3 py-3 font-mono text-sm tabular-nums tracking-wider text-forest"
|
||||||
|
>
|
||||||
|
<span className="text-[10px] text-ink/45 tabular-nums">{String(i + 1).padStart(2, "0")}</span>
|
||||||
{code}
|
{code}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ol>
|
||||||
|
|
||||||
<p className="mt-6 text-sm text-red-700 bg-red-50 border border-red-200 px-4 py-3">
|
<p className="mt-6 text-sm text-red-700 bg-red-50 border border-red-200 px-4 py-3">
|
||||||
{t.twoFactorBackupCodesPage.warning}
|
{t.twoFactorBackupCodesPage.warning}
|
||||||
@@ -41,6 +46,7 @@ export default async function ZweiFaktorBackupCodesPage() {
|
|||||||
<form action={dismissBackupCodesAction} className="mt-8">
|
<form action={dismissBackupCodesAction} className="mt-8">
|
||||||
<Button type="submit">{t.twoFactorBackupCodesPage.done}</Button>
|
<Button type="submit">{t.twoFactorBackupCodesPage.done}</Button>
|
||||||
</form>
|
</form>
|
||||||
|
</FormPanel>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Container, SectionHeading, Button } from "@/components/ui";
|
import { Container, SectionHeading, Button, FormPanel } from "@/components/ui";
|
||||||
import { BackLink } from "@/components/back-link";
|
import { BackLink } from "@/components/back-link";
|
||||||
import { getCurrentCustomer, getTwoFactorSetup } from "@/lib/auth";
|
import { getCurrentCustomer, getTwoFactorSetup } from "@/lib/auth";
|
||||||
import { generateTotpQrCodeDataUrl } from "@/lib/two-factor";
|
import { generateTotpQrCodeDataUrl } from "@/lib/two-factor";
|
||||||
@@ -24,7 +24,8 @@ export default async function ZweiFaktorEinrichtenPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<Container className="max-w-md">
|
<Container className="max-w-lg">
|
||||||
|
<FormPanel>
|
||||||
<BackLink href="/konto/zwei-faktor" label={t.twoFactorSetupPage.backLabel} />
|
<BackLink href="/konto/zwei-faktor" label={t.twoFactorSetupPage.backLabel} />
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow={t.twoFactorSetupPage.eyebrow}
|
eyebrow={t.twoFactorSetupPage.eyebrow}
|
||||||
@@ -55,6 +56,7 @@ export default async function ZweiFaktorEinrichtenPage() {
|
|||||||
</label>
|
</label>
|
||||||
<Button type="submit">{t.twoFactorSetupPage.activate}</Button>
|
<Button type="submit">{t.twoFactorSetupPage.activate}</Button>
|
||||||
</form>
|
</form>
|
||||||
|
</FormPanel>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Container, SectionHeading, Button } from "@/components/ui";
|
import { Container, SectionHeading, Button, FormPanel } from "@/components/ui";
|
||||||
import { BackLink } from "@/components/back-link";
|
import { BackLink } from "@/components/back-link";
|
||||||
import { ShieldIcon } from "@/components/account-ui";
|
import { ShieldIcon } from "@/components/account-ui";
|
||||||
import { PasswordField } from "@/components/password-field";
|
import { PasswordField } from "@/components/password-field";
|
||||||
@@ -25,7 +25,8 @@ export default async function ZweiFaktorPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<Container className="max-w-md">
|
<Container className="max-w-lg">
|
||||||
|
<FormPanel>
|
||||||
<BackLink href="/konto" label={t.twoFactorPage.backLabel} />
|
<BackLink href="/konto" label={t.twoFactorPage.backLabel} />
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow={t.twoFactorPage.eyebrow}
|
eyebrow={t.twoFactorPage.eyebrow}
|
||||||
@@ -61,6 +62,7 @@ export default async function ZweiFaktorPage() {
|
|||||||
<Button type="submit">{t.twoFactorPage.setupButton}</Button>
|
<Button type="submit">{t.twoFactorPage.setupButton}</Button>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
</FormPanel>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
import { CopyCodeButton } from "@/components/copy-code-button";
|
||||||
import { Container, SectionHeading, LinkButton } from "@/components/ui";
|
import { Container, SectionHeading, LinkButton } from "@/components/ui";
|
||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
import { getDictionary } from "@/i18n/get-dictionary";
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
@@ -38,13 +39,24 @@ export default async function NewsletterBestaetigtPage(props: PageProps<"/newsle
|
|||||||
<p className="mt-6 text-ink/70">{text}</p>
|
<p className="mt-6 text-ink/70">{text}</p>
|
||||||
|
|
||||||
{status === "ok" ? (
|
{status === "ok" ? (
|
||||||
<div className="mt-8 border border-forest/20 bg-forest/5 p-6">
|
// Der Rabatt als Gutschein: gestrichelter Rand wie zum Ausschneiden,
|
||||||
|
// Code gross und mit einem Klick kopierbar.
|
||||||
|
<div className="relative mt-10 border-2 border-dashed border-honey/60 bg-honey/[0.06] px-6 py-8">
|
||||||
|
<span aria-hidden="true" className="absolute -left-3 top-1/2 h-6 w-6 -translate-y-1/2 rounded-full bg-background" />
|
||||||
|
<span aria-hidden="true" className="absolute -right-3 top-1/2 h-6 w-6 -translate-y-1/2 rounded-full bg-background" />
|
||||||
<p className="text-sm text-ink/70">{t.newsletterBestaetigtPage.discountHeading}</p>
|
<p className="text-sm text-ink/70">{t.newsletterBestaetigtPage.discountHeading}</p>
|
||||||
<p className="mt-2 text-2xl text-forest tracking-wide">
|
<p className="mt-2 font-serif text-2xl text-forest">
|
||||||
{t.newsletterBestaetigtPage.discountText(NEWSLETTER_DISCOUNT_PERCENT)}{" "}
|
{t.newsletterBestaetigtPage.discountText(NEWSLETTER_DISCOUNT_PERCENT)}
|
||||||
<span className="font-medium">{NEWSLETTER_DISCOUNT_CODE}</span>
|
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-2 text-xs text-ink/70">{t.newsletterBestaetigtPage.discountCopyHint}</p>
|
<p className="mt-4 font-mono text-3xl tracking-[0.2em] text-honey-dark select-all">{NEWSLETTER_DISCOUNT_CODE}</p>
|
||||||
|
<div className="mt-5 flex flex-col items-center gap-2">
|
||||||
|
<CopyCodeButton
|
||||||
|
code={NEWSLETTER_DISCOUNT_CODE}
|
||||||
|
label={t.newsletterBestaetigtPage.copyCode}
|
||||||
|
copiedLabel={t.newsletterBestaetigtPage.codeCopied}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-ink/70">{t.newsletterBestaetigtPage.discountCopyHint}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { AnimatePresence, motion } from "motion/react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kopiert einen Gutscheincode in die Zwischenablage und bestaetigt das kurz
|
||||||
|
* am Knopf selbst. Ohne Clipboard-API (aeltere Browser, unsicherer Kontext)
|
||||||
|
* bleibt der Code sichtbar und markierbar — der Knopf meldet dann nichts.
|
||||||
|
*/
|
||||||
|
export function CopyCodeButton({ code, label, copiedLabel }: { code: string; label: string; copiedLabel: string }) {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
async function copy() {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(code);
|
||||||
|
setCopied(true);
|
||||||
|
window.setTimeout(() => setCopied(false), 2000);
|
||||||
|
} catch {
|
||||||
|
// Kein Zugriff auf die Zwischenablage — der Code bleibt zum Markieren sichtbar.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={copy}
|
||||||
|
className="inline-flex h-10 items-center gap-2 border border-forest px-4 text-xs tracking-wide-caps text-forest transition-colors hover:bg-forest hover:text-cream"
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
<AnimatePresence mode="wait" initial={false}>
|
||||||
|
<motion.span
|
||||||
|
key={copied ? "done" : "idle"}
|
||||||
|
initial={{ opacity: 0, y: 5 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -5 }}
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
className="inline-flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="1.6" aria-hidden="true">
|
||||||
|
{copied ? (
|
||||||
|
<path d="M5 12.5l4.5 4.5L19 7.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<rect x="8" y="8" width="12" height="12" rx="1.5" />
|
||||||
|
<path d="M16 8V5.5A1.5 1.5 0 0 0 14.5 4h-9A1.5 1.5 0 0 0 4 5.5v9A1.5 1.5 0 0 0 5.5 16H8" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
{copied ? copiedLabel : label}
|
||||||
|
</motion.span>
|
||||||
|
</AnimatePresence>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
+19
-1
@@ -1,6 +1,6 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
||||||
import { HexMark } from "@/components/honeycomb";
|
import { HexMark, HoneycombPattern } from "@/components/honeycomb";
|
||||||
|
|
||||||
export function Container({
|
export function Container({
|
||||||
id,
|
id,
|
||||||
@@ -164,3 +164,21 @@ export function FramedMedia({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Karte fuer die kurzen Formularseiten (Passwort, Zwei-Faktor, ...): hebt
|
||||||
|
* das Formular von der Flaeche ab, mit Wabenraster in der oberen Ecke als
|
||||||
|
* leiser Markengruss.
|
||||||
|
*/
|
||||||
|
export function FormPanel({ children, className = "" }: { children: ReactNode; className?: string }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`relative overflow-hidden border border-ink/10 bg-background px-7 py-9 shadow-[0_24px_50px_-34px_rgba(36,53,43,0.45)] sm:px-10 sm:py-11 ${className}`}
|
||||||
|
>
|
||||||
|
<div aria-hidden="true" className="pointer-events-none absolute -right-8 -top-8 h-40 w-40">
|
||||||
|
<HoneycombPattern className="text-honey/30" size={14} />
|
||||||
|
</div>
|
||||||
|
<div className="relative">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -668,6 +668,8 @@ export const de: Dictionary = {
|
|||||||
discountHeading: "Dein Willkommensrabatt",
|
discountHeading: "Dein Willkommensrabatt",
|
||||||
discountText: (percent) => `${percent}% auf deine nächste Bestellung mit Code`,
|
discountText: (percent) => `${percent}% auf deine nächste Bestellung mit Code`,
|
||||||
discountCopyHint: "Code beim Checkout eingeben.",
|
discountCopyHint: "Code beim Checkout eingeben.",
|
||||||
|
copyCode: "Code kopieren",
|
||||||
|
codeCopied: "Kopiert",
|
||||||
},
|
},
|
||||||
adminReviewsPage: {
|
adminReviewsPage: {
|
||||||
backToDashboard: "Zurück zum Dashboard",
|
backToDashboard: "Zurück zum Dashboard",
|
||||||
|
|||||||
@@ -656,6 +656,8 @@ export const en: Dictionary = {
|
|||||||
discountHeading: "Your welcome discount",
|
discountHeading: "Your welcome discount",
|
||||||
discountText: (percent) => `${percent}% off your next order with code`,
|
discountText: (percent) => `${percent}% off your next order with code`,
|
||||||
discountCopyHint: "Enter the code at checkout.",
|
discountCopyHint: "Enter the code at checkout.",
|
||||||
|
copyCode: "Copy code",
|
||||||
|
codeCopied: "Copied",
|
||||||
},
|
},
|
||||||
adminReviewsPage: {
|
adminReviewsPage: {
|
||||||
backToDashboard: "Back to dashboard",
|
backToDashboard: "Back to dashboard",
|
||||||
|
|||||||
@@ -347,6 +347,8 @@ export interface Dictionary {
|
|||||||
discountHeading: string;
|
discountHeading: string;
|
||||||
discountText: (percent: number) => string;
|
discountText: (percent: number) => string;
|
||||||
discountCopyHint: string;
|
discountCopyHint: string;
|
||||||
|
copyCode: string;
|
||||||
|
codeCopied: string;
|
||||||
};
|
};
|
||||||
adminReviewsPage: {
|
adminReviewsPage: {
|
||||||
backToDashboard: string;
|
backToDashboard: string;
|
||||||
|
|||||||
Reference in new issue
Block a user