From 81357aa7f5d783e970817dc349def72aef7e9d4e Mon Sep 17 00:00:00 2001 From: Mohamad Date: Tue, 12 Nov 2024 05:07:04 +0000 Subject: [PATCH] chore: update dependencies and refactor layout components for improved styling and functionality --- package.json | 1 + pnpm-lock.yaml | 14 +- src/lib/context/theme-provider.tsx | 15 +- src/lib/data/collections.ts | 4 +- .../components/line-item-options/index.tsx | 10 +- .../components/line-item-price/index.tsx | 17 +- .../featured-products/product-rail/index.tsx | 1 - src/modules/home/components/hero/index.tsx | 33 +- .../layout/components/cart-dropdown/index.tsx | 134 +++++-- src/modules/layout/index.tsx | 2 +- .../layout/templates/footer/footer-2.tsx | 370 ++++++++++++++++++ .../layout/templates/header/header-1.tsx | 2 +- src/styles/base.css | 4 +- 13 files changed, 531 insertions(+), 76 deletions(-) create mode 100644 src/modules/layout/templates/footer/footer-2.tsx diff --git a/package.json b/package.json index 9bae740..3bb483b 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@radix-ui/react-collapsible": "^1.1.1", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-menubar": "^1.1.2", "@radix-ui/react-navigation-menu": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1edea6..f67c1b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: '@radix-ui/react-dropdown-menu': specifier: ^2.1.2 version: 2.1.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-icons': + specifier: ^1.3.1 + version: 1.3.1(react@19.0.0-rc-66855b96-20241106) '@radix-ui/react-label': specifier: ^2.1.0 version: 2.1.0(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) @@ -937,7 +940,7 @@ packages: '@radix-ui/react-dialog@1.1.2': resolution: {integrity: sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==} peerDependencies: - '@types/react': 17.0.40 + '@types/react': '*' '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1039,6 +1042,11 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-icons@1.3.1': + resolution: {integrity: sha512-QvYompk0X+8Yjlo/Fv4McrzxohDdM5GgLHyQcPpcsPvlOSXCGFjdbuyGL5dzRbg0GpknAjQJJZzdiRK7iWVuFQ==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x || ^19.x + '@radix-ui/react-id@1.1.0': resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} peerDependencies: @@ -5149,6 +5157,10 @@ snapshots: '@types/react': types-react@19.0.0-rc.1 '@types/react-dom': types-react-dom@19.0.0-rc.1 + '@radix-ui/react-icons@1.3.1(react@19.0.0-rc-66855b96-20241106)': + dependencies: + react: 19.0.0-rc-66855b96-20241106 + '@radix-ui/react-id@1.1.0(react@19.0.0-rc-66855b96-20241106)(types-react@19.0.0-rc.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(react@19.0.0-rc-66855b96-20241106)(types-react@19.0.0-rc.1) diff --git a/src/lib/context/theme-provider.tsx b/src/lib/context/theme-provider.tsx index eea06c5..64dbabb 100644 --- a/src/lib/context/theme-provider.tsx +++ b/src/lib/context/theme-provider.tsx @@ -1,11 +1,16 @@ "use client" import * as React from "react" -import { ThemeProvider as NextThemesProvider } from "next-themes" +import type { ThemeProviderProps } from "next-themes" +import dynamic from "next/dynamic" -export function ThemeProvider({ - children, - ...props -}: React.ComponentProps) { +const NextThemesProvider = dynamic( + () => import("next-themes").then((e) => e.ThemeProvider), + { + ssr: false, + } +) + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { return {children} } diff --git a/src/lib/data/collections.ts b/src/lib/data/collections.ts index 7e85998..e0094a0 100644 --- a/src/lib/data/collections.ts +++ b/src/lib/data/collections.ts @@ -28,7 +28,8 @@ export const getCollectionByHandle = cache(async function ( export const getCollectionsWithProducts = cache( async (countryCode: string): Promise => { - const { collections } = await getCollectionsList(0, 3) + // TODO: Make the limit and offset dynamic + const { collections } = await getCollectionsList(0, 10) if (!collections) { return null @@ -39,6 +40,7 @@ export const getCollectionsWithProducts = cache( .filter(Boolean) as string[] const { response } = await getProductsList({ + pageParam: 0, queryParams: { collection_id: collectionIds }, countryCode, }) diff --git a/src/modules/common/components/line-item-options/index.tsx b/src/modules/common/components/line-item-options/index.tsx index 69ad527..c79f1e3 100644 --- a/src/modules/common/components/line-item-options/index.tsx +++ b/src/modules/common/components/line-item-options/index.tsx @@ -1,24 +1,30 @@ import { HttpTypes } from "@medusajs/types" import { Text } from "@medusajs/ui" +import { cn } from "@/lib/utils/cn" type LineItemOptionsProps = { variant: HttpTypes.StoreProductVariant | undefined "data-testid"?: string "data-value"?: HttpTypes.StoreProductVariant + className?: string } const LineItemOptions = ({ variant, "data-testid": dataTestid, "data-value": dataValue, + className, }: LineItemOptionsProps) => { return ( - Variant: {variant?.title} + {variant?.title} ) } diff --git a/src/modules/common/components/line-item-price/index.tsx b/src/modules/common/components/line-item-price/index.tsx index 1025508..fec4fa0 100644 --- a/src/modules/common/components/line-item-price/index.tsx +++ b/src/modules/common/components/line-item-price/index.tsx @@ -7,9 +7,14 @@ import { convertToLocale } from "@/lib/utils/money" type LineItemPriceProps = { item: HttpTypes.StoreCartLineItem | HttpTypes.StoreOrderLineItem style?: "default" | "tight" + className?: string } -const LineItemPrice = ({ item, style = "default" }: LineItemPriceProps) => { +const LineItemPrice = ({ + item, + style = "default", + className, +}: LineItemPriceProps) => { const { currency_code, calculated_price_number, original_price_number } = getPricesForVariant(item.variant) ?? {} @@ -49,9 +54,13 @@ const LineItemPrice = ({ item, style = "default" }: LineItemPriceProps) => { )} {convertToLocale({ diff --git a/src/modules/home/components/featured-products/product-rail/index.tsx b/src/modules/home/components/featured-products/product-rail/index.tsx index d7eedd4..fe82b47 100644 --- a/src/modules/home/components/featured-products/product-rail/index.tsx +++ b/src/modules/home/components/featured-products/product-rail/index.tsx @@ -28,7 +28,6 @@ export default function ProductRail({ {products && products.map((product) => (
  • - {/* @ts-ignore */}
  • ))} diff --git a/src/modules/home/components/hero/index.tsx b/src/modules/home/components/hero/index.tsx index ab55c1f..48cf2f3 100644 --- a/src/modules/home/components/hero/index.tsx +++ b/src/modules/home/components/hero/index.tsx @@ -1,31 +1,32 @@ -import { Github } from "@medusajs/icons" import { Button, Heading } from "@medusajs/ui" +// import { GithubIcon } from "lucide-react" +import { GitHubLogoIcon } from "@radix-ui/react-icons" const Hero = () => { return ( -
    -
    +
    +
    + +

    + Storefront UI Template +

    +

    + by Mohmdev +

    +
    + - - Ecommerce Starter Template - - Powered by Medusa and Next.js + Powered by Next.js 15 and Medusa 2.0 - +
    diff --git a/src/modules/layout/components/cart-dropdown/index.tsx b/src/modules/layout/components/cart-dropdown/index.tsx index 7bd1096..d6529a3 100644 --- a/src/modules/layout/components/cart-dropdown/index.tsx +++ b/src/modules/layout/components/cart-dropdown/index.tsx @@ -1,16 +1,23 @@ "use client" import { Fragment, useEffect, useRef, useState } from "react" -import { Popover, Transition } from "@headlessui/react" +import { + Popover, + PopoverButton, + PopoverPanel, + Transition, +} from "@headlessui/react" import { HttpTypes } from "@medusajs/types" -import { Button } from "@medusajs/ui" +import { XIcon } from "lucide-react" import { usePathname } from "next/navigation" +import { cn } from "@/lib/utils/cn" import { convertToLocale } from "@/lib/utils/money" import DeleteButton from "@/modules/common/components/delete-button" import LineItemOptions from "@/modules/common/components/line-item-options" import LineItemPrice from "@/modules/common/components/line-item-price" import LocalizedClientLink from "@/modules/common/components/localized-client-link" import Thumbnail from "@/modules/products/components/thumbnail" +import { Button } from "@/ui/button" const CartDropdown = ({ cart: cartState, @@ -49,6 +56,13 @@ const CartDropdown = ({ open() } + const toggle = () => { + if (activeTimer) { + clearTimeout(activeTimer) + } + setCartDropdownOpen(!cartDropdownOpen) + } + // Clean up the timer when the component unmounts useEffect(() => { return () => { @@ -65,7 +79,6 @@ const CartDropdown = ({ if (itemRef.current !== totalItems && !pathname.includes("/cart")) { timedOpen() } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [totalItems, itemRef.current]) return ( @@ -75,13 +88,18 @@ const CartDropdown = ({ onMouseLeave={close} > - - +
    {`Cart (${totalItems})`} - + onClick={(e) => { + e.preventDefault() + toggle() + }} + > + {`Cart (${totalItems})`} +
    + - -
    -

    Cart

    +
    +

    + Your Shopping Cart +

    +
    {cartState && cartState.items?.length ? ( <> -
    +
    {cartState.items .sort((a, b) => { return (a.created_at ?? "") > (b.created_at ?? "") @@ -117,7 +156,7 @@ const CartDropdown = ({ >
    -
    -
    -
    -

    - - {item.title} - -

    - + {/* */} +
    + {/* Info */} +
    +

    + + {item.product_title} + +

    + - Quantity: {item.quantity} + {item.quantity} ×  -
    -
    - -
    + + +
    + {/* Price */} +
    +
    + {/* */} - Remove + {/* Remove */}
    ))}
    -
    +
    Subtotal{" "} @@ -184,8 +233,9 @@ const CartDropdown = ({
    )} - +
    diff --git a/src/modules/layout/index.tsx b/src/modules/layout/index.tsx index 3d606d8..e90d324 100644 --- a/src/modules/layout/index.tsx +++ b/src/modules/layout/index.tsx @@ -3,7 +3,7 @@ import type { StoreRegion } from "@medusajs/types" import { getCategoriesList } from "@/lib/data/categories" import { getCollectionsList } from "@/lib/data/collections" import { listRegions } from "@/lib/data/regions" -import Footer from "./templates/footer/footer-1" +import Footer from "./templates/footer/footer-2" import Header from "./templates/header/header-1" interface LayoutTemplateProps { diff --git a/src/modules/layout/templates/footer/footer-2.tsx b/src/modules/layout/templates/footer/footer-2.tsx new file mode 100644 index 0000000..ad0e3eb --- /dev/null +++ b/src/modules/layout/templates/footer/footer-2.tsx @@ -0,0 +1,370 @@ +import type { + StoreCollection, + StoreProductCategory, + StoreRegion, +} from "@medusajs/types" +import { cn } from "@/lib/utils/cn" +import LocalizedClientLink from "@/modules/common/components/localized-client-link" +import MedusaCTA from "@/modules/layout/components/medusa-cta" +import ModeToggleDropdown from "@/ui/mode-toggle/dropdown" + +const Footer = ({ + regions, + collections, + product_categories, + className, +}: { + collections: StoreCollection[] + product_categories: StoreProductCategory[] + regions: StoreRegion[] + className?: string +}) => { + const storeName = process.env.NEXT_PUBLIC_STORE_NAME || "Medusa Store" + return ( +
    +
    + {/* Top-level Grid */} +
    + + + +
    +
    + +
    + ) +} + +export default Footer + +const FooterLinks = ({ + collections, + product_categories, + className, +}: { + collections: StoreCollection[] + product_categories: StoreProductCategory[] + className?: string +}) => { + return ( + // Link groups grid +
    + + + + +
    + ) +} + +const FooterCategoriesLinks = ({ + product_categories, + className, +}: { + product_categories: StoreProductCategory[] + className?: string +}) => { + return ( + <> + {product_categories && product_categories?.length > 0 && ( +
    + Categories +
      + {product_categories?.slice(0, 6).map((c) => { + if (c.parent_category) { + return + } + const children = + c.category_children?.map((child) => ({ + name: child.name, + handle: child.handle, + id: child.id, + })) || null + return ( +
    • + + {c.name} + + {children && children.length > 0 && ( +
        6, + })} + > + {children && + children.map((child) => ( +
      • + + {child.name} + +
      • + ))} +
      + )} +
    • + ) + })} +
    +
    + )} + + ) +} + +const FooterCollectionsLinks = ({ + collections, + className, +}: { + collections: StoreCollection[] + className?: string +}) => { + return ( + <> + {collections && collections.length > 0 && ( +
    + Collections +
      6, + } + )} + > + {collections?.slice(0, 6).map((c) => ( +
    • + + {c.title} + +
    • + ))} +
    +
    + )} + + ) +} + +const FooterMedusaLinks = ({ + className, + displayHeader = true, + devices = "all", +}: { + className?: string + displayHeader?: boolean + devices?: "mobile" | "desktop" | "all" +}) => { + const deviceClasses = + devices === "mobile" + ? "flex md:hidden" + : devices === "desktop" + ? "md:flex hidden" + : "flex" + return ( +
    + {displayHeader && ( + Medusa + )} + +
    + ) +} + +const FooterQuickLinks = ({ + className, + displayHeader = true, + devices = "all", +}: { + className?: string + displayHeader?: boolean + devices?: "mobile" | "desktop" | "all" +}) => { + const deviceClasses = + devices === "mobile" + ? "flex md:hidden" + : devices === "desktop" + ? "md:flex hidden" + : "flex" + + return ( +
    + {displayHeader && ( + Links + )} +
      +
    • + + About + +
    • +
    • + + Contact + +
    • +
    • + + Privacy Policy + +
    • +
    • + + Terms of Service + +
    • +
    • + + Customer Service + +
    • +
    +
    + ) +} + +const FooterCTA = ({ + devices = "desktop", + storeName, +}: { + devices?: "mobile" | "desktop" + storeName: string +}) => { + return ( + <> + {devices === "desktop" && ( +
    +
    + + {storeName} + + {/* */} + +
    +
    + + +
    +
    + )} + {devices === "mobile" && ( +
    + + +
    + )} + + ) +} + +const FooterCopyright = ({ + storeName, + className, +}: { + storeName: string + className?: string +}) => { + return ( +
    +
    +

    + © {new Date().getFullYear()} {storeName}. All rights reserved. +

    +
    +
    + ) +} diff --git a/src/modules/layout/templates/header/header-1.tsx b/src/modules/layout/templates/header/header-1.tsx index a96b47e..86c90f3 100644 --- a/src/modules/layout/templates/header/header-1.tsx +++ b/src/modules/layout/templates/header/header-1.tsx @@ -19,7 +19,7 @@ const Header = ({ }) => { return (
    -
    +