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>
341 lines
11 KiB
JavaScript
341 lines
11 KiB
JavaScript
// 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('.', ',')}`
|
||
}
|