feat: Remove Telegraf link from footer and add ManufacturerCarousel to SharedCarousel component

This commit is contained in:
sebseb7
2026-03-11 05:56:25 +01:00
parent 65a676de46
commit 78bb99b418
3 changed files with 193 additions and 3 deletions

View File

@@ -352,9 +352,6 @@ class Footer extends Component {
<Typography variant="body2" sx={{ fontSize: { xs: '11px', md: '14px' }, lineHeight: 1.5 }}>
© {new Date().getFullYear()} <StyledDomainLink href="https://growheads.de" target="_blank" rel="noopener noreferrer">GrowHeads.de</StyledDomainLink>
</Typography>
<Typography variant="body2" sx={{ fontSize: { xs: '9px', md: '9px' }, lineHeight: 1.5, mt: 1 }}>
<StyledDomainLink href="https://telegraf.growheads.de" target="_blank" rel="noreferrer">Telegraf - sicherer Chat mit unseren Mitarbeitern</StyledDomainLink>
</Typography>
</Box>
</Stack>
</Box>

View File

@@ -0,0 +1,189 @@
import React from 'react';
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { withTranslation } from 'react-i18next';
import { withLanguage } from '../i18n/withTranslation.js';
const ITEM_WIDTH = 140 + 16; // 140px width + 16px gap
const AUTO_SCROLL_SPEED = 0.4;
class ManufacturerCarousel extends React.Component {
_isMounted = false;
originalItems = [];
animationFrame = null;
translateX = 0;
constructor(props) {
super(props);
this.state = {
items: [], // [{ id, name, src }]
};
this.carouselTrackRef = React.createRef();
}
componentDidMount() {
this._isMounted = true;
this.loadImages();
}
componentWillUnmount() {
this._isMounted = false;
if (this.animationFrame) {
cancelAnimationFrame(this.animationFrame);
this.animationFrame = null;
}
// Revoke object URLs to avoid memory leaks
for (const item of this.originalItems) {
if (item.src) URL.revokeObjectURL(item.src);
}
}
loadImages = () => {
window.socketManager.emit('getHerstellerImages', {}, (res) => {
if (!this._isMounted || !res?.success || !res.manufacturers?.length) return;
const items = res.manufacturers
.filter(m => m.imageBuffer)
.map(m => {
const blob = new Blob([m.imageBuffer], { type: 'image/avif' });
return { id: m.id, name: m.name || '', src: URL.createObjectURL(blob) };
})
.sort(() => Math.random() - 0.5);
if (items.length === 0) return;
this.originalItems = items;
this.setState({ items: [...items, ...items] }, () => {
this.startAutoScroll();
});
});
};
startAutoScroll = () => {
if (!this.animationFrame) {
this.animationFrame = requestAnimationFrame(this.tick);
}
};
tick = () => {
if (!this._isMounted || this.originalItems.length === 0) return;
this.translateX -= AUTO_SCROLL_SPEED;
const maxScroll = ITEM_WIDTH * this.originalItems.length;
if (Math.abs(this.translateX) >= maxScroll) {
this.translateX = 0;
}
if (this.carouselTrackRef.current) {
this.carouselTrackRef.current.style.transform = `translateX(${this.translateX}px)`;
}
this.animationFrame = requestAnimationFrame(this.tick);
};
render() {
const { t } = this.props;
const { items } = this.state;
if (!items || items.length === 0) return null;
return (
<Box sx={{ mt: 4 }}>
<Typography
variant="h4"
component="div"
sx={{
fontFamily: 'SwashingtonCP',
textShadow: '3px 3px 10px rgba(0, 0, 0, 0.4)',
textAlign: 'center',
mb: 2,
color: 'primary.main',
}}
>
{t('product.manufacturer')}
</Typography>
<div
style={{
position: 'relative',
overflow: 'hidden',
width: '100%',
maxWidth: '1200px',
margin: '0 auto',
padding: '0 20px',
boxSizing: 'border-box',
}}
>
{/* Fade edges */}
<div style={{
position: 'absolute', top: 0, left: 0,
width: '60px', height: '100%',
background: 'linear-gradient(to right, #c8e6c9, transparent)',
zIndex: 2, pointerEvents: 'none',
}} />
<div style={{
position: 'absolute', top: 0, right: 0,
width: '60px', height: '100%',
background: 'linear-gradient(to left, #c8e6c9, transparent)',
zIndex: 2, pointerEvents: 'none',
}} />
<div
style={{
position: 'relative',
overflow: 'hidden',
padding: '12px 0',
width: '100%',
maxWidth: '1080px',
margin: '0 auto',
boxSizing: 'border-box',
}}
>
<div
ref={this.carouselTrackRef}
style={{
display: 'flex',
gap: '16px',
alignItems: 'center',
width: 'fit-content',
transform: 'translateX(0px)',
}}
>
{items.map((item, index) => (
<div
key={`${item.id}-${index}`}
style={{
flex: '0 0 140px',
width: '140px',
height: '140px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
userSelect: 'none',
pointerEvents: 'none',
}}
>
<img
src={item.src}
alt={item.name}
draggable={false}
style={{
width: '100%',
height: '100%',
objectFit: 'contain',
display: 'block',
}}
/>
</div>
))}
</div>
</div>
</div>
</Box>
);
}
}
export default withTranslation()(withLanguage(ManufacturerCarousel));

View File

@@ -7,6 +7,7 @@ import ChevronLeft from "@mui/icons-material/ChevronLeft";
import ChevronRight from "@mui/icons-material/ChevronRight";
import CategoryBox from "./CategoryBox.js";
import ProductCarousel from "./ProductCarousel.js";
import ManufacturerCarousel from "./ManufacturerCarousel.js";
import { withTranslation } from 'react-i18next';
import { withLanguage } from '../i18n/withTranslation.js';
@@ -419,6 +420,9 @@ class SharedCarousel extends React.Component {
{/* Product Carousel for "neu" category */}
<ProductCarousel categoryId="neu" />
{/* Manufacturer logo carousel */}
<ManufacturerCarousel />
</Box>
);
}