feat: Remove Telegraf link from footer and add ManufacturerCarousel to SharedCarousel component
This commit is contained in:
@@ -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>
|
||||
|
||||
189
src/components/ManufacturerCarousel.js
Normal file
189
src/components/ManufacturerCarousel.js
Normal 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));
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user