memo and lazy load

This commit is contained in:
2ManyProjects 2025-05-08 20:29:40 -05:00
parent 36c2dd98a2
commit bd6c90ab9d
5 changed files with 268 additions and 206 deletions

View file

@ -1,184 +1,236 @@
project/ / (root)
├── frontend/ ├── .env
│ ├── node_modules/ ├── .gitignore
│ ├── src/ ├── Dockerfile
│ │ ├── pages/ ├── README.md
│ │ │ ├── Admin/ ├── config.js
│ │ │ │ ├── BrandingPage.jsx ├── docker-compose.yml
│ │ │ │ ├── ReportsPage.jsx ├── fileStructure.txt
│ │ │ │ ├── ProductEditPage.jsx ├── index.html
│ │ │ │ ├── OrdersPage.jsx ├── main.jsx
│ │ │ │ ├── EmailTemplatesPage.jsx ├── nginx.conf
│ │ │ │ ├── SettingsPage.jsx ├── package.json
│ │ │ │ ├── BlogEditPage.jsx ├── setup-frontend.sh
│ │ │ │ ├── CategoriesPage.jsx ├── start.sh
│ │ │ │ ├── CustomersPage.jsx ├── vite.config.js
│ │ │ │ ├── CouponsPage.jsx
│ │ │ │ ├── ProductReviewsPage.jsx
│ │ │ │ ├── BlogPage.jsx
│ │ │ │ ├── BlogCommentsPage.jsx
│ │ │ │ ├── DashboardPage.jsx
│ │ │ │ └── ProductsPage.jsx
│ │ │ ├── CheckoutPage.jsx
│ │ │ ├── CouponEditPage.jsx
│ │ │ ├── ProductsPage.jsx
│ │ │ ├── UserOrdersPage.jsx
│ │ │ ├── CartPage.jsx
│ │ │ ├── ProductDetailPage.jsx
│ │ │ ├── BlogDetailPage.jsx
│ │ │ ├── BlogPage.jsx
│ │ │ ├── CouponRedemptionsPage.jsx
│ │ │ ├── HomePage.jsx
│ │ │ ├── LoginPage.jsx
│ │ │ ├── PaymentSuccessPage.jsx
│ │ │ ├── RegisterPage.jsx
│ │ │ ├── VerifyPage.jsx
│ │ │ ├── NotFoundPage.jsx
│ │ │ └── PaymentCancelPage.jsx
│ │ ├── services/
│ │ │ ├── emailTemplateService.js
│ │ │ ├── blogAdminService.js
│ │ │ ├── adminService.js
│ │ │ ├── couponService.js
│ │ │ ├── productService.js
│ │ │ ├── productReviewService.js
│ │ │ ├── consentService.js (NEW)
│ │ │ ├── settingsAdminService.js
│ │ │ ├── imageService.js
│ │ │ ├── cartService.js
│ │ │ ├── categoryAdminService.js
│ │ │ ├── authService.js
│ │ │ ├── blogService.js
│ │ │ └── api.js
│ │ ├── components/
│ │ │ ├── ProductReviews.jsx
│ │ │ ├── OrderStatusDialog.jsx
│ │ │ ├── ImageUploader.jsx
│ │ │ ├── CookieConsentPopup.jsx
│ │ │ ├── CookieSettingsButton.jsx
│ │ │ ├── Footer.jsx
│ │ │ ├── CouponInput.jsx
│ │ │ ├── EmailDialog.jsx
│ │ │ ├── StripePaymentForm.jsx
│ │ │ ├── ProductImage.jsx
│ │ │ ├── ProtectedRoute.jsx
│ │ │ ├── Notifications.jsx
│ │ │ └── ProductRatingDisplay.jsx
│ │ ├── hooks/
│ │ │ ├── apiHooks.js
│ │ │ ├── blogHooks.js
│ │ │ ├── emailTemplateHooks.js
│ │ │ ├── adminHooks.js
│ │ │ ├── productReviewHooks.js
│ │ │ ├── reduxHooks.js
│ │ │ ├── couponAdminHooks.js
│ │ │ ├── settingsAdminHooks.js
│ │ │ ├── categoryAdminHooks.js
│ │ │ └── brandingHooks.js
│ │ ├── layouts/
│ │ │ ├── MainLayout.jsx
│ │ │ ├── AdminLayout.jsx
│ │ │ └── AuthLayout.jsx
│ │ ├── features/
│ │ │ ├── ui/
│ │ │ │ └── uiSlice.js
│ │ │ ├── cart/
│ │ │ │ └── cartSlice.js
│ │ │ ├── auth/
│ │ │ │ └── authSlice.js
│ │ │ └── theme/
│ │ │ ├── ThemeProvider.jsx
│ │ │ └── index.js
│ │ ├── utils/
│ │ │ └── imageUtils.js
│ │ ├── store/
│ │ │ └── index.js
│ │ ├── context/
│ │ │ └── StripeContext.jsx
│ │ └── assets/
│ │ ├── App.jsx
│ │ ├── main.jsx
│ │ └── config.js
│ └── public/
│ ├── favicon.svg
│ ├── vite.config.js
│ ├── README.md
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── setup-frontend.sh
│ ├── index.html
│ └── .env
├── backend/ ├── backend/
│ ├── node_modules/ │ ├── node_modules/
│ ├── src/ │ ├── public/
│ │ ├── routes/ │ │ ├── seo-files/
│ │ │ ├── cart.js │ │ ├── uploads/
│ │ │ ├── couponAdmin.js │ │ ├── robots.txt
│ │ │ ├── emailTemplatesAdmin.js │ │ └── sitemap.xml
│ │ │ ├── productAdmin.js │ │
│ │ │ ├── blogAdmin.js │ └── src/
│ │ │ ├── orderAdmin.js │ ├── db/
│ │ │ ├── settingsAdmin.js │ │ └── index.js
│ │ │ ├── blog.js │ │
│ │ │ ├── auth.js │ ├── middleware/
│ │ │ ├── productReviews.js │ │ ├── adminAuth.js
│ │ │ ├── stripePayment.js │ │ ├── auth.js
│ │ │ ├── products.js │ │ ├── seoMiddleware.js
│ │ │ ├── productReviewsAdmin.js │ │ └── upload.js
│ │ │ ├── blogCommentsAdmin.js │ │
│ │ │ ├── userAdmin.js │ ├── models/
│ │ │ ├── categoryAdmin.js │ │ └── SystemSettings.js
│ │ │ ├── shipping.js │ │
│ │ │ ├── images.js │ ├── routes/
│ │ │ ├── userOrders.js │ │ ├── auth.js
│ │ │ ├── publicSettings.js │ │ ├── blog.js
│ │ │ └── productAdminImages.js │ │ ├── blogAdmin.js
│ │ ├── middleware/ │ │ ├── blogCommentsAdmin.js
│ │ │ ├── upload.js │ │ ├── cart.js
│ │ │ ├── adminAuth.js │ │ ├── categoryAdmin.js
│ │ │ └── auth.js │ │ ├── couponAdmin.js
│ │ ├── services/ │ │ ├── emailCampaignsAdmin.js
│ │ │ ├── shippingService.js │ │ ├── emailTemplatesAdmin.js
│ │ │ ├── emailService.js │ │ ├── emailTracking.js
│ │ │ └── notificationService.js │ │ ├── images.js
│ │ ├── models/ │ │ ├── mailingListAdmin.js
│ │ │ └── SystemSettings.js │ │ ├── orderAdmin.js
│ │ └── db/ │ │ ├── productAdmin.js
│ │ ├── index.js │ │ ├── productAdminImages.js
│ │ ├── index.js │ │ ├── productReviews.js
│ │ └── config.js │ │ ├── productReviewsAdmin.js
│ └── public/ │ │ ├── products.js
│ ├── uploads/ │ │ ├── publicSettings.js
│ │ ├── products/ │ │ ├── settingsAdmin.js
│ │ └── blog/ │ │ ├── shipping.js
│ ├── package-lock.json │ │ ├── stripePayment.js
│ ├── README.md │ │ ├── subscribers.js
│ ├── .env │ │ ├── subscribersAdmin.js
│ ├── package.json │ │ ├── userAdmin.js
│ ├── Dockerfile │ │ └── userOrders.js
│ └── .gitignore │ │
└── db/ │ ├── services/
├── init/ │ │ ├── cacheService.js
│ ├── 18-email-templates.sql │ │ ├── emailService.js
│ ├── 01-schema.sql │ │ ├── notificationService.js
│ ├── 02-seed.sql │ │ ├── queueService.js
│ ├── 16-blog-schema.sql │ │ ├── shippingService.js
│ ├── 15-coupon.sql │ │ ├── sitemapService.js
│ ├── 17-product-reviews.sql │ │ └── storageService.js
│ ├── 19-branding-settings.sql (NEW) │ │
│ ├── 04-product-images.sql │ └── uploads/
│ ├── 09-system-settings.sql │ ├── temp/
│ ├── 14-product-notifications.sql │ ├── config.js
│ ├── 10-payment.sql │ ├── index.js
│ ├── 11-notifications.sql │ └── worker.js
│ ├── 12-shipping-orders.sql
│ ├── 08-create-email.sql ├── db/
│ ├── 05-admin-role.sql │ ├── init/
│ ├── 03-api-key.sql │ │ ├── 01-schema.sql
│ ├── 07-user-keys.sql │ │ ├── 02-seed.sql
│ ├── 13-cart-metadata.sql │ │ ├── 03-api-key.sql
│ └── 06-product-categories.sql │ │ ├── 04-product-images.sql
└── test/ │ │ ├── 05-admin-role.sql
├── fileStructure.txt │ │ ├── 06-product-categories.sql
├── docker-compose.yml │ │ ├── 07-user-keys.sql
└── .gitignore │ │ ├── 08-create-email.sql
│ │ ├── 09-system-settings.sql
│ │ ├── 10-payment.sql
│ │ ├── 11-notifications.sql
│ │ ├── 12-shipping-orders.sql
│ │ ├── 13-cart-metadata.sql
│ │ ├── 14-product-notifications.sql
│ │ ├── 15-coupon.sql
│ │ ├── 16-blog-schema.sql
│ │ ├── 17-product-reviews.sql
│ │ ├── 18-email-templates.sql
│ │ ├── 19-branding-settings.sql
│ │ ├── 20-mailinglist.sql
│ │ ├── 21-order-refund.sql
│ │ └── 22-blog-json.sql
│ │
│ └── test/
└── frontend/
├── node_modules/
├── public/
│ ├── seo-files/
│ └── favicon.svg
└── src/
├── assets/
├── components/
│ ├── CookieConsentPopup.jsx
│ ├── CookieSettingsButton.jsx
│ ├── CouponInput.jsx
│ ├── EmailDialog.jsx
│ ├── Footer.jsx
│ ├── ImageUploader.jsx
│ ├── Notifications.jsx
│ ├── OrderStatusDialog.jsx
│ ├── ProductImage.jsx
│ ├── ProductRatingDisplay.jsx
│ ├── ProductReviews.jsx
│ ├── ProtectedRoute.jsx
│ ├── SEOMetaTags.jsx
│ ├── StripePaymentForm.jsx
│ └── SubscriptionForm.jsx
├── context/
│ └── StripeContext.jsx
├── features/
│ ├── auth/
│ │ └── authSlice.js
│ ├── cart/
│ │ └── cartSlice.js
│ └── ui/
│ └── uiSlice.js
├── hooks/
│ ├── adminHooks.js
│ ├── apiHooks.js
│ ├── blogHooks.js
│ ├── brandingHooks.js
│ ├── categoryAdminHooks.js
│ ├── couponAdminHooks.js
│ ├── emailCampaignHooks.js
│ ├── emailTemplateHooks.js
│ ├── productAdminHooks.js
│ ├── productReviewHooks.js
│ ├── reduxHooks.js
│ ├── settingsAdminHooks.js
│ ├── useProductSeo.js
│ ├── useSeoMeta.js
│ └── useSeoUrl.js
├── layouts/
│ ├── AdminLayout.jsx
│ ├── AuthLayout.jsx
│ └── MainLayout.jsx
├── pages/
│ ├── Admin/
│ │ ├── BlogCommentsPage.jsx
│ │ ├── BlogEditPage.jsx
│ │ ├── BlogPage.jsx
│ │ ├── BlogPreviewPage.jsx
│ │ ├── BrandingPage.jsx
│ │ ├── CampaignAnalyticsPage.jsx
│ │ ├── CampaignSendPage.jsx
│ │ ├── CategoriesPage.jsx
│ │ ├── CouponsPage.jsx
│ │ ├── CustomersPage.jsx
│ │ ├── DashboardPage.jsx
│ │ ├── EmailCampaignEditorPage.jsx
│ │ ├── EmailCampaignsPage.jsx
│ │ ├── EmailTemplatesPage.jsx
│ │ ├── MailingListsPage.jsx
│ │ ├── OrdersPage.jsx
│ │ ├── ProductEditPage.jsx
│ │ ├── ProductReviewsPage.jsx
│ │ ├── ProductsPage.jsx
│ │ ├── ReportsPage.jsx
│ │ ├── SettingsPage.jsx
│ │ └── SubscribersPage.jsx
│ │
│ ├── BlogDetailPage.jsx
│ ├── BlogPage.jsx
│ ├── CartPage.jsx
│ ├── CheckoutPage.jsx
│ ├── CouponEditPage.jsx
│ ├── CouponRedemptionsPage.jsx
│ ├── HomePage.jsx
│ ├── LoginPage.jsx
│ ├── NotFoundPage.jsx
│ ├── PaymentCancelPage.jsx
│ ├── PaymentSuccessPage.jsx
│ ├── ProductDetailPage.jsx
│ ├── ProductsPage.jsx
│ ├── RegisterPage.jsx
│ ├── SubscriptionConfirmPage.jsx
│ ├── SubscriptionPreferencesPage.jsx
│ ├── UnsubscribePage.jsx
│ ├── UserOrdersPage.jsx
│ └── VerifyPage.jsx
├── services/
│ ├── adminService.js
│ ├── api.js
│ ├── authService.js
│ ├── blogAdminService.js
│ ├── blogService.js
│ ├── cartService.js
│ ├── categoryAdminService.js
│ ├── consentService.js
│ ├── couponService.js
│ ├── emailTemplateService.js
│ ├── imageService.js
│ ├── productReviewsService.js
│ ├── productService.js
│ └── settingsAdminService.js
├── store/
│ └── index.js
├── theme/
│ ├── index.js
│ └── ThemeProvider.jsx
└── utils/
├── imageUtils.js
└── App.jsx

View file

@ -140,8 +140,16 @@ function App() {
<UserOrdersPage /> <UserOrdersPage />
</ProtectedRoute> </ProtectedRoute>
} /> } />
<Route index element={<HomePage />} /> <Route index element={
<Route path="products" element={<ProductsPage />} /> <Suspense fallback={<LoadingComponent />}>
<HomePage />
</Suspense>
} />
<Route path="products" element={
<Suspense fallback={<LoadingComponent />}>
<ProductsPage />
</Suspense>
} />
<Route path="products/:id" element={<ProductDetailPage />} /> <Route path="products/:id" element={<ProductDetailPage />} />
<Route path="confirm-subscription" element={<SubscriptionConfirmPage />} /> <Route path="confirm-subscription" element={<SubscriptionConfirmPage />} />
<Route path="unsubscribe" element={<UnsubscribePage />} /> <Route path="unsubscribe" element={<UnsubscribePage />} />

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useState, memo } from 'react';
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import imageUtils from '@utils/imageUtils'; import imageUtils from '@utils/imageUtils';
@ -11,7 +11,7 @@ import imageUtils from '@utils/imageUtils';
* @param {string} props.placeholderImage - Placeholder image to use when no image is available * @param {string} props.placeholderImage - Placeholder image to use when no image is available
* @returns {JSX.Element} - ProductImage component * @returns {JSX.Element} - ProductImage component
*/ */
const ProductImage = ({ const ProductImage = memo(({
images, images,
alt = 'Product image', alt = 'Product image',
sx = {}, sx = {},
@ -20,7 +20,6 @@ const ProductImage = ({
}) => { }) => {
const [imageError, setImageError] = useState(false); const [imageError, setImageError] = useState(false);
// Determine which image to use
let imageSrc = placeholderImage; let imageSrc = placeholderImage;
// Handle different types of image inputs // Handle different types of image inputs
@ -52,6 +51,7 @@ const ProductImage = ({
src={imageError ? placeholderImage : imageSrc} src={imageError ? placeholderImage : imageSrc}
alt={alt} alt={alt}
onError={handleError} onError={handleError}
loading="lazy"
sx={{ sx={{
display: 'block', display: 'block',
width: '100%', width: '100%',
@ -62,6 +62,6 @@ const ProductImage = ({
{...rest} {...rest}
/> />
); );
}; })
export default ProductImage; export default ProductImage;

View file

@ -1,10 +1,10 @@
import React from 'react'; import React, { memo } from 'react';
import { Box, Typography, Rating } from '@mui/material'; import { Box, Typography, Rating } from '@mui/material';
/** /**
* Component to display product rating in a compact format * Component to display product rating in a compact format
*/ */
const ProductRatingDisplay = ({ rating, reviewCount, showEmpty = false }) => { const ProductRatingDisplay = memo(({ rating, reviewCount, showEmpty = false }) => {
if (!rating && !reviewCount && !showEmpty) { if (!rating && !reviewCount && !showEmpty) {
return null; return null;
} }
@ -22,6 +22,6 @@ const ProductRatingDisplay = ({ rating, reviewCount, showEmpty = false }) => {
</Typography> </Typography>
</Box> </Box>
); );
}; })
export default ProductRatingDisplay; export default ProductRatingDisplay;

View file

@ -29,7 +29,8 @@ const HomePage = () => {
const featuredProductsData = products && products.length > 0 ? { const featuredProductsData = products && products.length > 0 ? {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "ItemList", "@type": "ItemList",
"itemListElement": products.slice(0, 6).map((product, index) => ({ "itemListElement": products.slice(0, 6).map((product, index) => {
return {
"@type": "ListItem", "@type": "ListItem",
"position": index + 1, "position": index + 1,
"item": { "item": {
@ -45,7 +46,8 @@ const HomePage = () => {
"priceCurrency": "CAD" "priceCurrency": "CAD"
} }
} }
})) }
})
} : null; } : null;
return ( return (