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 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();
|
||||
const req = require('https').get(offer.image, {
|
||||
headers: {
|
||||
@@ -114,7 +115,7 @@ async function scrapeMarktguru() {
|
||||
});
|
||||
req.on('error', () => { offer.image = null; resolve(); });
|
||||
req.setTimeout(8000, () => { req.destroy(); offer.image = null; resolve(); });
|
||||
})));
|
||||
}); }
|
||||
|
||||
// Filtern auf Ziel-Händler + nur König Pilsener
|
||||
const filtered = offers.filter(o => {
|
||||
|
||||
@@ -30,81 +30,68 @@ function OfferCard({ offer }) {
|
||||
const color = publisherColor(offer.retailer);
|
||||
return (
|
||||
<div style={{
|
||||
...S.card, padding:0, overflow:'hidden',
|
||||
...S.card, padding:'12px 14px',
|
||||
border:'1px solid rgba(255,255,255,0.1)',
|
||||
display:'flex', flexDirection:'column',
|
||||
display:'flex', flexDirection:'column', gap:8,
|
||||
}}>
|
||||
{/* Produktbild */}
|
||||
{offer.image && (
|
||||
<div style={{ height:160, background:'#f5f5f5', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0, overflow:'hidden', position:'relative' }}>
|
||||
<img
|
||||
src={offer.image}
|
||||
alt="König Pilsener"
|
||||
style={{ maxHeight:'100%', maxWidth:'100%', objectFit:'contain' }}
|
||||
onError={e => { e.target.parentElement.style.display='none'; }}
|
||||
/>
|
||||
{offer.oldPrice && (
|
||||
<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>
|
||||
)}
|
||||
{/* Oben: Bild links + Info rechts */}
|
||||
<div style={{ display:'flex', gap:10, alignItems:'flex-start' }}>
|
||||
{/* Bild */}
|
||||
<div style={{ width:80, height:80, flexShrink:0, background:'#f5f5f5', borderRadius:6, overflow:'hidden', display:'flex', alignItems:'center', justifyContent:'center' }}>
|
||||
{offer.image
|
||||
? <img src={offer.image} alt="König Pilsener"
|
||||
style={{ width:'100%', height:'100%', objectFit:'contain' }}
|
||||
onError={e => { e.target.parentElement.style.background='transparent'; e.target.style.display='none'; }}
|
||||
/>
|
||||
: <span style={{ fontSize:28 }}>🍺</span>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<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`,
|
||||
borderRadius:4, padding:'2px 8px', marginBottom:3,
|
||||
color, fontFamily:'monospace', fontSize:10, fontWeight:700,
|
||||
}}>{offer.retailer}</div>
|
||||
{/* Info */}
|
||||
<div style={{ flex:1, minWidth:0 }}>
|
||||
<div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:3, flexWrap:'wrap' }}>
|
||||
<span style={{
|
||||
background:`${color}22`, border:`1px solid ${color}55`,
|
||||
borderRadius:4, padding:'2px 7px',
|
||||
color, fontFamily:'monospace', fontSize:10, fontWeight:700,
|
||||
}}>{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 && (
|
||||
<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}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* König Pilsener */}
|
||||
<div style={{ color:'#fff', fontFamily:'monospace', fontSize:13, fontWeight:600, lineHeight:1.3 }}>
|
||||
König Pilsener
|
||||
</div>
|
||||
|
||||
{/* Flaschengröße/Beschreibung */}
|
||||
{offer.description && (
|
||||
<div style={{ color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:10, lineHeight:1.5 }}>
|
||||
{offer.description}
|
||||
<div style={{ color:'#fff', fontFamily:'monospace', fontSize:13, fontWeight:600 }}>
|
||||
König Pilsener
|
||||
</div>
|
||||
)}
|
||||
{offer.description && (
|
||||
<div style={{ color:'rgba(255,255,255,0.45)', fontFamily:'monospace', fontSize:9, marginTop:2, lineHeight:1.4 }}>
|
||||
{offer.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Preis */}
|
||||
<div style={{ display:'flex', alignItems:'baseline', gap:8, marginTop:4 }}>
|
||||
<span style={{ color:GOLD, fontFamily:'Space Mono,monospace', fontSize:20, fontWeight:700 }}>
|
||||
{offer.price}
|
||||
{/* Preis */}
|
||||
<div style={{ display:'flex', alignItems:'baseline', gap:8 }}>
|
||||
<span style={{ color:GOLD, fontFamily:'Space Mono,monospace', fontSize:22, fontWeight:700 }}>
|
||||
{offer.price}
|
||||
</span>
|
||||
{offer.oldPrice && (
|
||||
<span style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:12, textDecoration:'line-through' }}>
|
||||
{offer.oldPrice}
|
||||
</span>
|
||||
{offer.oldPrice && (
|
||||
<span style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:12, textDecoration:'line-through' }}>
|
||||
{offer.oldPrice}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Gültigkeit */}
|
||||
<div style={{ marginTop:'auto', paddingTop:6, borderTop:'1px solid rgba(255,255,255,0.06)', display:'flex', flexDirection:'column', gap:3 }}>
|
||||
{offer.validity && (
|
||||
<div style={{ color:'#f59e0b', fontFamily:'monospace', fontSize:10 }}>
|
||||
⏰ {offer.validity}
|
||||
</div>
|
||||
)}
|
||||
{offer.dateRange && (
|
||||
<div style={{ color:'rgba(255,255,255,0.35)', fontFamily:'monospace', fontSize:10 }}>
|
||||
📅 Gültig: {offer.dateRange}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Gültigkeit */}
|
||||
<div style={{ borderTop:'1px solid rgba(255,255,255,0.06)', paddingTop:6, display:'flex', gap:12, flexWrap:'wrap' }}>
|
||||
{offer.validity && (
|
||||
<span style={{ color:'#f59e0b', fontFamily:'monospace', fontSize:10 }}>⏰ {offer.validity}</span>
|
||||
)}
|
||||
{offer.dateRange && (
|
||||
<span style={{ color:'rgba(255,255,255,0.35)', fontFamily:'monospace', fontSize:10 }}>📅 {offer.dateRange}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -121,23 +108,19 @@ function ProspektCard({ b }) {
|
||||
...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)',
|
||||
}}>
|
||||
<div style={{ position:'relative' }}>
|
||||
{b.image && (
|
||||
<img src={`/api/tools/koepi/img?url=${encodeURIComponent(b.image)}`}
|
||||
alt={b.title} style={{ width:'100%', height:110, objectFit:'cover', display:'block' }}
|
||||
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>
|
||||
{b.image && (
|
||||
<img src={`/api/tools/koepi/img?url=${encodeURIComponent(b.image)}`}
|
||||
alt={b.title} style={{ width:'100%', height:110, objectFit:'cover', display:'block' }}
|
||||
onError={e => { e.target.style.display='none'; }}
|
||||
/>
|
||||
)}
|
||||
<div style={{ padding:'10px 12px' }}>
|
||||
<div style={{ display:'inline-block', background:`${color}22`, border:`1px solid ${color}44`,
|
||||
borderRadius:4, padding:'2px 8px', marginBottom:6, color, fontFamily:'monospace', fontSize:10, fontWeight:700 }}>
|
||||
{b.publisher}
|
||||
<div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:4, flexWrap:'wrap' }}>
|
||||
<div style={{ background:`${color}22`, border:`1px solid ${color}44`,
|
||||
borderRadius:4, padding:'2px 8px', color, fontFamily:'monospace', fontSize:10, fontWeight:700 }}>
|
||||
{b.publisher}
|
||||
</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:'rgba(255,255,255,0.4)', fontFamily:'monospace', fontSize:10, marginBottom:2 }}>
|
||||
|
||||
Reference in New Issue
Block a user