feat: Security CSRF Fix, Logout optimierung, Footer Anpassung, Rechtstexteanpassung
Docker Image bauen und veroeffentlichen / build-and-push (push) Failing after 1m33s
Docker Image bauen und veroeffentlichen / build-and-push (push) Failing after 1m33s
This commit is contained in:
1 parent
0753a3bc8a
commit
4d3100e9ba
28 files changed
+802
-121
No files matched your search
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState, useEffect, useRef, type RefObject } from "react";
|
||||
import { useState, useEffect, useRef, type RefObject, type ReactNode } from "react";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { useCartStore, cartItemCount, cartTotalCents } from "@/store/cart-store";
|
||||
import { formatPrice } from "@/lib/format";
|
||||
@@ -512,12 +512,88 @@ export function CartDrawer() {
|
||||
);
|
||||
}
|
||||
|
||||
export function Footer({
|
||||
companyInfo,
|
||||
// Erstes vollstaendiges Geschaeftsjahr — Startpunkt der im Footer
|
||||
// angezeigten Copyright-Spanne "2023–<aktuelles Jahr>". Das Endjahr wird
|
||||
// bewusst NICHT gespeichert, sondern bei jedem Render live aus
|
||||
// new Date().getFullYear() berechnet, damit es nie per Hand nachgepflegt
|
||||
// werden muss.
|
||||
const FOUNDING_YEAR = 2023;
|
||||
|
||||
const SOCIAL_ICON_PROPS = { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5 } as const;
|
||||
|
||||
function TikTokIcon() {
|
||||
return (
|
||||
<svg {...SOCIAL_ICON_PROPS} aria-hidden="true">
|
||||
<path d="M14 4v10.5a3.5 3.5 0 1 1-3.5-3.5" />
|
||||
<path d="M14 4c.3 1.8 1.6 3.6 4 4" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function YouTubeIcon() {
|
||||
return (
|
||||
<svg {...SOCIAL_ICON_PROPS} aria-hidden="true">
|
||||
<rect x="2" y="5" width="20" height="14" rx="4" />
|
||||
<path d="M10 9l6 3-6 3V9z" fill="currentColor" stroke="none" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function InstagramIcon() {
|
||||
return (
|
||||
<svg {...SOCIAL_ICON_PROPS} aria-hidden="true">
|
||||
<rect x="3" y="3" width="18" height="18" rx="5" />
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<circle cx="17.5" cy="6.5" r="0.75" fill="currentColor" stroke="none" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
interface SocialLinks {
|
||||
tiktok: string;
|
||||
youtube: string;
|
||||
instagram: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ein Icon+Name-Link wird nur gerendert, wenn im Admin-Bereich (Inhalte ->
|
||||
* Social Media) tatsaechlich eine URL hinterlegt ist — nie ein toter/
|
||||
* Platzhalter-Link (siehe getSocialLinks()/content-registry.ts).
|
||||
*/
|
||||
function SocialLink({
|
||||
href,
|
||||
name,
|
||||
icon,
|
||||
}: {
|
||||
companyInfo: { name: string; email: string; address: { street: string; zip: string; city: string } };
|
||||
href: string;
|
||||
/** Plattformname (Eigenname, z.B. "TikTok") — unlokalisiert, dient als sichtbarer Linktext. */
|
||||
name: string;
|
||||
icon: ReactNode;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={t.footer.socialAriaLabel(name)}
|
||||
className="flex items-center gap-1.5 text-cream/70 hover:text-cream transition-colors"
|
||||
>
|
||||
{icon}
|
||||
<span className="text-xs">{name}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export function Footer({
|
||||
companyInfo,
|
||||
socialLinks,
|
||||
}: {
|
||||
companyInfo: { name: string; email: string; address: { street: string; zip: string; city: string } };
|
||||
socialLinks: SocialLinks;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const hasAnySocialLink = socialLinks.tiktok || socialLinks.youtube || socialLinks.instagram;
|
||||
|
||||
const wabenhainLabels: Record<(typeof footerNav.wabenhain)[number]["key"], string> = {
|
||||
aboutUs: t.footer.aboutUs,
|
||||
@@ -527,8 +603,6 @@ export function Footer({
|
||||
};
|
||||
const einkaufenLabels: Record<(typeof footerNav.einkaufen)[number]["key"], string> = {
|
||||
shop: t.nav.shop,
|
||||
shipping: t.footer.shipping,
|
||||
pickup: t.footer.pickup,
|
||||
businessCustomers: t.footer.businessCustomers,
|
||||
trackOrder: t.footer.trackOrder,
|
||||
};
|
||||
@@ -565,8 +639,25 @@ export function Footer({
|
||||
<FooterColumn title={t.footer.columnShopping} links={footerNav.einkaufen} labels={einkaufenLabels} />
|
||||
<FooterColumn title={t.footer.columnLegal} links={footerNav.rechtliches} labels={rechtlichesLabels} />
|
||||
</div>
|
||||
<div className="mt-16 pt-8 border-t border-cream/15 flex flex-col md:flex-row justify-between gap-4 text-xs text-cream/70">
|
||||
<span>© {new Date().getFullYear()} Wabenhain</span>
|
||||
<div className="mt-16 pt-8 border-t border-cream/15 flex flex-col md:flex-row md:items-center justify-between gap-4 text-xs text-cream/70">
|
||||
<div className="flex flex-wrap items-center gap-x-6 gap-y-3">
|
||||
<span>
|
||||
© {FOUNDING_YEAR}–{new Date().getFullYear()} {companyInfo.name}
|
||||
</span>
|
||||
{hasAnySocialLink ? (
|
||||
<div className="flex items-center gap-4">
|
||||
{socialLinks.tiktok ? (
|
||||
<SocialLink href={socialLinks.tiktok} name="TikTok" icon={<TikTokIcon />} />
|
||||
) : null}
|
||||
{socialLinks.youtube ? (
|
||||
<SocialLink href={socialLinks.youtube} name="YouTube" icon={<YouTubeIcon />} />
|
||||
) : null}
|
||||
{socialLinks.instagram ? (
|
||||
<SocialLink href={socialLinks.instagram} name="Instagram" icon={<InstagramIcon />} />
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<span>{t.footer.bottomLine}</span>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
Reference in new issue
Block a user