Skip to content

Commit

Permalink
Merge pull request #173 from medusajs/add-pricing-module
Browse files Browse the repository at this point in the history
feat: pricing module + category improvements
  • Loading branch information
VariableVic authored Sep 19, 2023
2 parents 4945449 + 10df152 commit a218c5f
Show file tree
Hide file tree
Showing 25 changed files with 2,044 additions and 1,773 deletions.
2 changes: 1 addition & 1 deletion .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ NEXT_PUBLIC_MEDUSA_BACKEND_URL=http://localhost:9000
NEXT_PUBLIC_BASE_URL=http://localhost:8000

# Posgres URL for your Medusa DB for the Product Module. See - https://docs.medusajs.com/modules/products/serverless-module
PRODUCT_POSTGRES_URL=postgres://postgres:postgres@localhost:5432/medusa
POSTGRES_URL=postgres://postgres:postgres@localhost:5432/medusa

# Your Stripe public key. See – https://docs.medusajs.com/add-plugins/stripe
NEXT_PUBLIC_STRIPE_KEY=
Expand Down
5 changes: 4 additions & 1 deletion next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ const store = require("./store.config.json")
module.exports = withStoreConfig({
experimental: {
serverActions: true,
serverComponentsExternalPackages: ["@medusajs/product"],
serverComponentsExternalPackages: [
"@medusajs/product",
"@medusajs/modules-sdk",
],
},
features: store.features,
reactStrictMode: true,
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
"dependencies": {
"@headlessui/react": "^1.6.1",
"@hookform/error-message": "^2.0.0",
"@medusajs/link-modules": "^0.1.1",
"@medusajs/medusa-js": "^6.0.3",
"@medusajs/product": "^0.1.7",
"@medusajs/modules-sdk": "^1.11.0",
"@medusajs/pricing": "^0.0.3",
"@medusajs/product": "^0.2.0",
"@meilisearch/instant-meilisearch": "^0.7.1",
"@paypal/paypal-js": "^5.0.6",
"@paypal/react-paypal-js": "^7.8.1",
Expand All @@ -40,7 +43,8 @@
"react-hook-form": "^7.30.0",
"react-instantsearch-hooks-web": "^6.29.0",
"react-intersection-observer": "^9.3.4",
"sharp": "^0.30.7"
"sharp": "^0.30.7",
"webpack": "^5"
},
"devDependencies": {
"@babel/core": "^7.17.5",
Expand Down
21 changes: 21 additions & 0 deletions src/app/(checkout)/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Metadata } from "next"
import Link from "next/link"

export const metadata: Metadata = {
title: "404",
description: "Something went wrong",
}

export default function NotFound() {
return (
<div className="flex flex-col items-center justify-center min-h-[calc(100vh-64px)]">
<h1 className="text-2xl-semi text-gry-900">Page not found</h1>
<p className="text-small-regular text-gray-700">
The page you tried to access does not exist.
</p>
<Link href="/" className="mt-4 underline text-base-regular text-gray-900">
Go to frontpage
</Link>
</div>
)
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Metadata } from "next"
import { notFound } from "next/navigation"

type Props = {
params: { category: string }
params: { category: string[] }
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
Expand All @@ -29,7 +29,5 @@ export default async function CategoryPage({ params }: Props) {
notFound()
})

const category = product_categories[0]

return <CategoryTemplate category={category} />
return <CategoryTemplate categories={product_categories} />
}
38 changes: 0 additions & 38 deletions src/app/(main)/[category]/[subcategory]/page.tsx

This file was deleted.

5 changes: 0 additions & 5 deletions src/app/(main)/[category]/loading.tsx

This file was deleted.

21 changes: 21 additions & 0 deletions src/app/(main)/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Metadata } from "next"
import Link from "next/link"

export const metadata: Metadata = {
title: "404",
description: "Something went wrong",
}

export default function NotFound() {
return (
<div className="flex flex-col items-center justify-center min-h-[calc(100vh-64px)]">
<h1 className="text-2xl-semi text-gry-900">Page not found</h1>
<p className="text-small-regular text-gray-700">
The page you tried to access does not exist.
</p>
<Link href="/" className="mt-4 underline text-base-regular text-gray-900">
Go to frontpage
</Link>
</div>
)
}
177 changes: 177 additions & 0 deletions src/app/api/categories/[...handle]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { NextRequest, NextResponse } from "next/server"
import { initialize as initializeProductModule } from "@medusajs/product"
import { ProductDTO } from "@medusajs/types/dist/product"
import { IPricingModuleService } from "@medusajs/types"
import { notFound } from "next/navigation"
import { MedusaApp, Modules } from "@medusajs/modules-sdk"
import { getPricesByPriceSetId } from "@lib/util/get-prices-by-price-set-id"

/**
* This endpoint uses the serverless Product and Pricing Modules to retrieve a category and its products by handle.
* The module connects directly to you Medusa database to retrieve and manipulate data, without the need for a dedicated server.
* Read more about the Product Module here: https://docs.medusajs.com/modules/products/serverless-module
*/
export async function GET(
request: NextRequest,
{ params }: { params: Record<string, any> }
) {
// Initialize the Product Module
const productService = await initializeProductModule()

// Extract the query parameters
const searchParams = Object.fromEntries(request.nextUrl.searchParams)
const { page, limit } = searchParams

let { handle: categoryHandle } = params

const handle = categoryHandle.map((handle: string, index: number) =>
categoryHandle.slice(0, index + 1).join("/")
)

// Fetch the category by handle
const product_categories = await productService
.listCategories(
{
handle,
},
{
select: ["id", "handle", "name", "description"],
relations: ["category_children"],
take: handle.length,
}
)
.catch((e) => {
return notFound()
})

const category = product_categories[0]

if (!category) {
return notFound()
}

// Fetch the products by category id
const {
rows: products,
metadata: { count },
} = await getProductsByCategoryId(category.id, searchParams)

// Filter out unpublished products
const publishedProducts: ProductDTO[] = products.filter(
(product) => product.status === "published"
)

// Calculate the next page
const nextPage = parseInt(page) + parseInt(limit)

// Return the response
return NextResponse.json({
product_categories: Object.values(product_categories),
response: {
products: publishedProducts,
count,
},
nextPage: count > nextPage ? nextPage : null,
})
}

/**
* This function uses the serverless Product and Pricing Modules to retrieve products by category id.
* @param category_id The category id
* @param params The query parameters
* @returns The products and metadata
*/
async function getProductsByCategoryId(
category_id: string,
params: Record<string, any>
): Promise<{ rows: ProductDTO[]; metadata: Record<string, any> }> {
// Extract the query parameters
let { currency_code } = params

currency_code = currency_code && currency_code.toUpperCase()

// Initialize Remote Query with the Product and Pricing Modules
const { query, modules } = await MedusaApp({
modulesConfig: {
[Modules.PRODUCT]: true,
[Modules.PRICING]: true,
},
sharedResourcesConfig: {
database: { clientUrl: process.env.POSTGRES_URL },
},
})

// Set the filters for the query
const filters = {
take: parseInt(params.limit) || 100,
skip: parseInt(params.offset) || 0,
filters: {
category_id: [category_id],
},
currency_code,
}

// Set the GraphQL query
const productsQuery = `#graphql
query($filters: Record, $take: Int, $skip: Int) {
products(filters: $filters, take: $take, skip: $skip) {
id
title
handle
tags
status
collection
collection_id
thumbnail
images {
url
alt_text
id
}
options {
id
value
title
}
variants {
id
title
created_at
updated_at
thumbnail
inventory_quantity
material
weight
length
height
width
options {
id
value
title
}
price {
price_set {
id
}
}
}
}
}`

// Run the query
const { rows, metadata } = await query(productsQuery, filters)

// Calculate prices
const productsWithPrices = await getPricesByPriceSetId({
products: rows,
currency_code,
pricingService: modules.pricingService as unknown as IPricingModuleService,
})

// Return the response
return {
rows: productsWithPrices,
metadata,
}
}
Loading

0 comments on commit a218c5f

Please sign in to comment.