diff --git a/api/src/config/categoryRoutes.config.ts b/api/src/config/categoryRoutes.config.ts index 7e36071..76156e8 100644 --- a/api/src/config/categoryRoutes.config.ts +++ b/api/src/config/categoryRoutes.config.ts @@ -5,7 +5,8 @@ export default { update: '/api/update-category/:id', delete: '/api/delete-category/:id', getCategory: '/api/category/:id/:language', - getCategories: '/api/categories/:language', + getCategories: '/api/categories/:language/:imageRequired', + getFeaturedCategories: '/api/featured-categories/:language/:size', searchCategories: '/api/search-categories/:language', createImage: '/api/create-category-image', updateImage: '/api/update-category-image/:id', diff --git a/api/src/config/productRoutes.config.ts b/api/src/config/productRoutes.config.ts index cdc684b..8f5266c 100644 --- a/api/src/config/productRoutes.config.ts +++ b/api/src/config/productRoutes.config.ts @@ -9,5 +9,5 @@ export default { getProduct: '/api/product/:id/:language', getBackendProducts: '/api/backend-products/:user/:page/:size/:category?', getFrontendProducts: '/api/frontend-products/:page/:size/:category?', - getFeaturedProducts: '/api/featured-products/:size', + getFeaturedProducts: '/api/featured-products', } diff --git a/api/src/controllers/categoryController.ts b/api/src/controllers/categoryController.ts index dc6b6d0..f45ffb3 100644 --- a/api/src/controllers/categoryController.ts +++ b/api/src/controllers/categoryController.ts @@ -12,6 +12,7 @@ import * as env from '../config/env.config' import Category from '../models/Category' import Value from '../models/Value' import Product from '../models/Product' +import Cart from '../models/Cart' /** * Validate category name by language. @@ -249,9 +250,18 @@ export const getCategory = async (req: Request, res: Response) => { */ export const getCategories = async (req: Request, res: Response) => { try { - const { language } = req.params + const { language, imageRequired } = req.params + const _imageRequired = helper.StringToBoolean(imageRequired) + + let $match: mongoose.FilterQuery = {} + if (_imageRequired) { + $match = { image: { $ne: null } } + } const categories = await Category.aggregate([ + { + $match, + }, { $lookup: { from: 'Value', @@ -282,6 +292,142 @@ export const getCategories = async (req: Request, res: Response) => { } } +/** + * Get featured categories. + * + * @async + * @param {Request} req + * @param {Response} res + * @returns {unknown} + */ +export const getFeaturedCategories = async (req: Request, res: Response) => { + try { + const { language, size: _size } = req.params + const cartId = String(req.query.c || '') + const size = Number.parseInt(_size, 10) + + let cartProducts: mongoose.Types.ObjectId[] = [] + if (cartId) { + const _cart = await Cart + .findById(cartId) + .populate<{ cartItems: env.CartItem[] }>('cartItems') + .lean() + + if (_cart) { + cartProducts = _cart.cartItems.map((cartItem) => cartItem.product) + } + } + + const data = await Product.aggregate([ + { + $match: { soldOut: false, hidden: false, quantity: { $gt: 0 } }, + }, + // + // Add inCart field + // + { + $addFields: { + inCart: { + $cond: [{ $in: ['$_id', cartProducts] }, 1, 0], + }, + }, + }, + // + // lookup categories + // + { + $lookup: { + from: 'Category', + let: { categories: '$categories' }, + pipeline: [ + { + $match: { + $expr: { $in: ['$_id', '$$categories'] }, + }, + }, + { + $lookup: { + from: 'Value', + let: { values: '$values' }, + pipeline: [ + { + $match: { + $and: [ + { $expr: { $in: ['$_id', '$$values'] } }, + { $expr: { $eq: ['$language', language] } }, + ], + }, + }, + ], + as: 'value', + }, + }, + { $unwind: { path: '$value', preserveNullAndEmptyArrays: false } }, + { $addFields: { name: '$value.value' } }, + ], + as: 'category', + }, + }, + // + // unwind category + // + { $unwind: { path: '$category', preserveNullAndEmptyArrays: false } }, + // + // Skip categories and description fields + // + { + $project: { + categories: 0, + description: 0, + }, + }, + // + // Sort products by createdAt desc + // + { + $sort: { createdAt: -1 }, + }, + // + // Group products by category + // + { + $group: { + _id: '$category._id', + name: { $first: '$category.name' }, + image: { $first: '$category.image' }, + featured: { $first: '$category.featured' }, + products: { $push: '$$ROOT' }, + }, + }, + // + // Sort categories by name + // + { + $sort: { name: 1 }, + }, + // + // Build final result and take only (size) products + // + { + $project: { + _id: 0, + category: { + _id: '$_id', + name: '$name', + image: '$image', + featured: '$featured', + }, + products: { $slice: ['$products', size] }, + }, + }, + ]) + return res.json(data) + } catch (err) { + logger.error(`[category.getFeaturedCategories] ${i18n.t('DB_ERROR')}`, err) + return res.status(400).send(i18n.t('DB_ERROR') + err) + } +} + /** * Search categories. * diff --git a/api/src/controllers/productController.ts b/api/src/controllers/productController.ts index ed64f40..39a46c1 100644 --- a/api/src/controllers/productController.ts +++ b/api/src/controllers/productController.ts @@ -212,6 +212,10 @@ export const update = async (req: Request, res: Response) => { product.hidden = hidden product.featured = featured + if (quantity > 0) { + product.soldOut = false + } + if (image) { const oldImage = path.join(env.CDN_PRODUCTS, product.image!) if (await helper.exists(oldImage)) { @@ -552,7 +556,8 @@ export const getFrontendProducts = async (req: Request, res: Response) => { category = new mongoose.Types.ObjectId(req.params.category) } - const { cart: cartId } = req.body + const { body }: { body: wexcommerceTypes.GetProductsPayload } = req + const { cart: cartId } = body let cartProducts: mongoose.Types.ObjectId[] = [] if (cartId) { const _cart = await Cart @@ -635,7 +640,7 @@ export const getFrontendProducts = async (req: Request, res: Response) => { } /** - * Get frontend products. + * Get featured products. * * @async * @param {Request} req @@ -644,7 +649,10 @@ export const getFrontendProducts = async (req: Request, res: Response) => { */ export const getFeaturedProducts = async (req: Request, res: Response) => { try { - const { cart: cartId, size } = req.body + const { body }: { body: wexcommerceTypes.GetProductsPayload } = req + const { cart: cartId, size: _size } = body + + const size: number = _size || 10 let cartProducts: mongoose.Types.ObjectId[] = [] if (cartId) { @@ -658,10 +666,9 @@ export const getFeaturedProducts = async (req: Request, res: Response) => { } } - // TODO after: sort by price asc, desc const products = await Product.aggregate([ { - $match: { featured: true }, + $match: { featured: true, soldOut: false, hidden: false, quantity: { $gt: 0 } }, }, { $addFields: { @@ -677,23 +684,16 @@ export const getFeaturedProducts = async (req: Request, res: Response) => { }, }, { - $facet: { - resultData: [ - { $sort: { createdAt: -1 } }, - { $limit: size }, - ], - pageInfo: [ - { - $count: 'totalRecords', - }, - ], - }, + $sort: { createdAt: -1 }, + }, + { + $limit: size, }, ], { collation: { locale: env.DEFAULT_LANGUAGE, strength: 2 } }) return res.json(products) } catch (err) { - logger.error(i18n.t('DB_ERROR'), err) + logger.error(`[product.getFeaturedProducts] ${i18n.t('DB_ERROR')}`, err) return res.status(400).send(i18n.t('DB_ERROR') + err) } } diff --git a/api/src/models/User.ts b/api/src/models/User.ts index 8716a5a..8afb750 100644 --- a/api/src/models/User.ts +++ b/api/src/models/User.ts @@ -1,6 +1,7 @@ import validator from 'validator' import { Schema, model } from 'mongoose' import * as wexcommerceTypes from ':wexcommerce-types' +// import * as wexcommerceHelper from ':wexcommerce-helper' import * as env from '../config/env.config' export const USER_EXPIRE_AT_INDEX_NAME = 'expireAt' diff --git a/api/src/routes/categoryRoutes.ts b/api/src/routes/categoryRoutes.ts index 13718cf..73eb9a7 100644 --- a/api/src/routes/categoryRoutes.ts +++ b/api/src/routes/categoryRoutes.ts @@ -13,6 +13,7 @@ routes.route(routeNames.update).put(authJwt.verifyToken, categoryController.upda routes.route(routeNames.delete).delete(authJwt.verifyToken, categoryController.deleteCategory) routes.route(routeNames.getCategory).get(authJwt.verifyToken, categoryController.getCategory) routes.route(routeNames.getCategories).get(categoryController.getCategories) +routes.route(routeNames.getFeaturedCategories).get(categoryController.getFeaturedCategories) routes.route(routeNames.searchCategories).get(authJwt.verifyToken, categoryController.searchCategories) routes.route(routeNames.createImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], categoryController.createImage) routes.route(routeNames.updateImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], categoryController.updateImage) diff --git a/api/src/routes/productRoutes.ts b/api/src/routes/productRoutes.ts index 8f3c166..ac4a08b 100644 --- a/api/src/routes/productRoutes.ts +++ b/api/src/routes/productRoutes.ts @@ -16,5 +16,6 @@ routes.route(routeNames.delete).delete(authJwt.verifyToken, productController.de routes.route(routeNames.getProduct).post(productController.getProduct) routes.route(routeNames.getBackendProducts).post(authJwt.verifyToken, productController.getBackendProducts) routes.route(routeNames.getFrontendProducts).post(productController.getFrontendProducts) +routes.route(routeNames.getFeaturedProducts).post(productController.getFeaturedProducts) export default routes diff --git a/backend/src/app/(pages)/(dashboard)/category/page.client.tsx b/backend/src/app/(pages)/(dashboard)/category/page.client.tsx index ab9b6e0..aa3c20f 100644 --- a/backend/src/app/(pages)/(dashboard)/category/page.client.tsx +++ b/backend/src/app/(pages)/(dashboard)/category/page.client.tsx @@ -142,7 +142,10 @@ const CategoryForm: React.FC = ({ category }) => { type="category" categoryId={category._id} image={image} - onMainImageUpsert={(img) => setImage(img)} + onMainImageUpsert={(img) => { + setImage(img) + router.refresh() + }} /> { @@ -199,6 +202,7 @@ const CategoryForm: React.FC = ({ category }) => { size="small" onClick={() => { router.push('/categories') + router.refresh() }} > {commonStrings.CANCEL} diff --git a/backend/src/app/(pages)/(dashboard)/product/page.client.tsx b/backend/src/app/(pages)/(dashboard)/product/page.client.tsx index e5363e0..b0f954f 100644 --- a/backend/src/app/(pages)/(dashboard)/product/page.client.tsx +++ b/backend/src/app/(pages)/(dashboard)/product/page.client.tsx @@ -326,6 +326,7 @@ const CreateProductForm: React.FC = ({ product }) => { helper.error(err) } router.push('/products') + router.refresh() }} > {commonStrings.CANCEL} diff --git a/backend/src/app/(pages)/(dashboard)/products/page.client.tsx b/backend/src/app/(pages)/(dashboard)/products/page.client.tsx index 208d687..0f75331 100644 --- a/backend/src/app/(pages)/(dashboard)/products/page.client.tsx +++ b/backend/src/app/(pages)/(dashboard)/products/page.client.tsx @@ -120,6 +120,7 @@ const ProductsWrapper: React.FC = ({ children }) => { {strings.NEW_PRODUCT} } +
  • @@ -129,13 +130,26 @@ const ProductsWrapper: React.FC = ({ children }) => {
  • + { categories.map((category) => (
  • + title={category.name} + onClick={() => { + if (env.isMobile() && leftPanelRef.current) { + leftPanelRef.current.style.display = 'none' + if (productsRef.current) { + productsRef.current.style.display = 'block' + } + if (closeIconRef.current) { + closeIconRef.current.style.visibility = 'hidden' + } + } + }} + > {category.name} diff --git a/backend/src/app/(pages)/(login)/sign-up/page.tsx b/backend/src/app/(pages)/(login)/sign-up/page.tsx index 16cdc95..1f141e7 100644 --- a/backend/src/app/(pages)/(login)/sign-up/page.tsx +++ b/backend/src/app/(pages)/(login)/sign-up/page.tsx @@ -271,4 +271,11 @@ const SignUp: React.FC = () => { ) } +export function getStaticProps() { + return { + // returns the default 404 page with a status code of 404 in production + notFound: process.env.NODE_ENV === 'production' + } +} + export default SignUp diff --git a/backend/src/components/Header.tsx b/backend/src/components/Header.tsx index 9e9cda0..4fdf507 100644 --- a/backend/src/components/Header.tsx +++ b/backend/src/components/Header.tsx @@ -19,7 +19,6 @@ import { Menu as MenuIcon, Notifications as NotificationsIcon, More as MoreIcon, - Language as LanguageIcon, Settings as SettingsIcon, Inventory as OrdersIcon, AccountTree as CategoriesIcon, @@ -160,10 +159,6 @@ const Header: React.FC = ({ hidden, hideSearch }) => { setMobileMoreAnchorEl(null) } - const handleLangMenuOpen = (event: React.MouseEvent) => { - setLangAnchorEl(event.currentTarget) - } - const refreshPage = () => { router.refresh() } @@ -273,17 +268,6 @@ const Header: React.FC = ({ hidden, hideSearch }) => {

    {strings.SETTINGS}

    - - - - -

    {strings.LANGUAGE}

    -
    diff --git a/backend/src/lib/CategoryService.ts b/backend/src/lib/CategoryService.ts index 83c0327..30c9c2d 100644 --- a/backend/src/lib/CategoryService.ts +++ b/backend/src/lib/CategoryService.ts @@ -110,7 +110,7 @@ export const getCategory = async (language: string, id: string): Promise => ( fetchInstance.GET( - `/api/categories/${language}` + `/api/categories/${language}/${false}` ) .then((res) => res.data) ) diff --git a/backend/src/styles/categories.module.css b/backend/src/styles/categories.module.css index e86fd57..70f22ab 100644 --- a/backend/src/styles/categories.module.css +++ b/backend/src/styles/categories.module.css @@ -7,7 +7,7 @@ div.categories div.categoryList { flex: 1; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.categories { flex-direction: column; } @@ -18,7 +18,7 @@ div.categories div.categoryList { } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .sideBar { width: 300px; } diff --git a/backend/src/styles/category-list.module.css b/backend/src/styles/category-list.module.css index cdd2ce7..d7daf9d 100644 --- a/backend/src/styles/category-list.module.css +++ b/backend/src/styles/category-list.module.css @@ -63,7 +63,7 @@ justify-content: center; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .categoryList { padding: 0 10px; } @@ -73,7 +73,7 @@ } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .categories { width: 100%; } diff --git a/backend/src/styles/category.module.css b/backend/src/styles/category.module.css index 3cef32d..4128bc1 100644 --- a/backend/src/styles/category.module.css +++ b/backend/src/styles/category.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .form { margin-top: 45px !important; margin-left: calc(50% - 180px) !important; @@ -9,7 +9,7 @@ } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .form { margin-top: 45px !important; margin-left: calc(50% - 275px) !important; @@ -23,4 +23,4 @@ text-align: center; text-transform: none; color: #121212; -} \ No newline at end of file +} diff --git a/backend/src/styles/change-password.module.css b/backend/src/styles/change-password.module.css index 27c05b8..b6a2812 100644 --- a/backend/src/styles/change-password.module.css +++ b/backend/src/styles/change-password.module.css @@ -12,7 +12,7 @@ color: #121212; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .form { margin-top: 45px !important; margin-bottom: 45px !important; @@ -22,7 +22,7 @@ } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .form { margin-top: 45px !important; margin-bottom: 45px !important; diff --git a/backend/src/styles/create-category.module.css b/backend/src/styles/create-category.module.css index 271e843..a1b9eac 100644 --- a/backend/src/styles/create-category.module.css +++ b/backend/src/styles/create-category.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .form { margin-top: 45px !important; margin-left: calc(50% - 180px) !important; @@ -9,7 +9,7 @@ } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .form { margin-top: 45px !important; margin-left: calc(50% - 275px) !important; @@ -23,4 +23,4 @@ text-align: center; text-transform: none; color: #121212; -} \ No newline at end of file +} diff --git a/backend/src/styles/create-product.module.css b/backend/src/styles/create-product.module.css index 9b1fffe..6a88526 100644 --- a/backend/src/styles/create-product.module.css +++ b/backend/src/styles/create-product.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .form { margin-top: 45px !important; margin-left: calc(50% - 180px) !important; @@ -9,7 +9,7 @@ } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .form { margin-top: 45px !important; margin-left: calc(50% - 350px) !important; diff --git a/backend/src/styles/forgot-password.module.css b/backend/src/styles/forgot-password.module.css index a412afa..5494e35 100644 --- a/backend/src/styles/forgot-password.module.css +++ b/backend/src/styles/forgot-password.module.css @@ -18,7 +18,7 @@ div.forgotPassword h1.forgotPasswordTitle { text-align: center; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .forgotPasswordForm { margin-top: 40px; width: 330px; @@ -26,10 +26,10 @@ div.forgotPassword h1.forgotPasswordTitle { } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .forgotPasswordForm { margin-top: 40px; width: 450px; padding: 30px; } -} \ No newline at end of file +} diff --git a/backend/src/styles/globals.css b/backend/src/styles/globals.css index 2d6b390..0104f90 100644 --- a/backend/src/styles/globals.css +++ b/backend/src/styles/globals.css @@ -211,7 +211,7 @@ a { @media only screen and (min-width: 600px) {} -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.content { margin: 0; padding: 0; @@ -241,7 +241,7 @@ a { } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.content { margin: 0; diff --git a/backend/src/styles/header.module.css b/backend/src/styles/header.module.css index c7ecd77..0cde19b 100644 --- a/backend/src/styles/header.module.css +++ b/backend/src/styles/header.module.css @@ -110,7 +110,7 @@ img.logo { background-color: #f0f0f0; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .searchIcon { padding: 8px 4px; } @@ -152,7 +152,7 @@ img.logo { color: #121212 !important; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .searchContainer { justify-content: flex-end; @@ -194,7 +194,7 @@ img.logo { } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .signin { color: #121212; text-decoration: none; @@ -213,7 +213,7 @@ img.logo { } } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .headerDesktop { display: none; diff --git a/backend/src/styles/notifications.module.css b/backend/src/styles/notifications.module.css index fef9625..d4cd87a 100644 --- a/backend/src/styles/notifications.module.css +++ b/backend/src/styles/notifications.module.css @@ -8,14 +8,14 @@ div.notifications { align-items: center; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.notifications { top: 55px; } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.notifications { top: 65px; } diff --git a/backend/src/styles/order.module.css b/backend/src/styles/order.module.css index 56abf6c..70213cf 100644 --- a/backend/src/styles/order.module.css +++ b/backend/src/styles/order.module.css @@ -32,7 +32,7 @@ form.order article { justify-content: space-between; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { form.order article { width: calc(100% - 20px); } diff --git a/backend/src/styles/orders.module.css b/backend/src/styles/orders.module.css index 314e063..a4d85a9 100644 --- a/backend/src/styles/orders.module.css +++ b/backend/src/styles/orders.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.main { display: flex; flex-direction: column; @@ -23,7 +23,7 @@ } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.main { max-width: 1184px; diff --git a/backend/src/styles/pager.module.css b/backend/src/styles/pager.module.css index 1695e72..0ac0bca 100644 --- a/backend/src/styles/pager.module.css +++ b/backend/src/styles/pager.module.css @@ -55,7 +55,7 @@ color: #939393 !important; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .pagerContainer { display: flex; flex-direction: row; @@ -63,7 +63,7 @@ } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .pagerContainer { display: flex; flex-direction: row; diff --git a/backend/src/styles/product-list.module.css b/backend/src/styles/product-list.module.css index 4453ffe..899d609 100644 --- a/backend/src/styles/product-list.module.css +++ b/backend/src/styles/product-list.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { article.product { flex: 1 0 100%; max-width: calc(100% - 12px); @@ -14,7 +14,7 @@ } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { article.product { flex: 1 0 21%; diff --git a/backend/src/styles/product.module.css b/backend/src/styles/product.module.css index 53d38c5..772f0e5 100644 --- a/backend/src/styles/product.module.css +++ b/backend/src/styles/product.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .form { margin-top: 45px !important; margin-left: calc(50% - 180px) !important; @@ -9,7 +9,7 @@ } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .form { margin-top: 45px !important; margin-left: calc(50% - 350px) !important; diff --git a/backend/src/styles/products.module.css b/backend/src/styles/products.module.css index 0a5aaa6..ac30005 100644 --- a/backend/src/styles/products.module.css +++ b/backend/src/styles/products.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.main { margin-top: 5px; @@ -42,7 +42,7 @@ } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.main { max-width: 1184px; diff --git a/backend/src/styles/reset-password.module.css b/backend/src/styles/reset-password.module.css index 6cf2dad..8553bd3 100644 --- a/backend/src/styles/reset-password.module.css +++ b/backend/src/styles/reset-password.module.css @@ -32,7 +32,7 @@ div.resend { /* Device width is less than or equal to 960px */ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .resetUserPasswordForm { margin-top: 40px; width: 330px; @@ -50,7 +50,7 @@ div.resend { /* Device width is greater than or equal to 960px */ -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .resetUserPasswordForm { margin-top: 40px; width: 450px; @@ -64,4 +64,4 @@ div.resend { height: 320px; padding: 30px; } -} \ No newline at end of file +} diff --git a/backend/src/styles/settings.module.css b/backend/src/styles/settings.module.css index 05b69fe..26a0452 100644 --- a/backend/src/styles/settings.module.css +++ b/backend/src/styles/settings.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.settings .form { margin: 45px 0 20px calc(50% - 175px); width: 350px; @@ -7,7 +7,7 @@ } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.settings .form { margin: 45px 0 20px calc(50% - 275px); width: 550px; diff --git a/backend/src/styles/signin.module.css b/backend/src/styles/signin.module.css index ed646b1..4871118 100644 --- a/backend/src/styles/signin.module.css +++ b/backend/src/styles/signin.module.css @@ -43,7 +43,7 @@ div.resetPassword { margin-top: 15px; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .signinForm { align-items: center; @@ -69,7 +69,7 @@ div.resetPassword { } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .signinForm { align-items: center; @@ -127,7 +127,7 @@ div.resetPassword { margin-top: 15px; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .signinForm { align-items: center; @@ -153,7 +153,7 @@ div.resetPassword { } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .signinForm { align-items: center; diff --git a/backend/src/styles/signup.module.css b/backend/src/styles/signup.module.css index 48645fb..3b5c9ac 100644 --- a/backend/src/styles/signup.module.css +++ b/backend/src/styles/signup.module.css @@ -15,7 +15,7 @@ /* Device width is less than or equal to 960px */ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .signupForm { margin: 45px 0 120px 0; width: 350px; @@ -25,7 +25,7 @@ /* Device width is greater than or equal to 960px */ -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .signupForm { margin: 85px 0 120px 0; width: 550px; diff --git a/backend/src/styles/users.module.css b/backend/src/styles/users.module.css index 13500af..7da2870 100644 --- a/backend/src/styles/users.module.css +++ b/backend/src/styles/users.module.css @@ -1,6 +1,6 @@ -@media only screen and (min-width: 960px) {} +@media only screen and (width >=960px) {} -@media only screen and (max-width: 960px) {} +@media only screen and (width <=960px) {} .users { display: flex; @@ -124,4 +124,4 @@ a.disabled:hover { pointer-events: none; cursor: default; color: #939393 !important; -} \ No newline at end of file +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1b16c0c..377c7da 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,15 +15,19 @@ "@mui/x-date-pickers": "^7.19.0", "@stripe/react-stripe-js": "^2.8.1", "@stripe/stripe-js": "^4.7.0", + "@types/react-slick": "^0.23.13", "@types/validator": "^13.12.2", "date-fns": "^2.30.0", + "github-fork-ribbon-css": "^0.2.3", "next": "14.2.14", "next-nprogress-bar": "^2.3.13", "react": "^18.3.1", "react-dom": "^18.3.1", "react-localization": "^1.0.19", + "react-slick": "^0.30.2", "react-toastify": "^10.0.5", "reactjs-social-login": "^2.6.3", + "slick-carousel": "^1.8.1", "validator": "^13.12.0" }, "devDependencies": { @@ -1331,6 +1335,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-slick": { + "version": "0.23.13", + "resolved": "https://registry.npmjs.org/@types/react-slick/-/react-slick-0.23.13.tgz", + "integrity": "sha512-bNZfDhe/L8t5OQzIyhrRhBr/61pfBcWaYJoq6UDqFtv5LMwfg4NsVDD2J8N01JqdAdxLjOt66OZEp6PX+dGs/A==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.11", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", @@ -1976,6 +1989,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -2282,6 +2301,12 @@ "node": ">=10.13.0" } }, + "node_modules/enquire.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/enquire.js/-/enquire.js-2.1.6.tgz", + "integrity": "sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw==", + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3195,6 +3220,12 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-fork-ribbon-css": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/github-fork-ribbon-css/-/github-fork-ribbon-css-0.2.3.tgz", + "integrity": "sha512-cmGBV4sivRwmnteSOkqMjN2cnP5/J1SU5aDCVYsBWHmDokZ/JjwGEkduCxY9IULHdCPpw1WSk5Cy8N1LF6jOEw==", + "license": "MIT" + }, "node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -3946,6 +3977,13 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "license": "MIT", + "peer": true + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4004,6 +4042,15 @@ "dev": true, "license": "MIT" }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, "node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", @@ -4105,6 +4152,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4738,6 +4791,23 @@ "react": "^18.0.0 || ^17.0.0 || ^16.0.0 || ^15.6.0" } }, + "node_modules/react-slick": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.30.2.tgz", + "integrity": "sha512-XvQJi7mRHuiU3b9irsqS9SGIgftIfdV5/tNcURTb5LdIokRA5kIIx3l4rlq2XYHfxcSntXapoRg/GxaVOM1yfg==", + "license": "MIT", + "dependencies": { + "classnames": "^2.2.5", + "enquire.js": "^2.1.6", + "json2mq": "^0.2.0", + "lodash.debounce": "^4.0.8", + "resize-observer-polyfill": "^1.5.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-toastify": { "version": "10.0.5", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz", @@ -4827,6 +4897,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -5085,6 +5161,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/slick-carousel": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/slick-carousel/-/slick-carousel-1.8.1.tgz", + "integrity": "sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA==", + "license": "MIT", + "peerDependencies": { + "jquery": ">=1.8.0" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -5124,6 +5209,12 @@ "node": ">=10.0.0" } }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 357cc9c..8815deb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,15 +16,19 @@ "@mui/x-date-pickers": "^7.19.0", "@stripe/react-stripe-js": "^2.8.1", "@stripe/stripe-js": "^4.7.0", + "@types/react-slick": "^0.23.13", "@types/validator": "^13.12.2", "date-fns": "^2.30.0", + "github-fork-ribbon-css": "^0.2.3", "next": "14.2.14", "next-nprogress-bar": "^2.3.13", "react": "^18.3.1", "react-dom": "^18.3.1", "react-localization": "^1.0.19", + "react-slick": "^0.30.2", "react-toastify": "^10.0.5", "reactjs-social-login": "^2.6.3", + "slick-carousel": "^1.8.1", "validator": "^13.12.0" }, "devDependencies": { diff --git a/frontend/public/slides/slide1.jpg b/frontend/public/slides/slide1.jpg new file mode 100644 index 0000000..1c3a237 Binary files /dev/null and b/frontend/public/slides/slide1.jpg differ diff --git a/frontend/public/slides/slide2.jpg b/frontend/public/slides/slide2.jpg new file mode 100644 index 0000000..98b0f82 Binary files /dev/null and b/frontend/public/slides/slide2.jpg differ diff --git a/frontend/public/slides/slide3.jpg b/frontend/public/slides/slide3.jpg new file mode 100644 index 0000000..b8383a0 Binary files /dev/null and b/frontend/public/slides/slide3.jpg differ diff --git a/frontend/public/slides/slide4.jpg b/frontend/public/slides/slide4.jpg new file mode 100644 index 0000000..3743a84 Binary files /dev/null and b/frontend/public/slides/slide4.jpg differ diff --git a/frontend/src/app/(pages)/(login)/sign-in/page.tsx b/frontend/src/app/(pages)/(login)/sign-in/page.tsx index 403e5fd..a13e4d6 100644 --- a/frontend/src/app/(pages)/(login)/sign-in/page.tsx +++ b/frontend/src/app/(pages)/(login)/sign-in/page.tsx @@ -137,6 +137,7 @@ const SignIn: React.FC = () => { diff --git a/frontend/src/app/(pages)/(public)/checkout/page.tsx b/frontend/src/app/(pages)/(public)/checkout/page.tsx index 9ce7792..42be7f9 100644 --- a/frontend/src/app/(pages)/(public)/checkout/page.tsx +++ b/frontend/src/app/(pages)/(public)/checkout/page.tsx @@ -24,7 +24,7 @@ const Cart = async () => { console.error(err) } - return paymentTypes && deliveryTypes ? ( + return cart && paymentTypes && deliveryTypes ? ( { +import * as wexcommerceTypes from ':wexcommerce-types' +import env from '@/config/env.config' +import * as SettingService from '@/lib/SettingService' +import * as ProductService from '@/lib/ProductService' +import * as CartService from '@/lib/CartService' +import * as CategoryService from '@/lib/CategoryService' +import { strings } from '@/lang/home' +import FeaturedProducts from '@/components/FeaturedProducts' +import Carrousel from '@/components/Carrousel' +import CategoryList from '@/components/CategoryList' +import FeaturedCategories from '@/components/FeaturedCategories' + +import styles from '@/styles/home.module.css' + +const slides = [ + '/slides/slide1.jpg', + '/slides/slide2.jpg', + '/slides/slide3.jpg', + '/slides/slide4.jpg', +] + +const Home = async () => { + + let featuredProducts: wexcommerceTypes.Product[] = [] + let categories: wexcommerceTypes.CategoryInfo[] = [] + let categoryGroups: wexcommerceTypes.FeaturedCategory[] = [] + + try { + const cartId = await CartService.getCartId() + featuredProducts = await ProductService.getFeaturedProducts(env.FEATURED_PRODUCTS_SIZE, cartId) + + const language = await SettingService.getLanguage() + categories = await CategoryService.getCategories(language, true) + + categoryGroups = await CategoryService.getFeaturedCategories(language, env.FEATURED_PRODUCTS_SIZE, cartId) + } catch (err) { + console.error(err) + } + return ( -
    Home
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    ) } diff --git a/frontend/src/app/(pages)/(public)/search/page.client.tsx b/frontend/src/app/(pages)/(public)/search/page.client.tsx index 3df9f91..6d77329 100644 --- a/frontend/src/app/(pages)/(public)/search/page.client.tsx +++ b/frontend/src/app/(pages)/(public)/search/page.client.tsx @@ -40,7 +40,7 @@ const ProductsWrapper: React.FC = ({ children }) => { useEffect(() => { const fetchCategories = async () => { if (language) { - const _categories = await CategoryService.getCategories(language) + const _categories = await CategoryService.getCategories(language, false) setCategories(_categories) } } diff --git a/frontend/src/app/(pages)/layout.tsx b/frontend/src/app/(pages)/layout.tsx index a492b4c..3f65cf9 100644 --- a/frontend/src/app/(pages)/layout.tsx +++ b/frontend/src/app/(pages)/layout.tsx @@ -200,7 +200,7 @@ const Layout: React.FC = ({ children }) => { pauseOnFocusLoss={false} draggable={false} pauseOnHover={true} - theme='dark' + theme='light' /> diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index a54d143..f8e6d7f 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,4 +1,6 @@ import type { Metadata } from 'next' + +import 'github-fork-ribbon-css/gh-fork-ribbon.css' import '@/styles/globals.css' export const dynamic = 'force-dynamic' @@ -16,7 +18,15 @@ type RootLayoutProps = Readonly<{ const RootLayout: React.FC = ({ children }) => ( - {children} + + Fork me on GitHub + + {children} + ) diff --git a/frontend/src/components/Carrousel.tsx b/frontend/src/components/Carrousel.tsx new file mode 100644 index 0000000..1443475 --- /dev/null +++ b/frontend/src/components/Carrousel.tsx @@ -0,0 +1,102 @@ +'use client' + +import React, { useRef } from 'react' +import Slider from 'react-slick' +import Image from 'next/image' +import { Button } from '@mui/material' +import { ArrowRight, ArrowLeft } from '@mui/icons-material' +import { strings as commonStrings } from '@/lang/common' + +import styles from '@/styles/carrousel.module.css' + +import 'slick-carousel/slick/slick.css' +import 'slick-carousel/slick/slick-theme.css' + +interface CarrouselProps { + title?: string + images: string[] + autoplay?: boolean + autoplaySpeed?: number // in milliseconds + showNavigation?: boolean +} + +const Carrousel: React.FC = ({ + title, + images, + autoplay, + autoplaySpeed, + showNavigation, +}) => { + + const slider = useRef(null) + + const sliderSettings = { + arrows: false, + dots: true, + // eslint-disable-next-line react/no-unstable-nested-components + appendDots: (dots: React.ReactNode) => (showNavigation || !autoplay) ? ( +
    +
      + + {' '} + {dots} + {' '} + +
    +
    + ) : <>, + + infinite: true, + speed: 500, + autoplay: !!autoplay, + autoplaySpeed: autoplaySpeed || (3 * 1000), + + // centerMode: true, + + slidesToShow: 1, + slidesToScroll: 1, + variableWidth: false, + + responsive: [ + { + breakpoint: 960, + settings: { + slidesToShow: 1, + slidesToScroll: 1, + variableWidth: false, + } + } + ] + } + + return ( +
    + {title &&

    {title}

    } + + { + images.map((image, index) => ( +
    + +
    + )) + } +
    +
    + ) +} + +export default Carrousel diff --git a/frontend/src/components/Cart.tsx b/frontend/src/components/Cart.tsx index c522187..dfe13a5 100644 --- a/frontend/src/components/Cart.tsx +++ b/frontend/src/components/Cart.tsx @@ -232,7 +232,7 @@ const Cart: React.FC = ({ cart }) => { setTotal(helper.total(_cartItems)) if (res.data.cartDeleted) { - CartService.deleteCartId() + await CartService.deleteCartId() } helper.info(commonStrings.ARTICLE_REMOVED) @@ -263,7 +263,7 @@ const Cart: React.FC = ({ cart }) => { const status = await CartService.clearCart(cartId) if (status === 200) { - CartService.deleteCartId() + await CartService.deleteCartId() setCartItems([]) setCartItemCount(0) setTotal(0) diff --git a/frontend/src/components/CategoryList.tsx b/frontend/src/components/CategoryList.tsx new file mode 100644 index 0000000..397a406 --- /dev/null +++ b/frontend/src/components/CategoryList.tsx @@ -0,0 +1,107 @@ +'use client' + +import React, { useRef } from 'react' +import Slider from 'react-slick' +import Link from 'next/link' +import Image from 'next/image' +import { Button } from '@mui/material' +import { ArrowRight, ArrowLeft } from '@mui/icons-material' +import * as wexcommerceTypes from ':wexcommerce-types' +import * as wexcommerceHelper from ':wexcommerce-helper' +import env from '@/config/env.config' +import { strings as commonStrings } from '@/lang/common' + +import styles from '@/styles/category-list.module.css' + +import 'slick-carousel/slick/slick.css' +import 'slick-carousel/slick/slick-theme.css' + +interface CategoryListProps { + title?: string, + categories: wexcommerceTypes.CategoryInfo[] + autoplay?: boolean + autoplaySpeed?: number // in milliseconds + showNavigation?: boolean +} + +const CategoryList: React.FC = ( + { + title, + categories, + autoplay, + autoplaySpeed, + showNavigation, + }) => { + + const slider = useRef(null) + + const sliderSettings = { + arrows: false, + dots: true, + appendDots: (dots: React.ReactNode) => (showNavigation || !autoplay) ? ( +
    +
      + + {' '} + {dots} + {' '} + +
    +
    + ) : <>, + infinite: true, + speed: 500, + autoplay: !!autoplay, + autoplaySpeed: autoplaySpeed || (3 * 1000), + + slidesToShow: 3, + slidesToScroll: 1, + variableWidth: false, + responsive: [ + { + breakpoint: 960, + settings: { + slidesToShow: 1, + slidesToScroll: 1, + variableWidth: false, + } + } + ] + } + + return categories.length >= 4 && ( +
    + {title &&

    {title}

    } + + { + categories.map((category) => ( +
    + +
    + +
    + {category.name} + +
    + )) + } +
    +
    + ) +} + +export default CategoryList diff --git a/frontend/src/components/FeaturedCategories.tsx b/frontend/src/components/FeaturedCategories.tsx new file mode 100644 index 0000000..f840d60 --- /dev/null +++ b/frontend/src/components/FeaturedCategories.tsx @@ -0,0 +1,29 @@ +'use client' + +import React from 'react' +import * as wexcommerceTypes from ':wexcommerce-types' +import FeaturedProducts from './FeaturedProducts' + +import styles from '@/styles/featured-categories.module.css' +import Link from 'next/link' + +interface FeaturedCategoriesProps { + categoryGroups: wexcommerceTypes.FeaturedCategory[] +} + +const FeaturedCategories: React.FC = ({ categoryGroups }) => categoryGroups.length > 0 && ( +
    + { + categoryGroups.map((categoryGroup) => ( +
    + {categoryGroup.category.name} +
    + +
    +
    + )) + } +
    +) + +export default FeaturedCategories diff --git a/frontend/src/components/FeaturedProducts.tsx b/frontend/src/components/FeaturedProducts.tsx new file mode 100644 index 0000000..ffb1cd6 --- /dev/null +++ b/frontend/src/components/FeaturedProducts.tsx @@ -0,0 +1,193 @@ +'use client' + +import React, { useRef, useState } from 'react' +import Slider from 'react-slick' +import Link from 'next/link' +import Image from 'next/image' +import { Button, IconButton } from '@mui/material' +import { ArrowRight, ArrowLeft } from '@mui/icons-material' +import { ShoppingCart as CartIcon } from '@mui/icons-material' +import * as wexcommerceTypes from ':wexcommerce-types' +import * as wexcommerceHelper from ':wexcommerce-helper' +import env from '@/config/env.config' +import * as helper from '@/common/helper' +import { strings as commonStrings } from '@/lang/common' +import * as CartService from '@/lib/CartService' +import { LanguageContextType, useLanguageContext } from '@/context/LanguageContext' +import { CurrencyContextType, useCurrencyContext } from '@/context/CurrencyContext' +import { CartContextType, useCartContext } from '@/context/CartContext' +import { UserContextType, useUserContext } from '@/context/UserContext' + +import styles from '@/styles/featured-products.module.css' + +import 'slick-carousel/slick/slick.css' +import 'slick-carousel/slick/slick-theme.css' + +interface FeaturedProductsProps { + title?: string + products: wexcommerceTypes.Product[] + autoplay?: boolean + autoplaySpeed?: number // in milliseconds + showNavigation?: boolean +} + +const FeaturedProducts: React.FC = ( + { + title, + products: productsFromProps, + autoplay, + autoplaySpeed, + showNavigation, + }) => { + const { language } = useLanguageContext() as LanguageContextType + const { currency } = useCurrencyContext() as CurrencyContextType + const { user } = useUserContext() as UserContextType + const { cartItemCount, setCartItemCount } = useCartContext() as CartContextType + const [products, setProducts] = useState(productsFromProps) + + const slider = useRef(null) + + const sliderSettings = { + arrows: false, + dots: true, + // eslint-disable-next-line react/no-unstable-nested-components + appendDots: (dots: React.ReactNode) => (showNavigation || !autoplay) ? ( +
    +
      + + {' '} + {dots} + {' '} + +
    +
    + ) : <>, + + infinite: true, + speed: 500, + autoplay: !!autoplay, + autoplaySpeed: autoplaySpeed || (3 * 1000), + + slidesToShow: 3, + slidesToScroll: 1, + variableWidth: false, + responsive: [ + { + breakpoint: 960, + settings: { + slidesToShow: 1, + slidesToScroll: 1, + variableWidth: false, + } + } + ] + } + + if (products.length < 4) { + return null + } + + return products.length > 0 && ( +
    + {title &&

    {title}

    } + + { + products.map((product, index) => ( +
    + +
    + +
    + {product.name} + {`${wexcommerceHelper.formatPrice(product.price, currency, language)}`} + + +
    + { + product.inCart ? + + : + { + try { + const cartId = await CartService.getCartId() + const userId = (user && user._id) || '' + + const res = await CartService.addItem(cartId, userId, product._id) + + if (res.status === 200) { + if (!cartId) { + console.log('res.data', res.data) + await CartService.setCartId(res.data) + } + const _products = wexcommerceHelper.cloneArray(products) as wexcommerceTypes.Product[] + _products[index].inCart = true + setProducts(_products) + setCartItemCount(cartItemCount + 1) + helper.info(commonStrings.ARTICLE_ADDED) + } else { + helper.error() + } + } catch (err) { + helper.error(err) + } + }} + > + + + } +
    +
    + )) + } +
    +
    + ) +} + +export default FeaturedProducts diff --git a/frontend/src/components/Product.tsx b/frontend/src/components/Product.tsx index a7c7b37..455c187 100644 --- a/frontend/src/components/Product.tsx +++ b/frontend/src/components/Product.tsx @@ -148,7 +148,7 @@ const Product: React.FC = ({ product: productFromProps }) => { setCartItemCount(cartItemCount - 1) if (res.data.cartDeleted) { - CartService.deleteCartId() + await CartService.deleteCartId() } setOpenDeleteDialog(false) @@ -240,7 +240,7 @@ const Product: React.FC = ({ product: productFromProps }) => { setCartItemCount(cartItemCount - 1) if (res.data.cartDeleted) { - CartService.deleteCartId() + await CartService.deleteCartId() } setOpenDeleteDialog(false) diff --git a/frontend/src/components/ProductList.client.tsx b/frontend/src/components/ProductList.client.tsx index 4d79cde..6f2e4b8 100644 --- a/frontend/src/components/ProductList.client.tsx +++ b/frontend/src/components/ProductList.client.tsx @@ -13,9 +13,7 @@ import { DialogActions, IconButton, } from '@mui/material' -import { - ShoppingCart as CartIcon -} from '@mui/icons-material' +import { ShoppingCart as CartIcon } from '@mui/icons-material' import * as wexcommerceTypes from ':wexcommerce-types' import env from '@/config/env.config' import { strings } from '@/lang/product-list' @@ -104,11 +102,11 @@ export const Actions: React.FC = ({ product }) => { setCartItemCount(cartItemCount - 1) if (res.data.cartDeleted) { - CartService.deleteCartId() + await CartService.deleteCartId() } setOpenDeleteDialog(false) - // helper.info(commonStrings.ARTICLE_REMOVED) + helper.info(commonStrings.ARTICLE_REMOVED) } else { helper.error() } @@ -137,7 +135,7 @@ export const Actions: React.FC = ({ product }) => { } setInCart(true) setCartItemCount(cartItemCount + 1) - // helper.info(commonStrings.ARTICLE_ADDED) + helper.info(commonStrings.ARTICLE_ADDED) } else { helper.error() } @@ -169,7 +167,7 @@ export const Actions: React.FC = ({ product }) => { setCartItemCount(cartItemCount - 1) if (res.data.cartDeleted) { - CartService.deleteCartId() + await CartService.deleteCartId() } setOpenDeleteDialog(false) diff --git a/frontend/src/config/env.config.ts b/frontend/src/config/env.config.ts index 8116f32..3c989e6 100644 --- a/frontend/src/config/env.config.ts +++ b/frontend/src/config/env.config.ts @@ -21,11 +21,12 @@ const env = { DEFAULT_LANGUAGE: process.env.NEXT_PUBLIC_WC_DEFAULT_LANGUAGE || 'en', PAGE_SIZE: Number.parseInt(process.env.NEXT_PUBLIC_WC_PAGE_SIZE || '30'), CDN_PRODUCTS: process.env.NEXT_PUBLIC_WC_CDN_PRODUCTS, - CDN_TEMP_PRODUCTS: process.env.NEXT_PUBLIC_WC_CDN_TEMP_PRODUCTS, + CDN_CATEGORIES: process.env.NEXT_PUBLIC_WC_CDN_CATEGORIES, FB_APP_ID: String(process.env.NEXT_PUBLIC_WC_FB_APP_ID), APPLE_ID: String(process.env.NEXT_PUBLIC_WC_APPLE_ID), GG_APP_ID: String(process.env.NEXT_PUBLIC_WC_GG_APP_ID), STRIPE_PUBLISHABLE_KEY: String(process.env.NEXT_PUBLIC_WC_STRIPE_PUBLISHABLE_KEY), + FEATURED_PRODUCTS_SIZE: 10, } export const CookieOptions: Partial = { diff --git a/frontend/src/lang/common.ts b/frontend/src/lang/common.ts index 2d77e1e..4f8604a 100644 --- a/frontend/src/lang/common.ts +++ b/frontend/src/lang/common.ts @@ -59,6 +59,8 @@ export const strings = new LocalizedStrings({ SHIPPING: 'Livraison à domicile', WITHDRAWAL: 'Retrait en magasin', OR: 'ou', + BACK: 'Précédant', + NEXT: 'Suivant', }, en: { GENERIC_ERROR: 'An unhandled error occurred.', @@ -118,5 +120,7 @@ export const strings = new LocalizedStrings({ SHIPPING: 'Home delivery', WITHDRAWAL: 'Store withdrawal', OR: 'or', + BACK: 'Back', + NEXT: 'Next', } }) diff --git a/frontend/src/lang/home.ts b/frontend/src/lang/home.ts index 3fa1ee7..57b7954 100644 --- a/frontend/src/lang/home.ts +++ b/frontend/src/lang/home.ts @@ -2,7 +2,11 @@ import LocalizedStrings from 'react-localization' export const strings = new LocalizedStrings({ fr: { + FEATURED_PRODUCTS_TITLE: 'Produits en vedette', + CATEGORIES_TITLE: 'Parcourir les catégories', }, en: { + FEATURED_PRODUCTS_TITLE: 'Featured products', + CATEGORIES_TITLE: 'Browse categories', } }) diff --git a/frontend/src/lib/CategoryService.ts b/frontend/src/lib/CategoryService.ts index 17c8587..bbd1f47 100644 --- a/frontend/src/lib/CategoryService.ts +++ b/frontend/src/lib/CategoryService.ts @@ -9,9 +9,25 @@ import * as fetchInstance from './fetchInstance' * @param {string} language * @returns {Promise} */ -export const getCategories = async (language: string): Promise => ( +export const getCategories = async (language: string, imageRequired: boolean): Promise => ( fetchInstance.GET( - `/api/categories/${language}` + `/api/categories/${language}/${imageRequired}` + ) + .then((res) => res.data) +) + +/** + * Get featured categories with products. + * + * @async + * @param {string} language + * @param {number} size + * @param {string} cartId + * @returns {Promise} + */ +export const getFeaturedCategories = async (language: string, size: number, cartId: string): Promise => ( + fetchInstance.GET( + `/api/featured-categories/${language}/${size}?c=${cartId}` ) .then((res) => res.data) ) diff --git a/frontend/src/lib/ProductService.ts b/frontend/src/lib/ProductService.ts index b371aed..720878d 100644 --- a/frontend/src/lib/ProductService.ts +++ b/frontend/src/lib/ProductService.ts @@ -12,7 +12,7 @@ import * as fetchInstance from './fetchInstance' * @returns {Promise} */ export const getProduct = async (id: string, language: string, cartId: string): Promise => { - const data = { cart: cartId } + const data: wexcommerceTypes.GetProductPayload = { cart: cartId } return fetchInstance .POST( @@ -39,7 +39,7 @@ export const getProducts = async ( categoryId: string, cartId: string ): Promise> => { - const data = { cart: cartId } + const data: wexcommerceTypes.GetProductsPayload = { cart: cartId } return fetchInstance .POST( @@ -48,3 +48,17 @@ export const getProducts = async ( ) .then((res) => res.data) } + +export const getFeaturedProducts = async ( + size: number, + cartId: string +): Promise => { + const data: wexcommerceTypes.GetProductsPayload = { cart: cartId, size } + + return fetchInstance + .POST( + '/api/featured-products/', + data, + ) + .then((res) => res.data) +} diff --git a/frontend/src/lib/UserService.ts b/frontend/src/lib/UserService.ts index b638e34..8c09ec1 100644 --- a/frontend/src/lib/UserService.ts +++ b/frontend/src/lib/UserService.ts @@ -127,7 +127,7 @@ export const signout = async (_redirect = true, _redirectSignIn = false, _delete cookies().delete('wc-fe-user') if (_deleteCartId) { - CartService.deleteCartId() + await CartService.deleteCartId() } if (_redirect) { diff --git a/frontend/src/styles/activate.module.css b/frontend/src/styles/activate.module.css index 8cf76dd..2fc1a0c 100644 --- a/frontend/src/styles/activate.module.css +++ b/frontend/src/styles/activate.module.css @@ -25,7 +25,7 @@ div.resend { /* Device width is less than or equal to 960px */ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .activateForm { margin-top: 40px; @@ -44,7 +44,7 @@ div.resend { /* Device width is greater than or equal to 960px */ -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .activateForm { margin-top: 40px; @@ -59,4 +59,4 @@ div.resend { height: 270px; padding: 30px; } -} \ No newline at end of file +} diff --git a/frontend/src/styles/carrousel.module.css b/frontend/src/styles/carrousel.module.css new file mode 100644 index 0000000..f3b6bc8 --- /dev/null +++ b/frontend/src/styles/carrousel.module.css @@ -0,0 +1,55 @@ +section.main { + flex: 1; + display: grid; + place-items: center; +} + +section.main h1.title { + text-align: center; + font-size: 26px; + font-weight: 500; +} + +section.main .slider { + height: 320px; + width: 1075px; +} + +section.main .slickDots { + bottom: -60px; +} + +section.main .slickDots li.slickActive button:before { + color: #1976D2; +} + +section.main .btnSlider { + color: #000 !important; +} + +section.main .btnSliderPrev { + margin-right: 60px; +} + +section.main .btnSliderNext { + margin-left: 60px; +} + +section.main article.image { + height: 300px; + background-color: #fff; + margin: auto; +} + +section.main img.image { + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; +} + +@media only screen and (width <=960px) { + section.main { + display: none; + } +} diff --git a/frontend/src/styles/cart.module.css b/frontend/src/styles/cart.module.css index c8afc62..4a8572a 100644 --- a/frontend/src/styles/cart.module.css +++ b/frontend/src/styles/cart.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.main { margin-top: 20px; } @@ -15,7 +15,7 @@ } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.main { max-width: 1184px; margin-right: auto; diff --git a/frontend/src/styles/category-list.module.css b/frontend/src/styles/category-list.module.css new file mode 100644 index 0000000..8cb5e91 --- /dev/null +++ b/frontend/src/styles/category-list.module.css @@ -0,0 +1,105 @@ +section.main { + flex: 1; +} + +section.main h1.title { + text-align: center; + font-size: 26px; + font-weight: 500; +} + +section.main .slider { + height: 320px; +} + +section.main .slider { + height: 320px; +} + +section.main .slickDots { + bottom: -60px; +} + +section.main .slickDots li.slickActive button:before { + color: #1976D2; +} + +section.main .btnSlider { + color: #000 !important; +} + +section.main .btnSliderPrev { + margin-right: 60px; +} + +section.main .btnSliderNext { + margin-left: 60px; +} + + +section.main article.category { + width: 346px !important; + height: 300px; + background-color: #fff; + display: flex !important; + flex-direction: column; + justify-content: center; + margin-right: 30px; + padding: 6px; +} + +section.main article.category a { + color: #030303; + text-decoration: none; + display: flex; + flex-direction: column; + align-items: center; +} + +section.main article.category div.thumbnail { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 346px; + height: 140px; +} + +section.main article.category img.thumbnail { + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; +} + +section.main article.category span.name { + overflow: hidden; + display: block; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + text-overflow: ellipsis; + white-space: normal; + text-align: center; + font-weight: 300; +} + +@media only screen and (width <=960px) { + section.main article.category { + margin-right: auto; + margin-left: auto; + } + + section.main article.category span.name { + text-align: center; + padding: 5px; + } + + section.main .btnSliderPrev { + margin-right: 20px; + } + + section.main .btnSliderNext { + margin-left: 20px; + } +} diff --git a/frontend/src/styles/change-password.module.css b/frontend/src/styles/change-password.module.css index ec1cacc..472996c 100644 --- a/frontend/src/styles/change-password.module.css +++ b/frontend/src/styles/change-password.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .form { margin-top: 45px !important; margin-bottom: 45px !important; @@ -9,7 +9,7 @@ } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .form { margin-top: 45px !important; margin-bottom: 45px !important; @@ -23,4 +23,4 @@ text-align: center; text-transform: none; color: #1d1d1b; -} \ No newline at end of file +} diff --git a/frontend/src/styles/checkout.module.css b/frontend/src/styles/checkout.module.css index 5834df9..1f1b5bf 100644 --- a/frontend/src/styles/checkout.module.css +++ b/frontend/src/styles/checkout.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) {} +@media only screen and (width <=960px) {} @media only screen and (max-width: 550px) { @@ -25,7 +25,7 @@ } } -@media only screen and (min-width: 960px) {} +@media only screen and (width >=960px) {} .checkoutForm { padding: 30px; @@ -183,7 +183,7 @@ width: 550px; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .checkoutForm .stripe { width: calc(100% - 20px); } diff --git a/frontend/src/styles/featured-categories.module.css b/frontend/src/styles/featured-categories.module.css new file mode 100644 index 0000000..98ea751 --- /dev/null +++ b/frontend/src/styles/featured-categories.module.css @@ -0,0 +1,44 @@ +section.main { + flex: 1; + margin: 15px 0; +} + +section.main article.categoryGroup { + flex: 1; +} + +section.main article.categoryGroup .title { + color: rgba(0, 0, 0, 0.87); + text-align: left; + text-decoration: none; + font-size: 24px; + font-weight: 500; +} + +section.main article.categoryGroup section.products { + margin-top: 15px; + margin-bottom: 75px; +} + +section.main article.categoryGroup .title:hover { + opacity: 0.87; +} + +@media only screen and (width <=960px) { + section.main .btnSliderPrev { + margin-right: 20px; + } + + section.main .btnSliderNext { + margin-left: 20px; + } + + section.main article.categoryGroup .title { + margin-left: 15px; + } + + section.main article.categoryGroup { + margin-right: auto; + margin-left: auto; + } +} diff --git a/frontend/src/styles/featured-products.module.css b/frontend/src/styles/featured-products.module.css new file mode 100644 index 0000000..ac5d61d --- /dev/null +++ b/frontend/src/styles/featured-products.module.css @@ -0,0 +1,129 @@ +section.main { + flex: 1; +} + +section.main h1.title { + text-align: center; + font-size: 26px; + font-weight: 500; +} + +section.main .slider { + height: 320px; +} + +section.main .slickDots { + bottom: -60px; +} + +section.main .slickDots li.slickActive button:before { + color: #1976D2; +} + +section.main .btnSlider { + color: #000 !important; +} + +section.main .btnSliderPrev { + margin-right: 60px; +} + +section.main .btnSliderNext { + margin-left: 60px; +} + +section.main article.product { + width: 346px !important; + height: 300px; + background-color: #fff; + display: flex !important; + flex-direction: column; + justify-content: center; + margin-right: 30px; + padding: 6px; +} + +section.main article.product a { + color: #030303; + text-decoration: none; + display: flex; + flex-direction: column; + align-items: center; +} + +section.main article.product div.thumbnail { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 346px; + height: 140px; +} + +section.main article.product img.thumbnail { + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; +} + +section.main article.product span.name { + overflow: hidden; + display: block; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + text-overflow: ellipsis; + white-space: normal; + text-align: center; + font-weight: 300; +} + +section.main article.product span.price { + display: flex; + justify-content: center; + font-weight: bold; +} + +section.main article.product div.actions { + display: flex; + flex-direction: row; + justify-content: flex-end; +} + +section.main article.product div.actions .button { + width: 36px; + height: 36px; +} + +section.main article.product div.actions .buttonIcon { + width: 22px; +} + +section.main article.product div.actions .button:hover { + opacity: 0.8; +} + +section.main article.product div.actions .removeButton { + width: 100%; +} + +@media only screen and (width <=960px) { + section.main .btnSliderPrev { + margin-right: 20px; + } + + section.main .btnSliderNext { + margin-left: 20px; + } + + section.main article.product { + margin-right: auto; + margin-left: auto; + } + + section.main article.product span.name { + text-align: center; + padding: 5px; + } +} diff --git a/frontend/src/styles/footer.module.css b/frontend/src/styles/footer.module.css index fbfa71d..3d3f87f 100644 --- a/frontend/src/styles/footer.module.css +++ b/frontend/src/styles/footer.module.css @@ -21,7 +21,7 @@ div.footer div.main div.copyright { justify-content: space-between; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.footer div.main { margin-right: 15px; margin-left: 15px; @@ -44,7 +44,7 @@ div.footer div.main div.copyright { } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.footer div.main { max-width: 1184px; margin-right: auto; @@ -54,7 +54,7 @@ div.footer div.main div.copyright { } } -@media only screen and (min-width: 960px) and (max-width: 1184px) { +@media only screen and (width >=960px) and (max-width: 1184px) { div.footer div.main div.content { margin-right: 15px; } diff --git a/frontend/src/styles/forgot-password.module.css b/frontend/src/styles/forgot-password.module.css index ab9ffed..4f4c117 100644 --- a/frontend/src/styles/forgot-password.module.css +++ b/frontend/src/styles/forgot-password.module.css @@ -20,7 +20,7 @@ div.forgotPassword h1.forgotPasswordTitle { /* Device width is less than or equal to 960px */ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .forgotPasswordForm { margin-top: 40px; width: 330px; @@ -30,10 +30,10 @@ div.forgotPassword h1.forgotPasswordTitle { /* Device width is greater than or equal to 960px */ -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .forgotPasswordForm { margin-top: 40px; width: 450px; padding: 30px; } -} \ No newline at end of file +} diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index a4a5bc7..86bc321 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -203,7 +203,7 @@ a { height: 20px !important; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.content { min-height: calc(100vh - 283px); } @@ -244,7 +244,7 @@ div.content { flex: 1; } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.content { min-height: calc(100vh - 90px); } @@ -280,3 +280,7 @@ input[type=number] { height: 100%; overflow: hidden; } + +.github-fork-ribbon:before { + background-color: #272727; +} diff --git a/frontend/src/styles/header.module.css b/frontend/src/styles/header.module.css index c47fbb3..bcc6dd5 100644 --- a/frontend/src/styles/header.module.css +++ b/frontend/src/styles/header.module.css @@ -95,7 +95,7 @@ img.logo { background-color: #f0f0f0; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .searchIcon { padding: 8px 0; } @@ -137,7 +137,7 @@ img.logo { color: #121212 !important; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .searchContainer { justify-content: flex-end; @@ -179,7 +179,7 @@ img.logo { } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .signin { color: #121212; text-decoration: none; @@ -199,7 +199,7 @@ img.logo { } } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .headerDesktop { display: none; diff --git a/frontend/src/styles/home.module.css b/frontend/src/styles/home.module.css index a885f5e..10fab58 100644 --- a/frontend/src/styles/home.module.css +++ b/frontend/src/styles/home.module.css @@ -1,321 +1,48 @@ -@media only screen and (max-width: 960px) { - - div.main { - margin-top: 5px; - } - - .newProduct { - width: calc(100% - 20px); - margin: 10px !important; - } - - div.categoriesAction { - display: flex; - flex: 1; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 15px 15px 10px 15px; - font-weight: 600; - color: #030303; - } - - div.categoriesAction div.categoriesText { - display: flex; - flex: 1; - flex-direction: row; - align-items: center; - } - - div.categoriesAction .categoriesIcon { - margin-right: 5px; - } - - div.categoriesAction .closeIcon { - visibility: hidden; - } - - div.leftPanel { - display: none; - width: 100%; - margin: 0 5px; - } - - div.productList { - flex-direction: column; - align-items: center; - } - - article.product { - flex: 1 0 100%; - width: 100%; - max-width: 390px; - height: 320px; - margin: 3px 6px 3px 6px; - background-color: #fff; - padding: 6px; - } - - article.product span.name { - text-align: center; - padding: 5px; - } -} - -@media only screen and (min-width: 960px) { - - div.main { - max-width: 1184px; - margin-right: auto; - margin-left: auto; - padding-bottom: 30px; - margin-top: 15px; - } - - div.leftPanel { - width: 275px; - } - - div.leftPanel .newProduct { - width: 100%; - } - - ul.categories { - padding-right: 25px; - } - - div.products { - width: 100%; - margin-left: 15px; - } - - article.product { - flex: 1 0 21%; - max-width: calc(25% - 6px); - height: 320px; - margin: 3px; - background-color: #fff; - padding: 6px; - } - - article.product div.actions .button { - display: none; - } - - article.product:hover div.actions .button { - display: block; - } -} - -article.product div.actions .button, -article.product div.actions .removeButton { - height: 38px; -} - div.main { - display: flex; - flex-direction: row; - width: 100%; -} - -div.leftPanel { - background-color: #fff; - border-radius: 5px; - min-height: 100vh; + display: flex; + flex-direction: column; + width: 100%; + flex: 1; } -div.products { - flex: 1; - height: fit-content; +div.main div.carrousel { + margin: 20px 0; } -div.productList { - flex: 1; - display: flex; - flex-wrap: wrap; +div.main div.featuredProducs { + width: 100%; + height: 340px; + margin: 20px 0; } -div.productList a { - color: #030303; - text-decoration: none; - display: flex; - flex-direction: column; - height: 100%; +div.main div.categories { + margin: 60px 0; } -div.productList a:hover { - text-decoration: none; +div.main div.featuredCategories { + margin: 20px 0; } -article.product { - border-radius: 5px; - display: flex; - flex-direction: column; - justify-content: space-between; -} +@media only screen and (width <=960px) { -article.product div.thumbnail { - height: 160px; - width: 100%; - padding: 5px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -article.product img.thumbnail { - max-width: 100%; - max-height: 100%; -} - -article.product div.actions { + div.main { margin-top: 5px; - height: 38px; -} + } -article.product div.actions button, -article.product div.actions .removeButton { - width: 100%; -} + div.main div.carrousel { + margin: 0; + } -article.product span.name { - overflow: hidden; - display: block; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - text-overflow: ellipsis; - white-space: normal; - text-align: center; - font-weight: 300; } -article.product span.price { - display: flex; - justify-content: center; - font-weight: bold; -} +@media only screen and (width >=960px) { -article.product .label { + div.main { + max-width: 1124px; margin-right: auto; margin-left: auto; -} - -ul.categories { - list-style: none; - width: 100%; - margin: 0; - padding: 0; - box-sizing: content-box; - padding-top: 10px; - height: 100%; -} - -ul.categories:hover { - box-sizing: border-box; - padding-right: 0; -} - -ul.categories li a { - text-decoration: none; - display: flex; - flex-direction: row; - align-items: center; - height: 48px; - padding-left: 24px; -} - -ul.categories li a:hover { - background-color: #f2f2f2; -} - -ul.categories li a.selected { - background-color: #e2e2e2; -} - -ul.categories li a.selected:hover { - background-color: #d2d2d2; -} - -ul.categories li a span, -ul.categories li a.selected span { - font-weight: 400; - color: #030303; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} - -ul.categories li a.selected span { - font-weight: 500; -} + padding-bottom: 30px; + margin-top: 15px; + } -ul.categories li a .categoryIcon { - color: #030303; - font-size: 24px; - margin-right: 24px; } - -.emptyList { - text-align: center; - width: 250px; - margin-left: calc(50% - 125px); -} - -.footer { - height: 54px; - padding: 5px 10px 5px 0; - margin: 3px 3px 0 3px; - background-color: #fff; - border-radius: 5px; - display: flex; - flex-direction: row; - justify-content: flex-end; -} - -.footer .pager { - display: flex; - flex-direction: row; - align-items: center; -} - -.footer .pager .rowCount { - font-size: 14px; - display: flex; - flex-direction: row; - align-items: center; - margin-right: 7px; -} - -.footer .pager .actions { - display: flex; - flex-direction: row; - align-items: center; -} - -.footer .pager .actions a { - text-decoration: none; - color: #030303; - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - padding: 5px; -} - -.footer .pager .actions a:hover { - color: #131313 !important; -} - -.footer .pager .actions { - font-size: 18px; -} - -a.disabled, -a.disabled:hover { - pointer-events: none; - cursor: default; - color: #939393 !important; -} \ No newline at end of file diff --git a/frontend/src/styles/notifications.module.css b/frontend/src/styles/notifications.module.css index fef9625..d4cd87a 100644 --- a/frontend/src/styles/notifications.module.css +++ b/frontend/src/styles/notifications.module.css @@ -8,14 +8,14 @@ div.notifications { align-items: center; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.notifications { top: 55px; } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.notifications { top: 65px; } diff --git a/frontend/src/styles/orders.module.css b/frontend/src/styles/orders.module.css index 6d85f56..7edfb14 100644 --- a/frontend/src/styles/orders.module.css +++ b/frontend/src/styles/orders.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.main { display: flex; flex-direction: column; @@ -23,7 +23,7 @@ } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.main { max-width: 1184px; diff --git a/frontend/src/styles/pager.module.css b/frontend/src/styles/pager.module.css index 16f1b3a..5913893 100644 --- a/frontend/src/styles/pager.module.css +++ b/frontend/src/styles/pager.module.css @@ -55,7 +55,7 @@ color: #939393 !important; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .pagerContainer { display: flex; flex-direction: row; @@ -63,7 +63,7 @@ } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .pagerContainer { display: flex; flex-direction: row; diff --git a/frontend/src/styles/product-list.module.css b/frontend/src/styles/product-list.module.css index 3f2bf79..ec2b652 100644 --- a/frontend/src/styles/product-list.module.css +++ b/frontend/src/styles/product-list.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { article.product { flex: 1 0 100%; max-width: calc(100% - 12px); @@ -14,7 +14,7 @@ } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { article.product { flex: 1 0 21%; diff --git a/frontend/src/styles/product.module.css b/frontend/src/styles/product.module.css index e05eac2..5414194 100644 --- a/frontend/src/styles/product.module.css +++ b/frontend/src/styles/product.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.main { width: calc(100% - 20px); margin-top: 35px; @@ -12,7 +12,7 @@ } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.main { width: 100%; max-width: 872px; diff --git a/frontend/src/styles/reset-password.module.css b/frontend/src/styles/reset-password.module.css index e5668fe..5781144 100644 --- a/frontend/src/styles/reset-password.module.css +++ b/frontend/src/styles/reset-password.module.css @@ -32,7 +32,7 @@ div.resend { /* Device width is less than or equal to 960px */ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .resetUserPasswordForm { margin-top: 40px; width: 330px; @@ -50,7 +50,7 @@ div.resend { /* Device width is greater than or equal to 960px */ -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .resetUserPasswordForm { margin-top: 40px; width: 450px; @@ -64,4 +64,4 @@ div.resend { height: 320px; padding: 30px; } -} \ No newline at end of file +} diff --git a/frontend/src/styles/search.module.css b/frontend/src/styles/search.module.css index 574ece1..24e84a1 100644 --- a/frontend/src/styles/search.module.css +++ b/frontend/src/styles/search.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { div.main { margin-top: 5px; } @@ -36,7 +36,7 @@ } } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { div.main { max-width: 1184px; diff --git a/frontend/src/styles/settings.module.css b/frontend/src/styles/settings.module.css index 1be821b..6140846 100644 --- a/frontend/src/styles/settings.module.css +++ b/frontend/src/styles/settings.module.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .form { margin: 45px 0 20px calc(50% - 175px); width: 350px; @@ -7,7 +7,7 @@ } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .form { margin: 45px 0 20px calc(50% - 275px); width: 550px; @@ -19,4 +19,4 @@ text-align: center; text-transform: none; color: #1d1d1b; -} \ No newline at end of file +} diff --git a/frontend/src/styles/signin.module.css b/frontend/src/styles/signin.module.css index ea4263f..1d93108 100644 --- a/frontend/src/styles/signin.module.css +++ b/frontend/src/styles/signin.module.css @@ -43,7 +43,7 @@ div.resetPassword { margin-top: 15px; } -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .signinForm { align-items: center; @@ -69,7 +69,7 @@ div.resetPassword { } -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .signinForm { align-items: center; diff --git a/frontend/src/styles/signup.module.css b/frontend/src/styles/signup.module.css index 48645fb..3b5c9ac 100644 --- a/frontend/src/styles/signup.module.css +++ b/frontend/src/styles/signup.module.css @@ -15,7 +15,7 @@ /* Device width is less than or equal to 960px */ -@media only screen and (max-width: 960px) { +@media only screen and (width <=960px) { .signupForm { margin: 45px 0 120px 0; width: 350px; @@ -25,7 +25,7 @@ /* Device width is greater than or equal to 960px */ -@media only screen and (min-width: 960px) { +@media only screen and (width >=960px) { .signupForm { margin: 85px 0 120px 0; width: 550px; diff --git a/packages/wexcommerce-types/index.d.ts b/packages/wexcommerce-types/index.d.ts index 793b862..6aaa1f7 100644 --- a/packages/wexcommerce-types/index.d.ts +++ b/packages/wexcommerce-types/index.d.ts @@ -305,3 +305,10 @@ export interface CartItem { product: Product; quantity: number; } +export interface GetProductPayload { + cart?: string; +} +export interface GetProductsPayload { + cart?: string; + size?: number; +} diff --git a/packages/wexcommerce-types/index.ts b/packages/wexcommerce-types/index.ts index 059c12c..e11144c 100644 --- a/packages/wexcommerce-types/index.ts +++ b/packages/wexcommerce-types/index.ts @@ -350,3 +350,17 @@ export interface CartItem { product: Product quantity: number } + +export interface GetProductPayload { + cart?: string +} + +export interface GetProductsPayload { + cart?: string + size?: number +} + +export interface FeaturedCategory { + category: CategoryInfo + products: Product[] +} diff --git a/packages/wexcommerce-types/tsconfig.tsbuildinfo b/packages/wexcommerce-types/tsconfig.tsbuildinfo index cbe174a..e4e8b45 100644 --- a/packages/wexcommerce-types/tsconfig.tsbuildinfo +++ b/packages/wexcommerce-types/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"program":{"fileNames":["../../backend/node_modules/typescript/lib/lib.es5.d.ts","../../backend/node_modules/typescript/lib/lib.es2015.d.ts","../../backend/node_modules/typescript/lib/lib.es2016.d.ts","../../backend/node_modules/typescript/lib/lib.es2017.d.ts","../../backend/node_modules/typescript/lib/lib.es2018.d.ts","../../backend/node_modules/typescript/lib/lib.es2019.d.ts","../../backend/node_modules/typescript/lib/lib.es2020.d.ts","../../backend/node_modules/typescript/lib/lib.es2021.d.ts","../../backend/node_modules/typescript/lib/lib.es2022.d.ts","../../backend/node_modules/typescript/lib/lib.es2023.d.ts","../../backend/node_modules/typescript/lib/lib.esnext.d.ts","../../backend/node_modules/typescript/lib/lib.es2015.core.d.ts","../../backend/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../backend/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../backend/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../backend/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../backend/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../backend/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../backend/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../backend/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../backend/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../backend/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../backend/node_modules/typescript/lib/lib.es2017.date.d.ts","../../backend/node_modules/typescript/lib/lib.es2017.object.d.ts","../../backend/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../backend/node_modules/typescript/lib/lib.es2017.string.d.ts","../../backend/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../backend/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../backend/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../backend/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../backend/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../backend/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../backend/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../backend/node_modules/typescript/lib/lib.es2019.array.d.ts","../../backend/node_modules/typescript/lib/lib.es2019.object.d.ts","../../backend/node_modules/typescript/lib/lib.es2019.string.d.ts","../../backend/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../backend/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../backend/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../backend/node_modules/typescript/lib/lib.es2020.date.d.ts","../../backend/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../backend/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../backend/node_modules/typescript/lib/lib.es2020.string.d.ts","../../backend/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../backend/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../backend/node_modules/typescript/lib/lib.es2020.number.d.ts","../../backend/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../backend/node_modules/typescript/lib/lib.es2021.string.d.ts","../../backend/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../backend/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../backend/node_modules/typescript/lib/lib.es2022.array.d.ts","../../backend/node_modules/typescript/lib/lib.es2022.error.d.ts","../../backend/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../backend/node_modules/typescript/lib/lib.es2022.object.d.ts","../../backend/node_modules/typescript/lib/lib.es2022.sharedmemory.d.ts","../../backend/node_modules/typescript/lib/lib.es2022.string.d.ts","../../backend/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../backend/node_modules/typescript/lib/lib.es2023.array.d.ts","../../backend/node_modules/typescript/lib/lib.es2023.collection.d.ts","../../backend/node_modules/typescript/lib/lib.esnext.collection.d.ts","../../backend/node_modules/typescript/lib/lib.esnext.intl.d.ts","../../backend/node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../backend/node_modules/typescript/lib/lib.esnext.promise.d.ts","../../backend/node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../backend/node_modules/typescript/lib/lib.esnext.object.d.ts","../../backend/node_modules/typescript/lib/lib.decorators.d.ts","../../backend/node_modules/typescript/lib/lib.decorators.legacy.d.ts","./index.ts"],"fileInfos":[{"version":"824cb491a40f7e8fdeb56f1df5edf91b23f3e3ee6b4cde84d4a99be32338faee","affectsGlobalScope":true},"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","9a68c0c07ae2fa71b44384a839b7b8d81662a236d4b9ac30916718f7510b1b2d","5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","5514e54f17d6d74ecefedc73c504eadffdeda79c7ea205cf9febead32d45c4bc","1c0cdb8dc619bc549c3e5020643e7cf7ae7940058e8c7e5aefa5871b6d86f44b","886e50ef125efb7878f744e86908884c0133e7a6d9d80013f421b0cd8fb2af94",{"version":"138fb588d26538783b78d1e3b2c2cc12d55840b97bf5e08bca7f7a174fbe2f17","affectsGlobalScope":true},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true},{"version":"4443e68b35f3332f753eacc66a04ac1d2053b8b035a0e0ac1d455392b5e243b3","affectsGlobalScope":true},{"version":"bc47685641087c015972a3f072480889f0d6c65515f12bd85222f49a98952ed7","affectsGlobalScope":true},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true},{"version":"93495ff27b8746f55d19fcbcdbaccc99fd95f19d057aed1bd2c0cafe1335fbf0","affectsGlobalScope":true},{"version":"6fc23bb8c3965964be8c597310a2878b53a0306edb71d4b5a4dfe760186bcc01","affectsGlobalScope":true},{"version":"ea011c76963fb15ef1cdd7ce6a6808b46322c527de2077b6cfdf23ae6f5f9ec7","affectsGlobalScope":true},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true},{"version":"bb42a7797d996412ecdc5b2787720de477103a0b2e53058569069a0e2bae6c7e","affectsGlobalScope":true},{"version":"4738f2420687fd85629c9efb470793bb753709c2379e5f85bc1815d875ceadcd","affectsGlobalScope":true},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true},{"version":"9fc46429fbe091ac5ad2608c657201eb68b6f1b8341bd6d670047d32ed0a88fa","affectsGlobalScope":true},{"version":"61c37c1de663cf4171e1192466e52c7a382afa58da01b1dc75058f032ddf0839","affectsGlobalScope":true},{"version":"b541a838a13f9234aba650a825393ffc2292dc0fc87681a5d81ef0c96d281e7a","affectsGlobalScope":true},{"version":"b20fe0eca9a4e405f1a5ae24a2b3290b37cf7f21eba6cbe4fc3fab979237d4f3","affectsGlobalScope":true},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true},{"version":"49ed889be54031e1044af0ad2c603d627b8bda8b50c1a68435fe85583901d072","affectsGlobalScope":true},{"version":"e93d098658ce4f0c8a0779e6cab91d0259efb88a318137f686ad76f8410ca270","affectsGlobalScope":true},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true},{"version":"bf14a426dbbf1022d11bd08d6b8e709a2e9d246f0c6c1032f3b2edb9a902adbe","affectsGlobalScope":true},{"version":"5e07ed3809d48205d5b985642a59f2eba47c402374a7cf8006b686f79efadcbd","affectsGlobalScope":true},{"version":"2b72d528b2e2fe3c57889ca7baef5e13a56c957b946906d03767c642f386bbc3","affectsGlobalScope":true},{"version":"8073890e29d2f46fdbc19b8d6d2eb9ea58db9a2052f8640af20baff9afbc8640","affectsGlobalScope":true},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true},{"version":"51e547984877a62227042850456de71a5c45e7fe86b7c975c6e68896c86fa23b","affectsGlobalScope":true},{"version":"956d27abdea9652e8368ce029bb1e0b9174e9678a273529f426df4b3d90abd60","affectsGlobalScope":true},{"version":"4fa6ed14e98aa80b91f61b9805c653ee82af3502dc21c9da5268d3857772ca05","affectsGlobalScope":true},{"version":"e6633e05da3ff36e6da2ec170d0d03ccf33de50ca4dc6f5aeecb572cedd162fb","affectsGlobalScope":true},{"version":"d8670852241d4c6e03f2b89d67497a4bbefe29ecaa5a444e2c11a9b05e6fccc6","affectsGlobalScope":true},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true},{"version":"caccc56c72713969e1cfe5c3d44e5bab151544d9d2b373d7dbe5a1e4166652be","affectsGlobalScope":true},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true},{"version":"50d53ccd31f6667aff66e3d62adf948879a3a16f05d89882d1188084ee415bbc","affectsGlobalScope":true},{"version":"08a58483392df5fcc1db57d782e87734f77ae9eab42516028acbfe46f29a3ef7","affectsGlobalScope":true},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true},{"version":"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9","affectsGlobalScope":true},{"version":"13f6e6380c78e15e140243dc4be2fa546c287c6d61f4729bc2dd7cf449605471","affectsGlobalScope":true},{"version":"15b98a533864d324e5f57cd3cfc0579b231df58c1c0f6063ea0fcb13c3c74ff9","affectsGlobalScope":true},{"version":"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6","affectsGlobalScope":true},{"version":"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f","affectsGlobalScope":true},{"version":"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279","affectsGlobalScope":true},{"version":"33358442698bb565130f52ba79bfd3d4d484ac85fe33f3cb1759c54d18201393","affectsGlobalScope":true},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true},{"version":"1b7a688f2173cf106ec3df96cf4c45a4cc2bc25856b9f025806dc17880aa245a","signature":"e32438e6c9ed2a987b7234b9f9561d9d00d3f196286a4f4e53a9405b805044a1"}],"root":[68],"options":{"allowJs":false,"alwaysStrict":true,"composite":true,"declaration":true,"esModuleInterop":true,"module":99,"noEmitOnError":true,"noImplicitAny":true,"noImplicitReturns":true,"noUnusedLocals":true,"outDir":"./","rootDir":"./","skipLibCheck":true,"strict":true,"target":99},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[66,67,13,12,2,14,15,16,17,18,19,20,21,3,22,4,23,27,24,25,26,28,29,30,5,31,32,33,34,6,38,35,36,37,39,7,40,45,46,41,42,43,44,8,50,47,48,49,51,9,52,53,54,57,55,56,58,59,10,1,60,11,64,62,61,65,63,68],"latestChangedDtsFile":"./index.d.ts"},"version":"5.4.5"} \ No newline at end of file +{"program":{"fileNames":["../../frontend/node_modules/typescript/lib/lib.es5.d.ts","../../frontend/node_modules/typescript/lib/lib.es2015.d.ts","../../frontend/node_modules/typescript/lib/lib.es2016.d.ts","../../frontend/node_modules/typescript/lib/lib.es2017.d.ts","../../frontend/node_modules/typescript/lib/lib.es2018.d.ts","../../frontend/node_modules/typescript/lib/lib.es2019.d.ts","../../frontend/node_modules/typescript/lib/lib.es2020.d.ts","../../frontend/node_modules/typescript/lib/lib.es2021.d.ts","../../frontend/node_modules/typescript/lib/lib.es2022.d.ts","../../frontend/node_modules/typescript/lib/lib.es2023.d.ts","../../frontend/node_modules/typescript/lib/lib.esnext.d.ts","../../frontend/node_modules/typescript/lib/lib.es2015.core.d.ts","../../frontend/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../frontend/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../frontend/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../frontend/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../frontend/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../frontend/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../frontend/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../frontend/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../frontend/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../frontend/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../frontend/node_modules/typescript/lib/lib.es2017.date.d.ts","../../frontend/node_modules/typescript/lib/lib.es2017.object.d.ts","../../frontend/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../frontend/node_modules/typescript/lib/lib.es2017.string.d.ts","../../frontend/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../frontend/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../frontend/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../frontend/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../frontend/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../frontend/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../frontend/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../frontend/node_modules/typescript/lib/lib.es2019.array.d.ts","../../frontend/node_modules/typescript/lib/lib.es2019.object.d.ts","../../frontend/node_modules/typescript/lib/lib.es2019.string.d.ts","../../frontend/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../frontend/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../frontend/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../frontend/node_modules/typescript/lib/lib.es2020.date.d.ts","../../frontend/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../frontend/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../frontend/node_modules/typescript/lib/lib.es2020.string.d.ts","../../frontend/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../frontend/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../frontend/node_modules/typescript/lib/lib.es2020.number.d.ts","../../frontend/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../frontend/node_modules/typescript/lib/lib.es2021.string.d.ts","../../frontend/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../frontend/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../frontend/node_modules/typescript/lib/lib.es2022.array.d.ts","../../frontend/node_modules/typescript/lib/lib.es2022.error.d.ts","../../frontend/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../frontend/node_modules/typescript/lib/lib.es2022.object.d.ts","../../frontend/node_modules/typescript/lib/lib.es2022.sharedmemory.d.ts","../../frontend/node_modules/typescript/lib/lib.es2022.string.d.ts","../../frontend/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../frontend/node_modules/typescript/lib/lib.es2023.array.d.ts","../../frontend/node_modules/typescript/lib/lib.es2023.collection.d.ts","../../frontend/node_modules/typescript/lib/lib.esnext.collection.d.ts","../../frontend/node_modules/typescript/lib/lib.esnext.intl.d.ts","../../frontend/node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../frontend/node_modules/typescript/lib/lib.esnext.promise.d.ts","../../frontend/node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../frontend/node_modules/typescript/lib/lib.esnext.object.d.ts","../../frontend/node_modules/typescript/lib/lib.decorators.d.ts","../../frontend/node_modules/typescript/lib/lib.decorators.legacy.d.ts","./index.ts"],"fileInfos":[{"version":"824cb491a40f7e8fdeb56f1df5edf91b23f3e3ee6b4cde84d4a99be32338faee","affectsGlobalScope":true},"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","9a68c0c07ae2fa71b44384a839b7b8d81662a236d4b9ac30916718f7510b1b2d","5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","5514e54f17d6d74ecefedc73c504eadffdeda79c7ea205cf9febead32d45c4bc","1c0cdb8dc619bc549c3e5020643e7cf7ae7940058e8c7e5aefa5871b6d86f44b","886e50ef125efb7878f744e86908884c0133e7a6d9d80013f421b0cd8fb2af94",{"version":"138fb588d26538783b78d1e3b2c2cc12d55840b97bf5e08bca7f7a174fbe2f17","affectsGlobalScope":true},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true},{"version":"4443e68b35f3332f753eacc66a04ac1d2053b8b035a0e0ac1d455392b5e243b3","affectsGlobalScope":true},{"version":"bc47685641087c015972a3f072480889f0d6c65515f12bd85222f49a98952ed7","affectsGlobalScope":true},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true},{"version":"93495ff27b8746f55d19fcbcdbaccc99fd95f19d057aed1bd2c0cafe1335fbf0","affectsGlobalScope":true},{"version":"6fc23bb8c3965964be8c597310a2878b53a0306edb71d4b5a4dfe760186bcc01","affectsGlobalScope":true},{"version":"ea011c76963fb15ef1cdd7ce6a6808b46322c527de2077b6cfdf23ae6f5f9ec7","affectsGlobalScope":true},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true},{"version":"bb42a7797d996412ecdc5b2787720de477103a0b2e53058569069a0e2bae6c7e","affectsGlobalScope":true},{"version":"4738f2420687fd85629c9efb470793bb753709c2379e5f85bc1815d875ceadcd","affectsGlobalScope":true},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true},{"version":"9fc46429fbe091ac5ad2608c657201eb68b6f1b8341bd6d670047d32ed0a88fa","affectsGlobalScope":true},{"version":"61c37c1de663cf4171e1192466e52c7a382afa58da01b1dc75058f032ddf0839","affectsGlobalScope":true},{"version":"b541a838a13f9234aba650a825393ffc2292dc0fc87681a5d81ef0c96d281e7a","affectsGlobalScope":true},{"version":"b20fe0eca9a4e405f1a5ae24a2b3290b37cf7f21eba6cbe4fc3fab979237d4f3","affectsGlobalScope":true},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true},{"version":"49ed889be54031e1044af0ad2c603d627b8bda8b50c1a68435fe85583901d072","affectsGlobalScope":true},{"version":"e93d098658ce4f0c8a0779e6cab91d0259efb88a318137f686ad76f8410ca270","affectsGlobalScope":true},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true},{"version":"bf14a426dbbf1022d11bd08d6b8e709a2e9d246f0c6c1032f3b2edb9a902adbe","affectsGlobalScope":true},{"version":"5e07ed3809d48205d5b985642a59f2eba47c402374a7cf8006b686f79efadcbd","affectsGlobalScope":true},{"version":"2b72d528b2e2fe3c57889ca7baef5e13a56c957b946906d03767c642f386bbc3","affectsGlobalScope":true},{"version":"8073890e29d2f46fdbc19b8d6d2eb9ea58db9a2052f8640af20baff9afbc8640","affectsGlobalScope":true},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true},{"version":"51e547984877a62227042850456de71a5c45e7fe86b7c975c6e68896c86fa23b","affectsGlobalScope":true},{"version":"956d27abdea9652e8368ce029bb1e0b9174e9678a273529f426df4b3d90abd60","affectsGlobalScope":true},{"version":"4fa6ed14e98aa80b91f61b9805c653ee82af3502dc21c9da5268d3857772ca05","affectsGlobalScope":true},{"version":"e6633e05da3ff36e6da2ec170d0d03ccf33de50ca4dc6f5aeecb572cedd162fb","affectsGlobalScope":true},{"version":"d8670852241d4c6e03f2b89d67497a4bbefe29ecaa5a444e2c11a9b05e6fccc6","affectsGlobalScope":true},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true},{"version":"caccc56c72713969e1cfe5c3d44e5bab151544d9d2b373d7dbe5a1e4166652be","affectsGlobalScope":true},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true},{"version":"50d53ccd31f6667aff66e3d62adf948879a3a16f05d89882d1188084ee415bbc","affectsGlobalScope":true},{"version":"08a58483392df5fcc1db57d782e87734f77ae9eab42516028acbfe46f29a3ef7","affectsGlobalScope":true},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true},{"version":"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9","affectsGlobalScope":true},{"version":"13f6e6380c78e15e140243dc4be2fa546c287c6d61f4729bc2dd7cf449605471","affectsGlobalScope":true},{"version":"15b98a533864d324e5f57cd3cfc0579b231df58c1c0f6063ea0fcb13c3c74ff9","affectsGlobalScope":true},{"version":"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6","affectsGlobalScope":true},{"version":"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f","affectsGlobalScope":true},{"version":"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279","affectsGlobalScope":true},{"version":"33358442698bb565130f52ba79bfd3d4d484ac85fe33f3cb1759c54d18201393","affectsGlobalScope":true},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true},{"version":"7701ce2cb67cc4870e14c4cb980d7ade5c26e68813a652545df61b0327238577","signature":"7a9f3cd9655c72ac97ed2cb6e40bef73db2f94bc83d4e9a699cb937d16c20f49"}],"root":[68],"options":{"allowJs":false,"alwaysStrict":true,"composite":true,"declaration":true,"esModuleInterop":true,"module":99,"noEmitOnError":true,"noImplicitAny":true,"noImplicitReturns":true,"noUnusedLocals":true,"outDir":"./","rootDir":"./","skipLibCheck":true,"strict":true,"target":99},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[66,67,13,12,2,14,15,16,17,18,19,20,21,3,22,4,23,27,24,25,26,28,29,30,5,31,32,33,34,6,38,35,36,37,39,7,40,45,46,41,42,43,44,8,50,47,48,49,51,9,52,53,54,57,55,56,58,59,10,1,60,11,64,62,61,65,63,68],"latestChangedDtsFile":"./index.d.ts"},"version":"5.4.5"} \ No newline at end of file