Charakter-Vorstellungen wieder für alle sichtbar, Shop-Karten bleiben streng

Klarstellung zur gestrigen Änderung: die persönliche Vorstellung
("Hallo, ich bin Frieda...") ist reiner Text ohne Kaufversprechen und
wird jetzt wieder für JEDEN im Admin definierten Charakter gezeigt,
auch bevor ein passendes Produkt existiert (Frieda, Sam, Siggi, Gaby,
Pingi sind wieder mit ihrem echten Vorstellungstext auf ihren Seiten
zu sehen). Die "Mehr über mich"-Schaltfläche bleibt aber weiterhin nur
bei Charakteren mit hinterlegtem Link (aktuell nur HasiDog).

Die eigentliche Shop-Karte (Foto/Preis/Kaufinfos) bleibt dagegen streng
wie gestern festgelegt: die gibt es nach wie vor NUR für Charaktere mit
echtem, im Admin zugeordnetem Produkt — kein Vorstellungstext täuscht
mehr einen kaufbaren Artikel vor. Ohne Produkt erscheinen stattdessen
die bereits vorbereiteten, namenlosen "Kommt bald"-Karten darunter.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
qcigano
2026-08-03 01:01:52 +02:00
co-authored by Claude Sonnet 5
parent 8700e2c588
commit a4aff8aa7d
4 changed files with 91 additions and 59 deletions
@@ -21,22 +21,28 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind). // Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug); const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
// Ein Charakter (aus dem Kategorien-Bereich im Admin) ist erstmal nur eine VORBEREITETE // Zwei getrennte Dinge: die persönliche Vorstellung (Story-Box, "Hallo, ich bin ...") ist reiner
// Definition — er erscheint auf der Seite NUR, wenn ihm im Admin auch wirklich ein Produkt mit // Vorstellungstext OHNE Kaufversprechen — die zeigt VanVan für JEDEN im Admin definierten
// genau diesem Charakter-Code zugeordnet wurde (das Feld "Charakter" am Produkt = seine "Rolle"). // Charakter, auch schon bevor dafür ein Produkt existiert. Die "Mehr über mich"-Schaltfläche
// Ohne zugeordnetes Produkt taucht er hier NICHT auf, auch nicht als Platzhalter — nur echte, // erscheint dabei nur, wenn der Charakter auch wirklich einen externen Link (mehrLink) hat.
// im Admin angelegte Artikel dürfen auf der Seite erscheinen. // Eine echte Shop-KARTE (Foto/Preis/Kaufinfos) bekommt ein Charakter dagegen NUR, wenn ihm im
// Admin auch wirklich ein Produkt mit genau diesem Charakter-Code zugeordnet wurde (das Feld
// "Charakter" am Produkt = seine "Rolle") — ohne echtes Produkt gibt es keine Karte, die einen
// Kauf vortäuscht.
const characters = sub.characters ?? []; const characters = sub.characters ?? [];
const characterEntries = characters const characterEntries = characters.map((char) => ({
.map((char) => ({ char, product: alleProdukte.find((p) => p.charakter === char.slug) })) char,
.filter((e) => e.product); product: alleProdukte.find((p) => p.charakter === char.slug),
}));
const characterEntriesMitProdukt = characterEntries.filter((e) => e.product);
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter. // Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product!.slug)); const zugeordneteSlugs = new Set(characterEntriesMitProdukt.map((e) => e.product!.slug));
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug)); const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
const hatCharaktere = characterEntries.length > 0; const hatCharaktere = characterEntries.length > 0;
const hatProduktKarten = characterEntriesMitProdukt.length > 0;
const hatWeitere = weitere.length > 0; const hatWeitere = weitere.length > 0;
const komplettLeer = !hatCharaktere && !hatWeitere; const komplettLeer = !hatProduktKarten && !hatWeitere;
--- ---
<Layout title={`${sub.name[lang]} ${category.name[lang]}`} description={`${sub.name[lang]} bi Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}> <Layout title={`${sub.name[lang]} ${category.name[lang]}`} description={`${sub.name[lang]} bi Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}>
<div class="container"> <div class="container">
@@ -69,9 +75,11 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
))} ))}
</div> </div>
<div class="grid grid-3"> {hatProduktKarten && (
{characterEntries.map(({ product }) => <ProductCard product={product!} lang={lang} />)} <div class="grid grid-3">
</div> {characterEntriesMitProdukt.map(({ product }) => <ProductCard product={product!} lang={lang} />)}
</div>
)}
</div> </div>
</section> </section>
)} )}
@@ -97,7 +105,7 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
{hatWeitere && ( {hatWeitere && (
<section class="section-tight"> <section class="section-tight">
<div class="container"> <div class="container">
{hatCharaktere && <h2 class="text-center small more-variants-label">{t.shop.moreVariants}</h2>} {hatProduktKarten && <h2 class="text-center small more-variants-label">{t.shop.moreVariants}</h2>}
<div class="grid grid-3">{weitere.map((p) => <ProductCard product={p} lang={lang} />)}</div> <div class="grid grid-3">{weitere.map((p) => <ProductCard product={p} lang={lang} />)}</div>
</div> </div>
</section> </section>
@@ -21,22 +21,28 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind). // Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug); const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
// Ein Charakter (aus dem Kategorien-Bereich im Admin) ist erstmal nur eine VORBEREITETE // Zwei getrennte Dinge: die persönliche Vorstellung (Story-Box, "Hallo, ich bin ...") ist reiner
// Definition — er erscheint auf der Seite NUR, wenn ihm im Admin auch wirklich ein Produkt mit // Vorstellungstext OHNE Kaufversprechen — die zeigt VanVan für JEDEN im Admin definierten
// genau diesem Charakter-Code zugeordnet wurde (das Feld "Charakter" am Produkt = seine "Rolle"). // Charakter, auch schon bevor dafür ein Produkt existiert. Die "Mehr über mich"-Schaltfläche
// Ohne zugeordnetes Produkt taucht er hier NICHT auf, auch nicht als Platzhalter — nur echte, // erscheint dabei nur, wenn der Charakter auch wirklich einen externen Link (mehrLink) hat.
// im Admin angelegte Artikel dürfen auf der Seite erscheinen. // Eine echte Shop-KARTE (Foto/Preis/Kaufinfos) bekommt ein Charakter dagegen NUR, wenn ihm im
// Admin auch wirklich ein Produkt mit genau diesem Charakter-Code zugeordnet wurde (das Feld
// "Charakter" am Produkt = seine "Rolle") — ohne echtes Produkt gibt es keine Karte, die einen
// Kauf vortäuscht.
const characters = sub.characters ?? []; const characters = sub.characters ?? [];
const characterEntries = characters const characterEntries = characters.map((char) => ({
.map((char) => ({ char, product: alleProdukte.find((p) => p.charakter === char.slug) })) char,
.filter((e) => e.product); product: alleProdukte.find((p) => p.charakter === char.slug),
}));
const characterEntriesMitProdukt = characterEntries.filter((e) => e.product);
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter. // Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product!.slug)); const zugeordneteSlugs = new Set(characterEntriesMitProdukt.map((e) => e.product!.slug));
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug)); const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
const hatCharaktere = characterEntries.length > 0; const hatCharaktere = characterEntries.length > 0;
const hatProduktKarten = characterEntriesMitProdukt.length > 0;
const hatWeitere = weitere.length > 0; const hatWeitere = weitere.length > 0;
const komplettLeer = !hatCharaktere && !hatWeitere; const komplettLeer = !hatProduktKarten && !hatWeitere;
--- ---
<Layout title={`${sub.name[lang]} ${category.name[lang]}`} description={`${sub.name[lang]} at Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}> <Layout title={`${sub.name[lang]} ${category.name[lang]}`} description={`${sub.name[lang]} at Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}>
<div class="container"> <div class="container">
@@ -69,9 +75,11 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
))} ))}
</div> </div>
<div class="grid grid-3"> {hatProduktKarten && (
{characterEntries.map(({ product }) => <ProductCard product={product!} lang={lang} />)} <div class="grid grid-3">
</div> {characterEntriesMitProdukt.map(({ product }) => <ProductCard product={product!} lang={lang} />)}
</div>
)}
</div> </div>
</section> </section>
)} )}
@@ -97,7 +105,7 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
{hatWeitere && ( {hatWeitere && (
<section class="section-tight"> <section class="section-tight">
<div class="container"> <div class="container">
{hatCharaktere && <h2 class="text-center small more-variants-label">{t.shop.moreVariants}</h2>} {hatProduktKarten && <h2 class="text-center small more-variants-label">{t.shop.moreVariants}</h2>}
<div class="grid grid-3">{weitere.map((p) => <ProductCard product={p} lang={lang} />)}</div> <div class="grid grid-3">{weitere.map((p) => <ProductCard product={p} lang={lang} />)}</div>
</div> </div>
</section> </section>
@@ -21,22 +21,28 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind). // Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug); const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
// Ein Charakter (aus dem Kategorien-Bereich im Admin) ist erstmal nur eine VORBEREITETE // Zwei getrennte Dinge: die persönliche Vorstellung (Story-Box, "Hallo, ich bin ...") ist reiner
// Definition — er erscheint auf der Seite NUR, wenn ihm im Admin auch wirklich ein Produkt mit // Vorstellungstext OHNE Kaufversprechen — die zeigt VanVan für JEDEN im Admin definierten
// genau diesem Charakter-Code zugeordnet wurde (das Feld "Charakter" am Produkt = seine "Rolle"). // Charakter, auch schon bevor dafür ein Produkt existiert. Die "Mehr über mich"-Schaltfläche
// Ohne zugeordnetes Produkt taucht er hier NICHT auf, auch nicht als Platzhalter — nur echte, // erscheint dabei nur, wenn der Charakter auch wirklich einen externen Link (mehrLink) hat.
// im Admin angelegte Artikel dürfen auf der Seite erscheinen. // Eine echte Shop-KARTE (Foto/Preis/Kaufinfos) bekommt ein Charakter dagegen NUR, wenn ihm im
// Admin auch wirklich ein Produkt mit genau diesem Charakter-Code zugeordnet wurde (das Feld
// "Charakter" am Produkt = seine "Rolle") — ohne echtes Produkt gibt es keine Karte, die einen
// Kauf vortäuscht.
const characters = sub.characters ?? []; const characters = sub.characters ?? [];
const characterEntries = characters const characterEntries = characters.map((char) => ({
.map((char) => ({ char, product: alleProdukte.find((p) => p.charakter === char.slug) })) char,
.filter((e) => e.product); product: alleProdukte.find((p) => p.charakter === char.slug),
}));
const characterEntriesMitProdukt = characterEntries.filter((e) => e.product);
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter. // Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product!.slug)); const zugeordneteSlugs = new Set(characterEntriesMitProdukt.map((e) => e.product!.slug));
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug)); const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
const hatCharaktere = characterEntries.length > 0; const hatCharaktere = characterEntries.length > 0;
const hatProduktKarten = characterEntriesMitProdukt.length > 0;
const hatWeitere = weitere.length > 0; const hatWeitere = weitere.length > 0;
const komplettLeer = !hatCharaktere && !hatWeitere; const komplettLeer = !hatProduktKarten && !hatWeitere;
--- ---
<Layout title={`${sub.name[lang]} ${category.name[lang]}`} description={`${sub.name[lang]} chez Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}> <Layout title={`${sub.name[lang]} ${category.name[lang]}`} description={`${sub.name[lang]} chez Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}>
<div class="container"> <div class="container">
@@ -69,9 +75,11 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
))} ))}
</div> </div>
<div class="grid grid-3"> {hatProduktKarten && (
{characterEntries.map(({ product }) => <ProductCard product={product!} lang={lang} />)} <div class="grid grid-3">
</div> {characterEntriesMitProdukt.map(({ product }) => <ProductCard product={product!} lang={lang} />)}
</div>
)}
</div> </div>
</section> </section>
)} )}
@@ -97,7 +105,7 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
{hatWeitere && ( {hatWeitere && (
<section class="section-tight"> <section class="section-tight">
<div class="container"> <div class="container">
{hatCharaktere && <h2 class="text-center small more-variants-label">{t.shop.moreVariants}</h2>} {hatProduktKarten && <h2 class="text-center small more-variants-label">{t.shop.moreVariants}</h2>}
<div class="grid grid-3">{weitere.map((p) => <ProductCard product={p} lang={lang} />)}</div> <div class="grid grid-3">{weitere.map((p) => <ProductCard product={p} lang={lang} />)}</div>
</div> </div>
</section> </section>
@@ -21,26 +21,31 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
// Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind). // Alle Produkte dieser Unterkategorie (unabhängig davon, welchem Charakter sie zugeordnet sind).
const alleProdukte = productsByUnterkategorie(category.slug, sub.slug); const alleProdukte = productsByUnterkategorie(category.slug, sub.slug);
// Ein Charakter (aus dem Kategorien-Bereich im Admin) ist erstmal nur eine VORBEREITETE // Zwei getrennte Dinge: die persönliche Vorstellung (Story-Box, "Hallo, ich bin ...") ist reiner
// Definition — er erscheint auf der Seite NUR, wenn ihm im Admin auch wirklich ein Produkt mit // Vorstellungstext OHNE Kaufversprechen — die zeigt VanVan für JEDEN im Admin definierten
// genau diesem Charakter-Code zugeordnet wurde (das Feld "Charakter" am Produkt = seine "Rolle"). // Charakter, auch schon bevor dafür ein Produkt existiert. Die "Mehr über mich"-Schaltfläche
// Ohne zugeordnetes Produkt taucht er hier NICHT auf, auch nicht als Platzhalter — nur echte, // erscheint dabei nur, wenn der Charakter auch wirklich einen externen Link (mehrLink) hat.
// im Admin angelegte Artikel dürfen auf der Seite erscheinen. // Eine echte Shop-KARTE (Foto/Preis/Kaufinfos) bekommt ein Charakter dagegen NUR, wenn ihm im
// Admin auch wirklich ein Produkt mit genau diesem Charakter-Code zugeordnet wurde (das Feld
// "Charakter" am Produkt = seine "Rolle") — ohne echtes Produkt gibt es keine Karte, die einen
// Kauf vortäuscht.
const characters = sub.characters ?? []; const characters = sub.characters ?? [];
const characterEntries = characters const characterEntries = characters.map((char) => ({
.map((char) => ({ char, product: alleProdukte.find((p) => p.charakter === char.slug) })) char,
.filter((e) => e.product); product: alleProdukte.find((p) => p.charakter === char.slug),
}));
const characterEntriesMitProdukt = characterEntries.filter((e) => e.product);
// Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter. // Produkte ohne (bekannte) Charakter-Zuordnung landen als "weitere Modelle" darunter.
const zugeordneteSlugs = new Set(characterEntries.map((e) => e.product!.slug)); const zugeordneteSlugs = new Set(characterEntriesMitProdukt.map((e) => e.product!.slug));
const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug)); const weitere = alleProdukte.filter((p) => !zugeordneteSlugs.has(p.slug));
const hatCharaktere = characterEntries.length > 0; const hatCharaktere = characterEntries.length > 0;
const hatProduktKarten = characterEntriesMitProdukt.length > 0;
const hatWeitere = weitere.length > 0; const hatWeitere = weitere.length > 0;
// Damit "schon vorbereitet aussieht", auch wenn VanVan hier noch gar keine Produkte/Charaktere // Damit "schon vorbereitet aussieht", auch wenn VanVan hier noch gar keine Produkte/Charaktere
// eingetragen hat: statt einer nackten Text-Zeile zeigen wir schon die spätere Karten-Optik, // eingetragen hat: statt einer nackten Text-Zeile zeigen wir schon die spätere Karten-Optik,
// nur eben mit Platzhaltern. Sobald echte Produkte/Charaktere dazukommen, verschwinden diese // nur eben mit Platzhaltern. Sobald echte Produkte dazukommen, verschwinden diese automatisch.
// automatisch (siehe hatCharaktere/hatWeitere oben). const komplettLeer = !hatProduktKarten && !hatWeitere;
const komplettLeer = !hatCharaktere && !hatWeitere;
--- ---
<Layout title={`${sub.name[lang]} ${category.name[lang]}`} description={`${sub.name[lang]} bei Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}> <Layout title={`${sub.name[lang]} ${category.name[lang]}`} description={`${sub.name[lang]} bei Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}>
<div class="container"> <div class="container">
@@ -75,10 +80,13 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
))} ))}
</div> </div>
{/* Kartenreihe wie im Shop-Grid — nur Foto, Preis und Kaufinfos, keine Geschichten. */} {/* Kartenreihe wie im Shop-Grid — nur Foto, Preis und Kaufinfos, keine Geschichten. Nur
<div class="grid grid-3"> Charaktere mit echtem Produkt bekommen hier eine Karte. */}
{characterEntries.map(({ product }) => <ProductCard product={product!} lang={lang} />)} {hatProduktKarten && (
</div> <div class="grid grid-3">
{characterEntriesMitProdukt.map(({ product }) => <ProductCard product={product!} lang={lang} />)}
</div>
)}
</div> </div>
</section> </section>
)} )}
@@ -104,7 +112,7 @@ const komplettLeer = !hatCharaktere && !hatWeitere;
{hatWeitere && ( {hatWeitere && (
<section class="section-tight"> <section class="section-tight">
<div class="container"> <div class="container">
{hatCharaktere && <h2 class="text-center small more-variants-label">{t.shop.moreVariants}</h2>} {hatProduktKarten && <h2 class="text-center small more-variants-label">{t.shop.moreVariants}</h2>}
<div class="grid grid-3">{weitere.map((p) => <ProductCard product={p} lang={lang} />)}</div> <div class="grid grid-3">{weitere.map((p) => <ProductCard product={p} lang={lang} />)}</div>
</div> </div>
</section> </section>