Files
kaiser-natron/src/api/products.js
Dorian ea7d9b04cc docs: correct backend stack to PHP/MySQL and document checkout/orders/customers
Swap lingering "Python/MySQL" wording for "PHP / MySQL" across the
README, `src/api/` seam, the Pinia cart store, and the cart contract
doc. Add endpoint specs for checkout (Stripe handoff + webhook),
orders, and customers so the full plug-in surface is documented in
the same style as cart.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 10:48:11 +01:00

341 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Product catalog — frontend fixture until the PHP / MySQL backend takes over.
// Shape and helper are the same surface the API module will eventually expose.
export const products = [
{
id: 'kaiser-natron-pulver-50-g-beutel',
brand: 'Kaiser-Natron',
title: 'Kaiser-Natron® Pulver',
size: '50 g Beutel',
category: 'Pulver',
price: 1.29,
image: '/products/kaiser-natron-pulver-50-g-beutel.jpg',
href: '/shop/kaiser-natron-pulver-50-g-beutel',
inStock: true,
keywords: ['natron', 'backsoda', 'bicarbonat', 'natriumhydrogencarbonat', 'baking soda'],
},
{
id: 'kaiser-natron-pulver-250-g-grosspackung',
brand: 'Kaiser-Natron',
title: 'Kaiser-Natron® Pulver',
size: '250 g Großpackung',
category: 'Pulver',
price: 4.49,
image: '/products/cutouts/kaiser-natron-pulver-250-g-gro%C3%9Fpackung-removebg-preview%20%281%29.png',
href: '/shop/kaiser-natron-pulver-250-g-grosspackung',
inStock: true,
keywords: ['natron', 'backsoda', 'großpackung', 'bicarbonat', 'baking soda', 'vorrat'],
},
{
id: 'kaiser-natron-pulver-3490-g-eimer',
brand: 'Kaiser-Natron',
title: 'Kaiser-Natron® Pulver',
size: '3.490 g Eimer',
category: 'Pulver',
price: 29.9,
image: '/products/kaiser-natron-pulver-3.490-g-eimer.jpg',
href: '/shop/kaiser-natron-pulver-3490-g-eimer',
inStock: true,
keywords: ['natron', 'eimer', 'großgebinde', 'gastro', 'gewerbe', 'baking soda'],
},
{
id: 'kaiser-natron-tabletten-100-g-dose',
brand: 'Kaiser-Natron',
title: 'Kaiser-Natron® Tabletten',
size: '100 g Dose',
category: 'Tabletten',
price: 3.49,
image: '/products/kaiser-natron-tabletten-100-g-dose.jpg',
href: '/shop/kaiser-natron-tabletten-100-g-dose',
inStock: true,
keywords: ['natron', 'tabletten', 'magen', 'sodbrennen', 'verdauung'],
},
{
id: 'kaiser-natron-bad-500-g',
brand: 'Kaiser-Natron',
title: 'Kaiser-Natron® Bad',
size: '500 g',
category: 'Körperpflege',
price: 5.49,
image: '/products/kaiser-natron-bad-500-g.jpg',
href: '/shop/kaiser-natron-bad-500-g',
inStock: true,
keywords: ['basenbad', 'wellness', 'entspannung', 'haut', 'wanne'],
},
{
id: 'kaiser-natron-fussbad-500-g',
brand: 'Kaiser-Natron',
title: 'Kaiser-Natron® Fußbad',
size: '500 g',
category: 'Körperpflege',
price: 5.49,
image: '/products/kaiser-natron-fussbad-500-g.jpg',
href: '/shop/kaiser-natron-fussbad-500-g',
inStock: true,
keywords: ['fusspflege', 'basenbad', 'wellness', 'entspannung'],
},
{
id: 'kaiser-natron-daunenwasch-250-ml',
brand: 'Kaiser-Natron',
title: 'Kaiser-Natron® Daunenwasch',
size: '250 ml',
category: 'Wäsche',
price: 6.9,
image: '/products/kaiser-natron-daunenwasch-250-ml.jpg',
href: '/shop/kaiser-natron-daunenwasch-250-ml',
inStock: true,
keywords: ['daunen', 'wäsche', 'bettdecke', 'kissen', 'pflege'],
},
{
id: 'kaiser-natron-sport-profi-250-ml',
brand: 'Kaiser-Natron',
title: 'Kaiser-Natron® Sport Profi',
size: '250 ml',
category: 'Sport',
price: 7.9,
image: '/products/kaiser-natron-sport-profi-250-ml.jpg',
href: '/shop/kaiser-natron-sport-profi-250-ml',
inStock: true,
keywords: ['sport', 'wäsche', 'funktionskleidung', 'geruch'],
},
{
id: 'kaiser-natron-spuelmittel-500-ml',
brand: 'Kaiser-Natron',
title: 'Kaiser-Natron® Spülmittel',
size: '500 ml',
category: 'Reinigung',
price: 3.9,
image: '/products/kaiser-natron-spuelmittel-500-ml.jpg',
href: '/shop/kaiser-natron-spuelmittel-500-ml',
inStock: true,
keywords: ['geschirr', 'spülen', 'küche', 'reinigung'],
},
{
id: 'kaiser-natron-allzweck-reiniger-750-ml',
brand: 'Kaiser-Natron',
title: 'Kaiser-Natron® Allzweck-Reiniger',
size: '750 ml',
category: 'Reinigung',
price: 4.49,
image: '/products/kaiser-natron-allzweck-reiniger-750-ml.jpg',
href: '/shop/kaiser-natron-allzweck-reiniger-750-ml',
inStock: true,
keywords: ['reinigung', 'allzweck', 'universal', 'haushalt'],
},
{
id: 'kaiser-natron-allzweck-spray-500-ml',
brand: 'Kaiser-Natron',
title: 'Kaiser-Natron® Allzweck-Spray',
size: '500 ml',
category: 'Reinigung',
price: 4.9,
image: '/products/kaiser-natron-allzweck-spray-500-ml.jpg',
href: '/shop/kaiser-natron-allzweck-spray-500-ml',
inStock: true,
keywords: ['reinigung', 'allzweck', 'spray', 'haushalt'],
},
{
id: 'holste-wasch-soda-500-g-beutel',
brand: 'Holste',
title: 'Holste Wasch-Soda',
size: '500 g Beutel',
category: 'Wäsche',
price: 2.9,
image: '/products/holste-wasch-soda-500-g-beutel.jpg',
href: '/shop/holste-wasch-soda-500-g-beutel',
inStock: true,
keywords: ['soda', 'wasch-soda', 'waschen', 'natriumcarbonat'],
},
{
id: 'holste-handwaschpaste-500-ml',
brand: 'Holste',
title: 'Holste Handwaschpaste',
size: '500 ml',
category: 'Reinigung',
price: 6.9,
image: '/products/holste-handwaschpaste-500-ml.jpg',
href: '/shop/holste-handwaschpaste-500-ml',
inStock: true,
keywords: ['hände', 'werkstatt', 'arbeit', 'grobe verschmutzung'],
},
{
id: 'holste-kalk-und-urinsteinloeser-750-ml',
brand: 'Holste',
title: 'Holste Kalk- und Urinsteinlöser',
size: '750 ml',
category: 'Reinigung',
price: 5.9,
image: '/products/holste-kalk--und-urinsteinloeser-750-ml.jpg',
href: '/shop/holste-kalk-und-urinsteinloeser-750-ml',
inStock: true,
keywords: ['kalk', 'urinstein', 'bad', 'wc', 'toilette', 'entkalker'],
},
{
id: 'holste-reisstaerke-250-g-faltschachtel',
brand: 'Holste',
title: 'Holste Reisstärke',
size: '250 g Faltschachtel',
category: 'Wäsche',
price: 3.9,
image: '/products/holste-reisstaerke-250-g-faltschachtel.jpg',
href: '/shop/holste-reisstaerke-250-g-faltschachtel',
inStock: true,
keywords: ['stärke', 'reisstärke', 'wäsche', 'bügeln'],
},
{
id: 'holste-schmierseife-fluessig-1-l-flasche',
brand: 'Holste',
title: 'Holste Schmierseife flüssig',
size: '1 l Flasche',
category: 'Reinigung',
price: 6.49,
image: '/products/holste-schmierseife-fluessig-1-l-flasche.jpg',
href: '/shop/holste-schmierseife-fluessig-1-l-flasche',
inStock: true,
keywords: ['schmierseife', 'naturseife', 'boden', 'reinigung'],
},
{
id: 'holste-zitronensaeure-entkalker-fluessig-500-ml',
brand: 'Holste',
title: 'Holste Zitronensäure-Entkalker flüssig',
size: '500 ml',
category: 'Reinigung',
price: 4.9,
image: '/products/holste-zitronensaeure-entkalker-fluessig-500-ml.jpg',
href: '/shop/holste-zitronensaeure-entkalker-fluessig-500-ml',
inStock: true,
keywords: ['zitronensäure', 'entkalker', 'kalk', 'haushalt', 'küche'],
},
{
id: 'gazelle-waeschestaerke-1000-ml-flasche',
brand: 'Gazelle',
title: 'Gazelle Wäschestärke',
size: '1.000 ml Flasche',
category: 'Wäsche',
price: 5.9,
image: '/products/gazelle-waeschestaerke-1000-ml-flasche.jpg',
href: '/shop/gazelle-waeschestaerke-1000-ml-flasche',
inStock: true,
keywords: ['wäschestärke', 'stärke', 'bügeln', 'tischwäsche'],
},
{
id: 'gruene-tante-mit-quarzmehl-500-ml-dose',
brand: 'Grüne Tante',
title: 'Grüne Tante mit Quarzmehl',
size: '500 ml Dose',
category: 'Reinigung',
price: 7.9,
image: '/products/gruene-tante-mit-quarzmehl-500-ml-dose.jpg',
href: '/shop/gruene-tante-mit-quarzmehl-500-ml-dose',
inStock: true,
keywords: ['handreiniger', 'werkstatt', 'quarzmehl', 'grobe verschmutzung'],
},
{
id: 'linda-fleckenweg-200-ml-tube',
brand: 'Linda',
title: 'Linda Fleckenweg',
size: '200 ml Tube',
category: 'Wäsche',
price: 4.9,
image: '/products/linda-fleckenweg-200-ml-tube.jpg',
href: '/shop/linda-fleckenweg-200-ml-tube',
inStock: true,
keywords: ['fleck', 'fleckenentferner', 'vorbehandlung', 'wäsche'],
},
{
id: 'linda-handreiniger-der-kraftvolle-200-g-tube',
brand: 'Linda',
title: 'Linda Handreiniger Der Kraftvolle',
size: '200 g Tube',
category: 'Reinigung',
price: 5.9,
image: '/products/linda-handreiniger-der-kraftvolle-200-g-tube.jpg',
href: '/shop/linda-handreiniger-der-kraftvolle-200-g-tube',
inStock: true,
keywords: ['handreiniger', 'werkstatt', 'öl', 'fett'],
},
{
id: 'linda-neutral-375-ml-dose',
brand: 'Linda',
title: 'Linda Neutral',
size: '375 ml Dose',
category: 'Reinigung',
price: 5.49,
image: '/products/linda-neutral-375-ml-dose.jpg',
href: '/shop/linda-neutral-375-ml-dose',
inStock: true,
keywords: ['handreiniger', 'neutral', 'haut', 'empfindlich'],
},
]
// Normalize for search: lowercase + fold German diacritics so "Spülmittel"
// matches "spulmittel" and "großpackung" matches "grosspackung".
function normalize(input) {
if (!input) return ''
return String(input)
.toLowerCase()
.replace(/ß/g, 'ss')
.replace(/ä/g, 'ae')
.replace(/ö/g, 'oe')
.replace(/ü/g, 'ue')
.normalize('NFD')
.replace(/[̀-ͯ]/g, '')
}
function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
// Ranked match: every query term must hit at least one field; fields have
// weights so a title hit outranks a keyword hit. Exact > startsWith >
// word-boundary > substring. Terms AND together (all must match something).
export function searchProducts(query, list = products, limit = 8) {
const q = normalize(query).trim()
if (!q) return []
const terms = q.split(/\s+/).filter(Boolean)
const scored = []
for (const product of list) {
const haystacks = [
{ text: normalize(product.title), weight: 3 },
{ text: normalize(product.brand), weight: 2.2 },
{ text: normalize(product.category), weight: 1.4 },
{ text: normalize(product.size), weight: 1.2 },
{ text: normalize((product.keywords || []).join(' ')), weight: 1 },
]
let total = 0
let allTermsMatched = true
for (const term of terms) {
const boundary = new RegExp(`\\b${escapeRegExp(term)}`)
let best = 0
for (const { text, weight } of haystacks) {
if (!text) continue
let s = 0
if (text === term) s = 100
else if (text.startsWith(term)) s = 80
else if (boundary.test(text)) s = 55
else if (text.includes(term)) s = 25
if (s * weight > best) best = s * weight
}
if (best === 0) {
allTermsMatched = false
break
}
total += best
}
if (allTermsMatched) scored.push({ product, score: total })
}
scored.sort((a, b) => b.score - a.score)
return scored.slice(0, limit).map((s) => s.product)
}
export function formatPrice(amount, currency = '€') {
const num = typeof amount === 'number' ? amount : Number(amount)
if (!Number.isFinite(num)) return ''
return `${currency} ${num.toFixed(2).replace('.', ',')}`
}