diff --git a/prerender/seo/homepage.cjs b/prerender/seo/homepage.cjs index 812685d..2a79206 100644 --- a/prerender/seo/homepage.cjs +++ b/prerender/seo/homepage.cjs @@ -7,8 +7,6 @@ const generateHomepageMetaTags = (baseUrl, config) => { const canonicalUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; return ` - - @@ -235,4 +233,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 6856ba9..214816d 100644 --- a/src/App.js +++ b/src/App.js @@ -32,10 +32,11 @@ import i18n from './i18n/index.js'; import Header from "./components/Header.js"; import Footer from "./components/Footer.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")); +import MainPageLayout from "./components/MainPageLayout.js"; +import IdleMainPagesSlideshow from "./components/IdleMainPagesSlideshow.js"; + +import Content from "./components/Content.js"; +import ProductDetail from "./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 0d37973..0496e55 100644 --- a/src/PrerenderHome.js +++ b/src/PrerenderHome.js @@ -7,8 +7,6 @@ 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 { @@ -124,18 +122,9 @@ 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; +export default PrerenderHome; \ No newline at end of file diff --git a/src/components/ChatAssistant.js b/src/components/ChatAssistant.js index 3f96556..05da261 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 '../utils/authSession.js'; +import { isUserLoggedIn } from './LoginComponent.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 10cff1c..cb2974a 100644 --- a/src/components/LanguageSwitcher.js +++ b/src/components/LanguageSwitcher.js @@ -6,37 +6,13 @@ 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 + anchorEl: null, + loadedFlags: {} }; } @@ -60,7 +36,72 @@ 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 ( - {LANGUAGE_FLAG_EMOJIS[lang] || this.getLanguageLabel(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', @@ -216,4 +275,4 @@ class LanguageSwitcher extends Component { } } -export default withI18n()(LanguageSwitcher); +export default withI18n()(LanguageSwitcher); \ No newline at end of file diff --git a/src/components/LoginComponent.js b/src/components/LoginComponent.js index 18bd712..a2f6656 100644 --- a/src/components/LoginComponent.js +++ b/src/components/LoginComponent.js @@ -21,7 +21,6 @@ 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 { @@ -54,6 +53,24 @@ 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}); @@ -792,4 +809,4 @@ export class LoginComponent extends Component { } } -export default withRouter(withI18n()(LoginComponent)); +export default withRouter(withI18n()(LoginComponent)); \ No newline at end of file diff --git a/src/components/MainPageLayout.js b/src/components/MainPageLayout.js index 7778a18..33a982b 100644 --- a/src/components/MainPageLayout.js +++ b/src/components/MainPageLayout.js @@ -275,15 +275,7 @@ const ContentBox = ({ box, index, pageType, starHovered, setStarHovered, opacity > {opacity === 1 && ( - {box.title} + {box.title} )} {box.title} diff --git a/src/components/header/ButtonGroup.js b/src/components/header/ButtonGroup.js index a7dec0b..3b98517 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; +export default ButtonGroupWithRouter; \ No newline at end of file diff --git a/src/components/profile/OrderProcessingService.js b/src/components/profile/OrderProcessingService.js index 6e791a3..2723047 100644 --- a/src/components/profile/OrderProcessingService.js +++ b/src/components/profile/OrderProcessingService.js @@ -1,4 +1,4 @@ -import { isUserLoggedIn } from "../../utils/authSession.js"; +import { isUserLoggedIn } from "../LoginComponent.js"; class OrderProcessingService { constructor(getContext, setState) { @@ -369,4 +369,4 @@ class OrderProcessingService { } } -export default OrderProcessingService; +export default OrderProcessingService; \ No newline at end of file diff --git a/src/utils/authSession.js b/src/utils/authSession.js deleted file mode 100644 index 9afec43..0000000 --- a/src/utils/authSession.js +++ /dev/null @@ -1,16 +0,0 @@ -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 0418305..4d574ee 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,31 +5,17 @@ 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 { execFileSync } from 'child_process'; +import { execSync } from 'child_process'; import webpack from 'webpack'; import fs from 'fs'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; -// Git hash for meta tag / currentHash.json — avoid execSync (spawns /bin/sh; fails with EPERM in some sandboxes/CI) +// Get git commit hash 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 execFileSync('git', ['rev-parse', 'HEAD'], { - encoding: 'utf8', - maxBuffer: 1024 * 1024, - }).trim(); + return execSync('git rev-parse HEAD').toString().trim(); } catch (e) { - console.warn( - 'Git commit hash unavailable (set GIT_COMMIT or run build in a git repo):', - e && e.message ? e.message : e - ); + console.error('Failed to get git commit hash:', e); return 'unknown'; } }; @@ -315,36 +301,9 @@ 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|engine\.io-parser|@socket\.io|socket\.io-parser|socket\.io-msgpack-parser)[\\/]/, + test: /[\\/]node_modules[\\/](socket\.io-client|engine\.io-client|@socket\.io|socket\.io-parser|socket\.io-msgpack-parser)[\\/]/, name: 'socketio', priority: 15, chunks: 'async',