diff --git a/prerender/seo/homepage.cjs b/prerender/seo/homepage.cjs
index 2a79206..812685d 100644
--- a/prerender/seo/homepage.cjs
+++ b/prerender/seo/homepage.cjs
@@ -7,6 +7,8 @@ const generateHomepageMetaTags = (baseUrl, config) => {
const canonicalUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
return `
+
+
@@ -233,4 +235,4 @@ const generateHomepageJsonLd = (baseUrl, config, categories = []) => {
module.exports = {
generateHomepageMetaTags,
generateHomepageJsonLd,
-};
\ No newline at end of file
+};
diff --git a/src/App.js b/src/App.js
index 214816d..6856ba9 100644
--- a/src/App.js
+++ b/src/App.js
@@ -32,11 +32,10 @@ import i18n from './i18n/index.js';
import Header from "./components/Header.js";
import Footer from "./components/Footer.js";
-import MainPageLayout from "./components/MainPageLayout.js";
-import IdleMainPagesSlideshow from "./components/IdleMainPagesSlideshow.js";
-
-import Content from "./components/Content.js";
-import ProductDetail from "./components/ProductDetail.js";
+const IdleMainPagesSlideshow = lazy(() => import(/* webpackChunkName: "idle-slideshow" */ "./components/IdleMainPagesSlideshow.js"));
+const MainPageLayout = lazy(() => import(/* webpackChunkName: "main-page-layout" */ "./components/MainPageLayout.js"));
+const Content = lazy(() => import(/* webpackChunkName: "content" */ "./components/Content.js"));
+const ProductDetail = lazy(() => import(/* webpackChunkName: "product-detail" */ "./components/ProductDetail.js"));
// Lazy load rarely-accessed pages
const ProfilePage = lazy(() => import(/* webpackChunkName: "profile" */ "./pages/ProfilePage.js"));
diff --git a/src/PrerenderHome.js b/src/PrerenderHome.js
index 0496e55..0d37973 100644
--- a/src/PrerenderHome.js
+++ b/src/PrerenderHome.js
@@ -7,6 +7,8 @@ import {
} from '@mui/material';
import Footer from './components/Footer.js';
import { Logo, CategoryList } from './components/header/index.js';
+import MainPageLayout from './components/MainPageLayout.js';
+import { CarouselProvider } from './contexts/CarouselContext.js';
class PrerenderHome extends React.Component {
@@ -122,9 +124,18 @@ class PrerenderHome extends React.Component {
),
React.createElement(CategoryList, { categoryId: 209, activeCategoryId: null })
),
+ React.createElement(
+ Box,
+ { component: 'main', sx: { flexGrow: 1 } },
+ React.createElement(
+ CarouselProvider,
+ null,
+ React.createElement(MainPageLayout)
+ )
+ ),
React.createElement(Footer)
);
}
}
-export default PrerenderHome;
\ No newline at end of file
+export default PrerenderHome;
diff --git a/src/components/ChatAssistant.js b/src/components/ChatAssistant.js
index 05da261..3f96556 100644
--- a/src/components/ChatAssistant.js
+++ b/src/components/ChatAssistant.js
@@ -18,7 +18,7 @@ import { Link } from 'react-router-dom';
import MuiLink from '@mui/material/Link';
import { alpha } from '@mui/material/styles';
import TelegramIcon from '@mui/icons-material/Telegram';
-import { isUserLoggedIn } from './LoginComponent.js';
+import { isUserLoggedIn } from '../utils/authSession.js';
import { withTranslation } from '../i18n/withTranslation.js';
const TELEGRAM_ASSISTANT_URL = 'https://t.me/Growheads_de_Bot';
diff --git a/src/components/LanguageSwitcher.js b/src/components/LanguageSwitcher.js
index cb2974a..10cff1c 100644
--- a/src/components/LanguageSwitcher.js
+++ b/src/components/LanguageSwitcher.js
@@ -6,13 +6,37 @@ import MenuItem from '@mui/material/MenuItem';
import Typography from '@mui/material/Typography';
import { withI18n } from '../i18n/withTranslation.js';
+const LANGUAGE_FLAG_EMOJIS = {
+ ar: '🇪🇬',
+ bg: '🇧🇬',
+ cs: '🇨🇿',
+ de: '🇩🇪',
+ el: '🇬🇷',
+ en: '🇺🇸',
+ es: '🇪🇸',
+ fr: '🇫🇷',
+ hr: 'ðŸ‡ðŸ‡·',
+ hu: 'ðŸ‡ðŸ‡º',
+ it: '🇮🇹',
+ pl: '🇵🇱',
+ ro: '🇷🇴',
+ ru: '🇷🇺',
+ sk: '🇸🇰',
+ sl: '🇸🇮',
+ sq: '🇦🇱',
+ sr: '🇷🇸',
+ sv: '🇸🇪',
+ tr: '🇹🇷',
+ uk: '🇺🇦',
+ zh: '🇨🇳'
+};
+
class LanguageSwitcher extends Component {
constructor(props) {
super(props);
-
+
this.state = {
- anchorEl: null,
- loadedFlags: {}
+ anchorEl: null
};
}
@@ -36,72 +60,7 @@ class LanguageSwitcher extends Component {
this.handleClose();
};
- // Lazy load flag components
- loadFlagComponent = async (lang) => {
- if (this.state.loadedFlags[lang]) {
- return this.state.loadedFlags[lang];
- }
-
- try {
- const flagMap = {
- 'ar': () => import('country-flag-icons/react/3x2').then(m => m.EG),
- 'bg': () => import('country-flag-icons/react/3x2').then(m => m.BG),
- 'cs': () => import('country-flag-icons/react/3x2').then(m => m.CZ),
- 'de': () => import('country-flag-icons/react/3x2').then(m => m.DE),
- 'el': () => import('country-flag-icons/react/3x2').then(m => m.GR),
- 'en': () => import('country-flag-icons/react/3x2').then(m => m.US),
- 'es': () => import('country-flag-icons/react/3x2').then(m => m.ES),
- 'fr': () => import('country-flag-icons/react/3x2').then(m => m.FR),
- 'hr': () => import('country-flag-icons/react/3x2').then(m => m.HR),
- 'hu': () => import('country-flag-icons/react/3x2').then(m => m.HU),
- 'it': () => import('country-flag-icons/react/3x2').then(m => m.IT),
- 'pl': () => import('country-flag-icons/react/3x2').then(m => m.PL),
- 'ro': () => import('country-flag-icons/react/3x2').then(m => m.RO),
- 'ru': () => import('country-flag-icons/react/3x2').then(m => m.RU),
- 'sk': () => import('country-flag-icons/react/3x2').then(m => m.SK),
- 'sl': () => import('country-flag-icons/react/3x2').then(m => m.SI),
- 'sq': () => import('country-flag-icons/react/3x2').then(m => m.AL),
- 'sr': () => import('country-flag-icons/react/3x2').then(m => m.RS),
- 'sv': () => import('country-flag-icons/react/3x2').then(m => m.SE),
- 'tr': () => import('country-flag-icons/react/3x2').then(m => m.TR),
- 'uk': () => import('country-flag-icons/react/3x2').then(m => m.UA),
- 'zh': () => import('country-flag-icons/react/3x2').then(m => m.CN)
- };
-
- const flagLoader = flagMap[lang];
- if (flagLoader) {
- const FlagComponent = await flagLoader();
- this.setState(prevState => ({
- loadedFlags: {
- ...prevState.loadedFlags,
- [lang]: FlagComponent
- }
- }));
- return FlagComponent;
- }
- } catch (error) {
- console.warn(`Failed to load flag for language: ${lang}`, error);
- }
- return null;
- };
-
getLanguageFlag = (lang) => {
- const FlagComponent = this.state.loadedFlags[lang];
-
- if (FlagComponent) {
- return (
-
- );
- }
-
- // Loading placeholder or fallback
return (
- {this.getLanguageLabel(lang)}
+ {LANGUAGE_FLAG_EMOJIS[lang] || this.getLanguageLabel(lang)}
);
};
- // Load flags when menu opens
- componentDidUpdate(prevProps, prevState) {
- const { anchorEl } = this.state;
- const { languageContext } = this.props;
-
- if (anchorEl && !prevState.anchorEl && languageContext) {
- // Menu just opened, lazy load flags for all languages (not just available ones)
- languageContext.allLanguages.forEach(lang => {
- if (!this.state.loadedFlags[lang]) {
- this.loadFlagComponent(lang);
- }
- });
- }
- }
-
getLanguageLabel = (lang) => {
const labels = {
'ar': 'EG',
@@ -275,4 +216,4 @@ class LanguageSwitcher extends Component {
}
}
-export default withI18n()(LanguageSwitcher);
\ No newline at end of file
+export default withI18n()(LanguageSwitcher);
diff --git a/src/components/LoginComponent.js b/src/components/LoginComponent.js
index a2f6656..18bd712 100644
--- a/src/components/LoginComponent.js
+++ b/src/components/LoginComponent.js
@@ -21,6 +21,7 @@ import { withRouter } from './withRouter.js';
import GoogleLoginButton from './GoogleLoginButton.js';
import CartSyncDialog from './CartSyncDialog.js';
import { localAndArchiveServer, mergeCarts } from '../utils/cartUtils.js';
+import { isUserLoggedIn } from '../utils/authSession.js';
import config from '../config.js';
import { withI18n } from '../i18n/withTranslation.js';
import {
@@ -53,24 +54,6 @@ const persistSessionAuth = (response) => {
}
};
-// Function to check if user is logged in
-export const isUserLoggedIn = () => {
- const storedUser = sessionStorage.getItem('user');
- if (storedUser) {
- try {
- const parsedUser = JSON.parse(storedUser);
- console.log('Parsed User:', parsedUser);
- return { isLoggedIn: true, user: parsedUser, isAdmin: !!parsedUser.admin };
-
- } catch (error) {
- console.error('Error parsing user from sessionStorage:', error);
- sessionStorage.removeItem('user');
- }
- }
- console.log('isUserLoggedIn', false);
- return { isLoggedIn: false, user: null, isAdmin: false };
-};
-
// Hilfsfunktion zum Vergleich zweier Cart-Arrays
function cartsAreIdentical(cartA, cartB) {
console.log('Vergleiche Carts:', {cartA, cartB});
@@ -809,4 +792,4 @@ export class LoginComponent extends Component {
}
}
-export default withRouter(withI18n()(LoginComponent));
\ No newline at end of file
+export default withRouter(withI18n()(LoginComponent));
diff --git a/src/components/MainPageLayout.js b/src/components/MainPageLayout.js
index 33a982b..7778a18 100644
--- a/src/components/MainPageLayout.js
+++ b/src/components/MainPageLayout.js
@@ -275,7 +275,15 @@ const ContentBox = ({ box, index, pageType, starHovered, setStarHovered, opacity
>
{opacity === 1 && (
-
+
)}
{box.title}
diff --git a/src/components/header/ButtonGroup.js b/src/components/header/ButtonGroup.js
index 3b98517..a7dec0b 100644
--- a/src/components/header/ButtonGroup.js
+++ b/src/components/header/ButtonGroup.js
@@ -18,8 +18,8 @@ const LoginComponent = lazy(() => import(/* webpackChunkName: "login" */ "../Log
import CartDropdown from '../CartDropdown.js';
import LanguageSwitcher from '../LanguageSwitcher.js';
-import { isUserLoggedIn } from '../LoginComponent.js';
import { withI18n } from '../../i18n/withTranslation.js';
+import { isUserLoggedIn } from '../../utils/authSession.js';
function getBadgeNumber() {
let count = 0;
@@ -196,4 +196,4 @@ const ButtonGroupWithRouter = (props) => {
return ;
};
-export default ButtonGroupWithRouter;
\ No newline at end of file
+export default ButtonGroupWithRouter;
diff --git a/src/components/profile/OrderProcessingService.js b/src/components/profile/OrderProcessingService.js
index 2723047..6e791a3 100644
--- a/src/components/profile/OrderProcessingService.js
+++ b/src/components/profile/OrderProcessingService.js
@@ -1,4 +1,4 @@
-import { isUserLoggedIn } from "../LoginComponent.js";
+import { isUserLoggedIn } from "../../utils/authSession.js";
class OrderProcessingService {
constructor(getContext, setState) {
@@ -369,4 +369,4 @@ class OrderProcessingService {
}
}
-export default OrderProcessingService;
\ No newline at end of file
+export default OrderProcessingService;
diff --git a/src/utils/authSession.js b/src/utils/authSession.js
new file mode 100644
index 0000000..9afec43
--- /dev/null
+++ b/src/utils/authSession.js
@@ -0,0 +1,16 @@
+export const isUserLoggedIn = () => {
+ const storedUser = sessionStorage.getItem('user');
+ if (storedUser) {
+ try {
+ const parsedUser = JSON.parse(storedUser);
+ console.log('Parsed User:', parsedUser);
+ return { isLoggedIn: true, user: parsedUser, isAdmin: !!parsedUser.admin };
+ } catch (error) {
+ console.error('Error parsing user from sessionStorage:', error);
+ sessionStorage.removeItem('user');
+ }
+ }
+
+ console.log('isUserLoggedIn', false);
+ return { isLoggedIn: false, user: null, isAdmin: false };
+};
diff --git a/webpack.config.js b/webpack.config.js
index 4d574ee..0418305 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -5,17 +5,31 @@ import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import ESLintPlugin from 'eslint-webpack-plugin';
import { cpSync } from 'fs';
-import { execSync } from 'child_process';
+import { execFileSync } from 'child_process';
import webpack from 'webpack';
import fs from 'fs';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
-// Get git commit hash
+// Git hash for meta tag / currentHash.json — avoid execSync (spawns /bin/sh; fails with EPERM in some sandboxes/CI)
const getGitCommitHash = () => {
+ const fromEnv =
+ process.env.GIT_COMMIT ||
+ process.env.VERCEL_GIT_COMMIT_SHA ||
+ process.env.CI_COMMIT_SHA ||
+ process.env.GITHUB_SHA ||
+ '';
+ if (fromEnv) return String(fromEnv).trim();
+
try {
- return execSync('git rev-parse HEAD').toString().trim();
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
+ encoding: 'utf8',
+ maxBuffer: 1024 * 1024,
+ }).trim();
} catch (e) {
- console.error('Failed to get git commit hash:', e);
+ console.warn(
+ 'Git commit hash unavailable (set GIT_COMMIT or run build in a git repo):',
+ e && e.message ? e.message : e
+ );
return 'unknown';
}
};
@@ -301,9 +315,36 @@ export default {
priority: 20,
reuseExistingChunk: true,
},
+ // Keep Stripe checkout code out of the initial vendor bundle
+ stripe: {
+ test: /[\\/]node_modules[\\/]@stripe[\\/]/,
+ name: 'stripe',
+ priority: 19,
+ chunks: 'async',
+ reuseExistingChunk: true,
+ enforce: true,
+ },
+ // Lazy HTML parsing/sanitizing should stay async with the product detail/chat flows
+ htmlParser: {
+ test: /[\\/]node_modules[\\/](html-react-parser|sanitize-html|htmlparser2|domhandler|dom-serializer|entities|react-property|parse-srcset|postcss)[\\/]/,
+ name: 'html-parser-vendor',
+ priority: 18,
+ chunks: 'async',
+ reuseExistingChunk: true,
+ enforce: true,
+ },
+ // Girocode/QR code generation is only needed in checkout/profile flows
+ payments: {
+ test: /[\\/]node_modules[\\/](qrcode|sepa-payment-qr-code|iban)[\\/]/,
+ name: 'payments',
+ priority: 17,
+ chunks: 'async',
+ reuseExistingChunk: true,
+ enforce: true,
+ },
// socket.io-client and its dependencies — always async, never initial
socketio: {
- test: /[\\/]node_modules[\\/](socket\.io-client|engine\.io-client|@socket\.io|socket\.io-parser|socket\.io-msgpack-parser)[\\/]/,
+ test: /[\\/]node_modules[\\/](socket\.io-client|engine\.io-client|engine\.io-parser|@socket\.io|socket\.io-parser|socket\.io-msgpack-parser)[\\/]/,
name: 'socketio',
priority: 15,
chunks: 'async',