init starter package

This commit is contained in:
2025-10-16 17:29:57 +02:00
commit b7c67b5834
216 changed files with 37028 additions and 0 deletions
@@ -0,0 +1,114 @@
"use client"
import { clx } from "@medusajs/ui"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
export function Pagination({
page,
totalPages,
'data-testid': dataTestid
}: {
page: number
totalPages: number
'data-testid'?: string
}) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// Helper function to generate an array of numbers within a range
const arrayRange = (start: number, stop: number) =>
Array.from({ length: stop - start + 1 }, (_, index) => start + index)
// Function to handle page changes
const handlePageChange = (newPage: number) => {
const params = new URLSearchParams(searchParams)
params.set("page", newPage.toString())
router.push(`${pathname}?${params.toString()}`)
}
// Function to render a page button
const renderPageButton = (
p: number,
label: string | number,
isCurrent: boolean
) => (
<button
key={p}
className={clx("txt-xlarge-plus text-ui-fg-muted", {
"text-ui-fg-base hover:text-ui-fg-subtle": isCurrent,
})}
disabled={isCurrent}
onClick={() => handlePageChange(p)}
>
{label}
</button>
)
// Function to render ellipsis
const renderEllipsis = (key: string) => (
<span
key={key}
className="txt-xlarge-plus text-ui-fg-muted items-center cursor-default"
>
...
</span>
)
// Function to render page buttons based on the current page and total pages
const renderPageButtons = () => {
const buttons = []
if (totalPages <= 7) {
// Show all pages
buttons.push(
...arrayRange(1, totalPages).map((p) =>
renderPageButton(p, p, p === page)
)
)
} else {
// Handle different cases for displaying pages and ellipses
if (page <= 4) {
// Show 1, 2, 3, 4, 5, ..., lastpage
buttons.push(
...arrayRange(1, 5).map((p) => renderPageButton(p, p, p === page))
)
buttons.push(renderEllipsis("ellipsis1"))
buttons.push(
renderPageButton(totalPages, totalPages, totalPages === page)
)
} else if (page >= totalPages - 3) {
// Show 1, ..., lastpage - 4, lastpage - 3, lastpage - 2, lastpage - 1, lastpage
buttons.push(renderPageButton(1, 1, 1 === page))
buttons.push(renderEllipsis("ellipsis2"))
buttons.push(
...arrayRange(totalPages - 4, totalPages).map((p) =>
renderPageButton(p, p, p === page)
)
)
} else {
// Show 1, ..., page - 1, page, page + 1, ..., lastpage
buttons.push(renderPageButton(1, 1, 1 === page))
buttons.push(renderEllipsis("ellipsis3"))
buttons.push(
...arrayRange(page - 1, page + 1).map((p) =>
renderPageButton(p, p, p === page)
)
)
buttons.push(renderEllipsis("ellipsis4"))
buttons.push(
renderPageButton(totalPages, totalPages, totalPages === page)
)
}
}
return buttons
}
// Render the component
return (
<div className="flex justify-center w-full mt-12">
<div className="flex gap-3 items-end" data-testid={dataTestid}>{renderPageButtons()}</div>
</div>
)
}
@@ -0,0 +1,41 @@
"use client"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useCallback } from "react"
import SortProducts, { SortOptions } from "./sort-products"
type RefinementListProps = {
sortBy: SortOptions
search?: boolean
'data-testid'?: string
}
const RefinementList = ({ sortBy, 'data-testid': dataTestId }: RefinementListProps) => {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const createQueryString = useCallback(
(name: string, value: string) => {
const params = new URLSearchParams(searchParams)
params.set(name, value)
return params.toString()
},
[searchParams]
)
const setQueryParams = (name: string, value: string) => {
const query = createQueryString(name, value)
router.push(`${pathname}?${query}`)
}
return (
<div className="flex small:flex-col gap-12 py-4 mb-8 small:px-0 pl-6 small:min-w-[250px] small:ml-[1.675rem]">
<SortProducts sortBy={sortBy} setQueryParams={setQueryParams} data-testid={dataTestId} />
</div>
)
}
export default RefinementList
@@ -0,0 +1,48 @@
"use client"
import FilterRadioGroup from "@modules/common/components/filter-radio-group"
export type SortOptions = "price_asc" | "price_desc" | "created_at"
type SortProductsProps = {
sortBy: SortOptions
setQueryParams: (name: string, value: SortOptions) => void
"data-testid"?: string
}
const sortOptions = [
{
value: "created_at",
label: "Latest Arrivals",
},
{
value: "price_asc",
label: "Price: Low -> High",
},
{
value: "price_desc",
label: "Price: High -> Low",
},
]
const SortProducts = ({
"data-testid": dataTestId,
sortBy,
setQueryParams,
}: SortProductsProps) => {
const handleChange = (value: SortOptions) => {
setQueryParams("sortBy", value)
}
return (
<FilterRadioGroup
title="Sort by"
items={sortOptions}
value={sortBy}
handleChange={handleChange}
data-testid={dataTestId}
/>
)
}
export default SortProducts
+43
View File
@@ -0,0 +1,43 @@
import { Suspense } from "react"
import SkeletonProductGrid from "@modules/skeletons/templates/skeleton-product-grid"
import RefinementList from "@modules/store/components/refinement-list"
import { SortOptions } from "@modules/store/components/refinement-list/sort-products"
import PaginatedProducts from "./paginated-products"
const StoreTemplate = ({
sortBy,
page,
countryCode,
}: {
sortBy?: SortOptions
page?: string
countryCode: string
}) => {
const pageNumber = page ? parseInt(page) : 1
const sort = sortBy || "created_at"
return (
<div
className="flex flex-col small:flex-row small:items-start py-6 content-container"
data-testid="category-container"
>
<RefinementList sortBy={sort} />
<div className="w-full">
<div className="mb-8 text-2xl-semi">
<h1 data-testid="store-page-title">All products</h1>
</div>
<Suspense fallback={<SkeletonProductGrid />}>
<PaginatedProducts
sortBy={sort}
page={pageNumber}
countryCode={countryCode}
/>
</Suspense>
</div>
</div>
)
}
export default StoreTemplate
@@ -0,0 +1,92 @@
import { listProductsWithSort } from "@lib/data/products"
import { getRegion } from "@lib/data/regions"
import ProductPreview from "@modules/products/components/product-preview"
import { Pagination } from "@modules/store/components/pagination"
import { SortOptions } from "@modules/store/components/refinement-list/sort-products"
const PRODUCT_LIMIT = 12
type PaginatedProductsParams = {
limit: number
collection_id?: string[]
category_id?: string[]
id?: string[]
order?: string
}
export default async function PaginatedProducts({
sortBy,
page,
collectionId,
categoryId,
productsIds,
countryCode,
}: {
sortBy?: SortOptions
page: number
collectionId?: string
categoryId?: string
productsIds?: string[]
countryCode: string
}) {
const queryParams: PaginatedProductsParams = {
limit: 12,
}
if (collectionId) {
queryParams["collection_id"] = [collectionId]
}
if (categoryId) {
queryParams["category_id"] = [categoryId]
}
if (productsIds) {
queryParams["id"] = productsIds
}
if (sortBy === "created_at") {
queryParams["order"] = "created_at"
}
const region = await getRegion(countryCode)
if (!region) {
return null
}
let {
response: { products, count },
} = await listProductsWithSort({
page,
queryParams,
sortBy,
countryCode,
})
const totalPages = Math.ceil(count / PRODUCT_LIMIT)
return (
<>
<ul
className="grid grid-cols-2 w-full small:grid-cols-3 medium:grid-cols-4 gap-x-6 gap-y-8"
data-testid="products-list"
>
{products.map((p) => {
return (
<li key={p.id}>
<ProductPreview product={p} region={region} />
</li>
)
})}
</ul>
{totalPages > 1 && (
<Pagination
data-testid="product-pagination"
page={page}
totalPages={totalPages}
/>
)}
</>
)
}