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/
├── frontend/
│ ├── node_modules/
│ ├── src/
│ │ ├── pages/
│ │ │ ├── Admin/
│ │ │ │ ├── BrandingPage.jsx
│ │ │ │ ├── ReportsPage.jsx
│ │ │ │ ├── ProductEditPage.jsx
│ │ │ │ ├── OrdersPage.jsx
│ │ │ │ ├── EmailTemplatesPage.jsx
│ │ │ │ ├── SettingsPage.jsx
│ │ │ │ ├── BlogEditPage.jsx
│ │ │ │ ├── CategoriesPage.jsx
│ │ │ │ ├── CustomersPage.jsx
│ │ │ │ ├── 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
/ (root)
├── .env
├── .gitignore
├── Dockerfile
├── README.md
├── config.js
├── docker-compose.yml
├── fileStructure.txt
├── index.html
├── main.jsx
├── nginx.conf
├── package.json
├── setup-frontend.sh
├── start.sh
├── vite.config.js
├── backend/
│ ├── node_modules/
│ ├── src/
│ │ ├── routes/
│ │ │ ├── cart.js
│ │ │ ├── couponAdmin.js
│ │ │ ├── emailTemplatesAdmin.js
│ │ │ ├── productAdmin.js
│ │ │ ├── blogAdmin.js
│ │ │ ├── orderAdmin.js
│ │ │ ├── settingsAdmin.js
│ │ │ ├── blog.js
│ │ │ ├── auth.js
│ │ │ ├── productReviews.js
│ │ │ ├── stripePayment.js
│ │ │ ├── products.js
│ │ │ ├── productReviewsAdmin.js
│ │ │ ├── blogCommentsAdmin.js
│ │ │ ├── userAdmin.js
│ │ │ ├── categoryAdmin.js
│ │ │ ├── shipping.js
│ │ │ ├── images.js
│ │ │ ├── userOrders.js
│ │ │ ├── publicSettings.js
│ │ │ └── productAdminImages.js
│ │ ├── middleware/
│ │ │ ├── upload.js
│ │ │ ├── adminAuth.js
│ │ │ └── auth.js
│ │ ├── services/
│ │ │ ├── shippingService.js
│ │ │ ├── emailService.js
│ │ │ └── notificationService.js
│ │ ├── models/
│ │ │ └── SystemSettings.js
│ │ └── db/
│ │ ├── index.js
│ │ ├── index.js
│ │ └── config.js
│ └── public/
│ ├── uploads/
│ │ ├── products/
│ │ └── blog/
│ ├── package-lock.json
│ ├── README.md
│ ├── .env
│ ├── package.json
│ ├── Dockerfile
│ └── .gitignore
└── db/
├── init/
│ ├── 18-email-templates.sql
│ ├── 01-schema.sql
│ ├── 02-seed.sql
│ ├── 16-blog-schema.sql
│ ├── 15-coupon.sql
│ ├── 17-product-reviews.sql
│ ├── 19-branding-settings.sql (NEW)
│ ├── 04-product-images.sql
│ ├── 09-system-settings.sql
│ ├── 14-product-notifications.sql
│ ├── 10-payment.sql
│ ├── 11-notifications.sql
│ ├── 12-shipping-orders.sql
│ ├── 08-create-email.sql
│ ├── 05-admin-role.sql
│ ├── 03-api-key.sql
│ ├── 07-user-keys.sql
│ ├── 13-cart-metadata.sql
│ └── 06-product-categories.sql
└── test/
├── fileStructure.txt
├── docker-compose.yml
└── .gitignore
│ ├── public/
│ │ ├── seo-files/
│ │ ├── uploads/
│ │ ├── robots.txt
│ │ └── sitemap.xml
│ │
│ └── src/
│ ├── db/
│ │ └── index.js
│ │
│ ├── middleware/
│ │ ├── adminAuth.js
│ │ ├── auth.js
│ │ ├── seoMiddleware.js
│ │ └── upload.js
│ │
│ ├── models/
│ │ └── SystemSettings.js
│ │
│ ├── routes/
│ │ ├── auth.js
│ │ ├── blog.js
│ │ ├── blogAdmin.js
│ │ ├── blogCommentsAdmin.js
│ │ ├── cart.js
│ │ ├── categoryAdmin.js
│ │ ├── couponAdmin.js
│ │ ├── emailCampaignsAdmin.js
│ │ ├── emailTemplatesAdmin.js
│ │ ├── emailTracking.js
│ │ ├── images.js
│ │ ├── mailingListAdmin.js
│ │ ├── orderAdmin.js
│ │ ├── productAdmin.js
│ │ ├── productAdminImages.js
│ │ ├── productReviews.js
│ │ ├── productReviewsAdmin.js
│ │ ├── products.js
│ │ ├── publicSettings.js
│ │ ├── settingsAdmin.js
│ │ ├── shipping.js
│ │ ├── stripePayment.js
│ │ ├── subscribers.js
│ │ ├── subscribersAdmin.js
│ │ ├── userAdmin.js
│ │ └── userOrders.js
│ │
│ ├── services/
│ │ ├── cacheService.js
│ │ ├── emailService.js
│ │ ├── notificationService.js
│ │ ├── queueService.js
│ │ ├── shippingService.js
│ │ ├── sitemapService.js
│ │ └── storageService.js
│ │
│ └── uploads/
│ ├── temp/
│ ├── config.js
│ ├── index.js
│ └── worker.js
├── db/
│ ├── init/
│ │ ├── 01-schema.sql
│ │ ├── 02-seed.sql
│ │ ├── 03-api-key.sql
│ │ ├── 04-product-images.sql
│ │ ├── 05-admin-role.sql
│ │ ├── 06-product-categories.sql
│ │ ├── 07-user-keys.sql
│ │ ├── 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 />
</ProtectedRoute>
} />
<Route index element={<HomePage />} />
<Route path="products" element={<ProductsPage />} />
<Route index element={
<Suspense fallback={<LoadingComponent />}>
<HomePage />
</Suspense>
} />
<Route path="products" element={
<Suspense fallback={<LoadingComponent />}>
<ProductsPage />
</Suspense>
} />
<Route path="products/:id" element={<ProductDetailPage />} />
<Route path="confirm-subscription" element={<SubscriptionConfirmPage />} />
<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 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
* @returns {JSX.Element} - ProductImage component
*/
const ProductImage = ({
const ProductImage = memo(({
images,
alt = 'Product image',
sx = {},
@ -20,7 +20,6 @@ const ProductImage = ({
}) => {
const [imageError, setImageError] = useState(false);
// Determine which image to use
let imageSrc = placeholderImage;
// Handle different types of image inputs
@ -52,6 +51,7 @@ const ProductImage = ({
src={imageError ? placeholderImage : imageSrc}
alt={alt}
onError={handleError}
loading="lazy"
sx={{
display: 'block',
width: '100%',
@ -62,6 +62,6 @@ const ProductImage = ({
{...rest}
/>
);
};
})
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';
/**
* 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) {
return null;
}
@ -22,6 +22,6 @@ const ProductRatingDisplay = ({ rating, reviewCount, showEmpty = false }) => {
</Typography>
</Box>
);
};
})
export default ProductRatingDisplay;

View file

@ -29,23 +29,25 @@ const HomePage = () => {
const featuredProductsData = products && products.length > 0 ? {
"@context": "https://schema.org",
"@type": "ItemList",
"itemListElement": products.slice(0, 6).map((product, index) => ({
"@type": "ListItem",
"position": index + 1,
"item": {
"@type": "Product",
"name": product.name,
"image": product.images && product.images.length > 0 ?
imageUtils.getImageUrl(product.images.find(img => img.isPrimary)?.path || product.images[0].path) : null,
"description": product.description,
"url": `${window.location.origin}/products/${product.id}`,
"offers": {
"@type": "Offer",
"price": parseFloat(product.price).toFixed(2),
"priceCurrency": "CAD"
"itemListElement": products.slice(0, 6).map((product, index) => {
return {
"@type": "ListItem",
"position": index + 1,
"item": {
"@type": "Product",
"name": product.name,
"image": product.images && product.images.length > 0 ?
imageUtils.getImageUrl(product.images.find(img => img.isPrimary)?.path || product.images[0].path) : null,
"description": product.description,
"url": `${window.location.origin}/products/${product.id}`,
"offers": {
"@type": "Offer",
"price": parseFloat(product.price).toFixed(2),
"priceCurrency": "CAD"
}
}
}
}))
})
} : null;
return (