import { useEffect, useMemo, useState } from "react";
import { Link, useNavigate, useParams } from "react-router";
import perfumes from "../data/perfumes";
import SharedNavbar from "./SharedNavbar";
import { formatChf } from "../shop/money";
import { useShop } from "../shop/useShop";
import "./ProductDetailPage.css";
const priceToCents = (price) => {
const match = String(price).match(/(\d+)/);
return match ? Number(match[1]) * 100 : 0;
};
function ProductDetailContent({ perfumeSlug }) {
const navigate = useNavigate();
const { addToCart, subscribeToProduct, user } = useShop();
const perfume = useMemo(
() => perfumes.find((item) => item.slug === perfumeSlug) || perfumes[0],
[perfumeSlug]
);
const [selectedImage, setSelectedImage] = useState(
perfume.gallery?.[0] || perfume.image
);
const [selectedSize, setSelectedSize] = useState("sample");
const [showReviewDetails, setShowReviewDetails] = useState(false);
const [commentPage, setCommentPage] = useState(0);
const [isStructureOpen, setIsStructureOpen] = useState(false);
const [isMoodOpen, setIsMoodOpen] = useState(false);
const selectedProductId = `${perfume.slug}-${selectedSize === "sample" ? "sample" : "full"}`;
const selectedProductLabel = selectedSize === "sample" ? "Sample" : "Full Size";
const selectedPriceCents = priceToCents(perfume.prices[selectedSize]);
const sampleCredit = user?.sampleCredits?.find(
(credit) => credit.slug === perfume.slug && credit.status === "available"
);
const discountPreviewCents =
selectedSize === "full"
? Math.min(
selectedPriceCents,
(user?.discoveryStatus === "Discount available" ? 4800 : 0) +
(sampleCredit?.amount_cents || 0)
)
: 0;
const sizeOptions = [
{
key: "sample",
title: "Sample 2ml",
price: perfume.prices.sample,
note: "Zum Testen · ca. 20 Anwendungen",
},
{
key: "full",
title: "Full Size 50ml",
price: perfume.prices.full,
note: "Nachkauf · 500+ Anwendungen",
},
];
const reviewSummary = perfume.reviews || {
score: 0,
total: 0,
metrics: [],
};
const reviewComments = perfume.commentSpotlight || [];
const commentPages = [];
for (let i = 0; i < reviewComments.length; i += 2) {
commentPages.push(reviewComments.slice(i, i + 2));
}
const safeCommentPages =
commentPages.length > 0
? commentPages
: [
[
{
id: "fallback-1",
name: "Atelier",
title: "Noch keine Stimmen",
text: "Für diesen Duft sind aktuell noch keine Kommentare hinterlegt.",
},
],
];
useEffect(() => {
const interval = window.setInterval(() => {
setCommentPage((prev) => (prev + 1) % safeCommentPages.length);
}, 5000);
return () => window.clearInterval(interval);
}, [safeCommentPages.length]);
return (
navigate("/")}>
←
Zurück zur Startseite
DUFTDETAIL
{perfume.name}
{[perfume.image, ...(perfume.gallery || [])]
.slice(0, 3)
.map((img, index) => (
setSelectedImage(img)}
>
))}
TRAGEHINWEIS
{perfume.dosage}
HALTBARKEIT
{perfume.longevity}
ANLASS
{perfume.occasion}
{/* --- Accordion Group Start --- */}
{/* Dropdown: Duftstruktur */}
setIsStructureOpen(!isStructureOpen)}
>
DUFTSTRUKTUR
{isStructureOpen ? "−" : "+"}
{isStructureOpen && (
PHASE 1: TOP NOTES (0–1 H)
{perfume.phases.top.map((note) => (
{note}
))}
PHASE 2: HEART NOTES (1–4 H)
{perfume.phases.heart.map((note) => (
{note}
))}
PHASE 3: BASE NOTES (4 H+)
{perfume.phases.base.map((note) => (
{note}
))}
ZUR EINORDNUNG
Die Duftstruktur zeigt, wie sich der Duft über die Zeit entfaltet:
der erste Eindruck im Auftakt, die eigentliche Signatur im Herzen
und die Spur, die lange auf Haut und Kleidung bleibt.
)}
{/* Dropdown: Moodsetting */}
setIsMoodOpen(!isMoodOpen)}
>
MOODSETTING
{isMoodOpen ? "−" : "+"}
{isMoodOpen && (
)}
{/* --- Accordion Group End --- */}
Edition 04
{perfume.name}
{perfume.shortText}
MATERIAL-KOMPOSITION
{perfume.materialTags.map((tag) => (
{tag}
))}
GRÖSSE WÄHLEN
{sizeOptions.map((option) => (
setSelectedSize(option.key)}
>
{option.title}
{option.price}
{option.note}
))}
Discovery Set wird einmalig angerechnet
Nur das erste Discovery Set erzeugt CHF 48 Guthaben. Es wird
einmal bei einem späteren Full-Size-Kauf automatisch abgezogen.
{discountPreviewCents > 0 && (
Erwarteter Preis mit Rabatt:{" "}
{formatChf(selectedPriceCents - discountPreviewCents)}
)}
Zum Set
addToCart(
selectedProductId,
1,
`${perfume.name} ${selectedProductLabel} added.`
).catch(() => {})
}
>
KAUFEN
subscribeToProduct(selectedProductId, "restock").catch(() => {})}
>
RESTOCK UPDATE ABONNIEREN
BESCHREIBUNG
PARFÜMERIE / STUDIO
{perfume.description}
HERKUNFT
{perfume.origin}
KONZENTRATION
{perfume.concentration}
EDITION
{perfume.edition}
LIEFERUNG
CH
VERSAND
Innerhalb von 1–2 Werktagen
ZUSTELLUNG
In der Regel in 5–6 Tagen bei dir
HINWEIS
Sorgfältig verpackt und geschützt versendet.
STIMMEN ZUM DUFT
{safeCommentPages.map((_, index) => (
setCommentPage(index)}
aria-label={`Kommentargruppe ${index + 1}`}
/>
))}
{safeCommentPages[commentPage].map((comment) => (
{comment.title}
{comment.text}
{comment.name}
))}
RESONANZ
Verdichtete Wahrnehmung aus bisherigen Stimmen zu Charakter,
Haltbarkeit, Sillage und Originalität.
{reviewSummary.score.toFixed(1)}
★★★★★
{reviewSummary.total} Stimmen
setShowReviewDetails((prev) => !prev)}
aria-expanded={showReviewDetails}
>
Detailbewertungen
+
{showReviewDetails && (
{reviewSummary.metrics.map((metric) => (
{metric.label}
{metric.value.toFixed(1)}
))}
Bewertung schreiben
)}
Lieber erst testen?
Bestelle ein 2ml Sample für CHF 12 oder das komplette Discovery Set
mit allen 6 Düften für CHF 48. Beide werden beim späteren Full-Size-Kauf
vollständig angerechnet.
addToCart(`${perfume.slug}-sample`, 1, `${perfume.name} Sample added.`).catch(() => {})
}
>
SAMPLE BESTELLEN – {perfume.prices.sample}
addToCart("discovery-set", 1, "Discovery Set added.").catch(() => {})}
>
DISCOVERY SET – CHF 48
);
}
function ProductDetailPage() {
const { perfumeSlug = "kalter-beton" } = useParams();
return ;
}
export default ProductDetailPage;