fix: KöPi - OfferCard Layout, Bilder sequenziell, Badge Position
This commit is contained in:
@@ -93,7 +93,8 @@ async function scrapeMarktguru() {
|
|||||||
const cookies = await page.cookies('https://cdn.marktguru.de');
|
const cookies = await page.cookies('https://cdn.marktguru.de');
|
||||||
const cookieStr = cookies.map(c => `${c.name}=${c.value}`).join('; ');
|
const cookieStr = cookies.map(c => `${c.name}=${c.value}`).join('; ');
|
||||||
|
|
||||||
await Promise.all(offers.map(offer => new Promise(resolve => {
|
// Sequenziell laden damit Cookie-Session stabil bleibt
|
||||||
|
for (const offer of offers) { await new Promise(resolve => {
|
||||||
if (!offer.image || !offer.image.startsWith('https://')) return resolve();
|
if (!offer.image || !offer.image.startsWith('https://')) return resolve();
|
||||||
const req = require('https').get(offer.image, {
|
const req = require('https').get(offer.image, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -114,7 +115,7 @@ async function scrapeMarktguru() {
|
|||||||
});
|
});
|
||||||
req.on('error', () => { offer.image = null; resolve(); });
|
req.on('error', () => { offer.image = null; resolve(); });
|
||||||
req.setTimeout(8000, () => { req.destroy(); offer.image = null; resolve(); });
|
req.setTimeout(8000, () => { req.destroy(); offer.image = null; resolve(); });
|
||||||
})));
|
}); }
|
||||||
|
|
||||||
// Filtern auf Ziel-Händler + nur König Pilsener
|
// Filtern auf Ziel-Händler + nur König Pilsener
|
||||||
const filtered = offers.filter(o => {
|
const filtered = offers.filter(o => {
|
||||||
|
|||||||
@@ -30,59 +30,51 @@ function OfferCard({ offer }) {
|
|||||||
const color = publisherColor(offer.retailer);
|
const color = publisherColor(offer.retailer);
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
...S.card, padding:0, overflow:'hidden',
|
...S.card, padding:'12px 14px',
|
||||||
border:'1px solid rgba(255,255,255,0.1)',
|
border:'1px solid rgba(255,255,255,0.1)',
|
||||||
display:'flex', flexDirection:'column',
|
display:'flex', flexDirection:'column', gap:8,
|
||||||
}}>
|
}}>
|
||||||
{/* Produktbild */}
|
{/* Oben: Bild links + Info rechts */}
|
||||||
{offer.image && (
|
<div style={{ display:'flex', gap:10, alignItems:'flex-start' }}>
|
||||||
<div style={{ height:160, background:'#f5f5f5', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0, overflow:'hidden', position:'relative' }}>
|
{/* Bild */}
|
||||||
<img
|
<div style={{ width:80, height:80, flexShrink:0, background:'#f5f5f5', borderRadius:6, overflow:'hidden', display:'flex', alignItems:'center', justifyContent:'center' }}>
|
||||||
src={offer.image}
|
{offer.image
|
||||||
alt="König Pilsener"
|
? <img src={offer.image} alt="König Pilsener"
|
||||||
style={{ maxHeight:'100%', maxWidth:'100%', objectFit:'contain' }}
|
style={{ width:'100%', height:'100%', objectFit:'contain' }}
|
||||||
onError={e => { e.target.parentElement.style.display='none'; }}
|
onError={e => { e.target.parentElement.style.background='transparent'; e.target.style.display='none'; }}
|
||||||
/>
|
/>
|
||||||
{offer.oldPrice && (
|
: <span style={{ fontSize:28 }}>🍺</span>
|
||||||
<div style={{ position:'absolute', top:6, left:6, background:'#e2001a', color:'#fff',
|
}
|
||||||
borderRadius:4, padding:'2px 6px', fontSize:9, fontFamily:'monospace', fontWeight:700 }}>
|
|
||||||
-29%
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{/* Info */}
|
||||||
</div>
|
<div style={{ flex:1, minWidth:0 }}>
|
||||||
)}
|
<div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:3, flexWrap:'wrap' }}>
|
||||||
|
<span style={{
|
||||||
<div style={{ padding:'12px 14px', flex:1, display:'flex', flexDirection:'column', gap:5 }}>
|
|
||||||
{/* Händler Badge + Adresse */}
|
|
||||||
<div>
|
|
||||||
<div style={{
|
|
||||||
display:'inline-block',
|
|
||||||
background:`${color}22`, border:`1px solid ${color}55`,
|
background:`${color}22`, border:`1px solid ${color}55`,
|
||||||
borderRadius:4, padding:'2px 8px', marginBottom:3,
|
borderRadius:4, padding:'2px 7px',
|
||||||
color, fontFamily:'monospace', fontSize:10, fontWeight:700,
|
color, fontFamily:'monospace', fontSize:10, fontWeight:700,
|
||||||
}}>{offer.retailer}</div>
|
}}>{offer.retailer}</span>
|
||||||
|
{offer.oldPrice && <span style={{ background:'#e2001a', color:'#fff', borderRadius:4, padding:'2px 6px', fontSize:9, fontFamily:'monospace', fontWeight:700 }}>-29%</span>}
|
||||||
|
</div>
|
||||||
{offer.address && (
|
{offer.address && (
|
||||||
<div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:9 }}>
|
<div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:9, marginBottom:3 }}>
|
||||||
📍 {offer.address}
|
📍 {offer.address}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
<div style={{ color:'#fff', fontFamily:'monospace', fontSize:13, fontWeight:600 }}>
|
||||||
|
|
||||||
{/* König Pilsener */}
|
|
||||||
<div style={{ color:'#fff', fontFamily:'monospace', fontSize:13, fontWeight:600, lineHeight:1.3 }}>
|
|
||||||
König Pilsener
|
König Pilsener
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Flaschengröße/Beschreibung */}
|
|
||||||
{offer.description && (
|
{offer.description && (
|
||||||
<div style={{ color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:10, lineHeight:1.5 }}>
|
<div style={{ color:'rgba(255,255,255,0.45)', fontFamily:'monospace', fontSize:9, marginTop:2, lineHeight:1.4 }}>
|
||||||
{offer.description}
|
{offer.description}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Preis */}
|
{/* Preis */}
|
||||||
<div style={{ display:'flex', alignItems:'baseline', gap:8, marginTop:4 }}>
|
<div style={{ display:'flex', alignItems:'baseline', gap:8 }}>
|
||||||
<span style={{ color:GOLD, fontFamily:'Space Mono,monospace', fontSize:20, fontWeight:700 }}>
|
<span style={{ color:GOLD, fontFamily:'Space Mono,monospace', fontSize:22, fontWeight:700 }}>
|
||||||
{offer.price}
|
{offer.price}
|
||||||
</span>
|
</span>
|
||||||
{offer.oldPrice && (
|
{offer.oldPrice && (
|
||||||
@@ -93,20 +85,15 @@ function OfferCard({ offer }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Gültigkeit */}
|
{/* Gültigkeit */}
|
||||||
<div style={{ marginTop:'auto', paddingTop:6, borderTop:'1px solid rgba(255,255,255,0.06)', display:'flex', flexDirection:'column', gap:3 }}>
|
<div style={{ borderTop:'1px solid rgba(255,255,255,0.06)', paddingTop:6, display:'flex', gap:12, flexWrap:'wrap' }}>
|
||||||
{offer.validity && (
|
{offer.validity && (
|
||||||
<div style={{ color:'#f59e0b', fontFamily:'monospace', fontSize:10 }}>
|
<span style={{ color:'#f59e0b', fontFamily:'monospace', fontSize:10 }}>⏰ {offer.validity}</span>
|
||||||
⏰ {offer.validity}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{offer.dateRange && (
|
{offer.dateRange && (
|
||||||
<div style={{ color:'rgba(255,255,255,0.35)', fontFamily:'monospace', fontSize:10 }}>
|
<span style={{ color:'rgba(255,255,255,0.35)', fontFamily:'monospace', fontSize:10 }}>📅 {offer.dateRange}</span>
|
||||||
📅 Gültig: {offer.dateRange}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,24 +108,20 @@ function ProspektCard({ b }) {
|
|||||||
...S.card, padding:0, overflow:'hidden', cursor:'pointer',
|
...S.card, padding:0, overflow:'hidden', cursor:'pointer',
|
||||||
border: expiring ? '1px solid rgba(239,68,68,0.4)' : '1px solid rgba(255,255,255,0.08)',
|
border: expiring ? '1px solid rgba(239,68,68,0.4)' : '1px solid rgba(255,255,255,0.08)',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ position:'relative' }}>
|
|
||||||
{b.image && (
|
{b.image && (
|
||||||
<img src={`/api/tools/koepi/img?url=${encodeURIComponent(b.image)}`}
|
<img src={`/api/tools/koepi/img?url=${encodeURIComponent(b.image)}`}
|
||||||
alt={b.title} style={{ width:'100%', height:110, objectFit:'cover', display:'block' }}
|
alt={b.title} style={{ width:'100%', height:110, objectFit:'cover', display:'block' }}
|
||||||
onError={e => { e.target.style.display='none'; }}
|
onError={e => { e.target.style.display='none'; }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{expiring && (
|
|
||||||
<div style={{ position:'absolute', top:6, left:6 }}>
|
|
||||||
<span style={{ background:'#ef4444', color:'#fff', borderRadius:4, padding:'2px 6px', fontSize:9, fontFamily:'monospace', fontWeight:700 }}>ENDET BALD</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div style={{ padding:'10px 12px' }}>
|
<div style={{ padding:'10px 12px' }}>
|
||||||
<div style={{ display:'inline-block', background:`${color}22`, border:`1px solid ${color}44`,
|
<div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:4, flexWrap:'wrap' }}>
|
||||||
borderRadius:4, padding:'2px 8px', marginBottom:6, color, fontFamily:'monospace', fontSize:10, fontWeight:700 }}>
|
<div style={{ background:`${color}22`, border:`1px solid ${color}44`,
|
||||||
|
borderRadius:4, padding:'2px 8px', color, fontFamily:'monospace', fontSize:10, fontWeight:700 }}>
|
||||||
{b.publisher}
|
{b.publisher}
|
||||||
</div>
|
</div>
|
||||||
|
{expiring && <span style={{ background:'#ef4444', color:'#fff', borderRadius:4, padding:'2px 6px', fontSize:9, fontFamily:'monospace', fontWeight:700 }}>ENDET BALD</span>}
|
||||||
|
</div>
|
||||||
<div style={{ color:'#fff', fontFamily:'monospace', fontSize:11, marginBottom:3 }}>{b.title}</div>
|
<div style={{ color:'#fff', fontFamily:'monospace', fontSize:11, marginBottom:3 }}>{b.title}</div>
|
||||||
<div style={{ color:'rgba(255,255,255,0.4)', fontFamily:'monospace', fontSize:10, marginBottom:2 }}>
|
<div style={{ color:'rgba(255,255,255,0.4)', fontFamily:'monospace', fontSize:10, marginBottom:2 }}>
|
||||||
📍 {b.street || b.store}
|
📍 {b.street || b.store}
|
||||||
|
|||||||
Reference in New Issue
Block a user