docs: added customer storefront guides (#7685)

* added customer guides

* fixes to sidebar

* remove old customer registration guide

* fix build error

* generate files

* run linter
This commit is contained in:
Shahed Nasser authored and GitHub committed 2024-06-13 12:21:54 +03:00
1 parent d862d03de0
commit c1db40b564
80 files changed
+2301 -701

No files matched your search

@@ -35,7 +35,7 @@ import {
createContext,
useContext,
useEffect,
useState
useState,
} from "react"
import { HttpTypes } from "@medusajs/types"
import { useRegion } from "./region"
@@ -73,11 +73,11 @@ export const CartProvider = ({ children }: CartProviderProps) => {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
region_id: region.id,
})
}),
})
.then((res) => res.json())
.then(({ cart: dataCart }) => {
@@ -87,7 +87,7 @@ export const CartProvider = ({ children }: CartProviderProps) => {
} else {
// retrieve cart
fetch(`http://localhost:9000/store/carts/${cartId}`, {
credentials: "include"
credentials: "include",
})
.then((res) => res.json())
.then(({ cart: dataCart }) => {
@@ -105,7 +105,7 @@ export const CartProvider = ({ children }: CartProviderProps) => {
<CartContext.Provider value={{
cart,
setCart,
refreshCart
refreshCart,
}}>
{children}
</CartContext.Provider>
@@ -147,7 +147,7 @@ const inter = Inter({ subsets: ["latin"] })
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
}
export default function RootLayout({
children,
@@ -31,11 +31,11 @@ export const fetchHighlights = [
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
region_id: region.id,
})
}),
})
.then((res) => res.json())
.then(({ cart }) => {
@@ -58,9 +58,9 @@ export const highlights = [
```tsx highlights={highlights}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react";
import { useEffect, useState } from "react"
// other imports...
export default function Home() {
// TODO assuming you have the region retrieved
const region = {
@@ -82,11 +82,11 @@ export const highlights = [
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
region_id: region.id,
})
}),
})
.then((res) => res.json())
.then(({ cart }) => {
@@ -130,8 +130,8 @@ fetch(`http://localhost:9000/store/carts`, {
// ...
body: JSON.stringify({
// ...
customer_id: customer.id
})
customer_id: customer.id,
}),
})
.then((res) => res.json())
.then(({ cart }) => {
@@ -34,12 +34,12 @@ const addToCart = (variant_id: string) => {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
variant_id,
quantity: 1,
})
}),
})
.then((res) => res.json())
.then(({ cart }) => {
@@ -89,11 +89,11 @@ const updateQuantity = (
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
quantity
})
quantity,
}),
})
.then((res) => res.json())
.then(({ cart }) => {
@@ -21,7 +21,7 @@ export const fetchHighlights = [
```ts highlights={fetchHighlights}
fetch(`http://localhost:9000/store/carts/${cartId}`, {
credentials: "include"
credentials: "include",
})
.then((res) => res.json())
.then(({ cart }) => {
@@ -47,7 +47,7 @@ export const highlights = [
import { useEffect, useState } from "react"
import { HttpTypes } from "@medusajs/types"
export default function Cart () {
export default function Cart() {
const [cart, setCart] = useState<
HttpTypes.StoreCart
>()
@@ -64,7 +64,7 @@ export const highlights = [
}
fetch(`http://localhost:9000/store/carts/${cartId}`, {
credentials: "include"
credentials: "include",
})
.then((res) => res.json())
.then(({ cart: dataCart }) => {
@@ -29,11 +29,11 @@ fetch(`http://localhost:9000/store/carts/${cartId}`, {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
region_id: "new_id"
})
region_id: "new_id",
}),
})
.then((res) => res.json())
.then(({ cart }) => {
@@ -61,11 +61,11 @@ fetch(`http://localhost:9000/store/carts/${cartId}`, {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
customer_id: "logged_in_id"
})
customer_id: "logged_in_id",
}),
})
.then((res) => res.json())
.then(({ cart }) => {
@@ -29,19 +29,19 @@ For example:
city,
country_code,
province,
phone
phone,
}
fetch(`http://localhost:9000/store/carts/${cartId}`, {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
shipping_address: address,
billing_address: address
})
billing_address: address,
}),
})
.then((res) => res.json())
.then(({ cart }) => {
@@ -64,10 +64,10 @@ export const highlights = [
```tsx highlights={highlights}
"use client" // include with Next.js 13+
import { useState } from "react";
import { useCart } from "../../../providers/cart";
export default function CheckoutAddressStep () {
import { useState } from "react"
import { useCart } from "../../../providers/cart"
export default function CheckoutAddressStep() {
const { cart, setCart } = useCart()
const [loading, setLoading] = useState(false)
const [firstName, setFirstName] = useState("")
@@ -99,19 +99,19 @@ export const highlights = [
city,
country_code: countryCode,
province,
phone: phoneNumber
phone: phoneNumber,
}
fetch(`http://localhost:9000/store/carts/${cart.id}`, {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
shipping_address: address,
billing_address: address
})
billing_address: address,
}),
})
.then((res) => res.json())
.then(({ cart: updatedCart }) => {
@@ -17,7 +17,7 @@ fetch(
`http://localhost:9000/store/carts/${cartId}/complete`,
{
credentials: "include",
method: "POST"
method: "POST",
}
)
.then((res) => res.json())
@@ -65,7 +65,7 @@ export const highlights = [
import { useState } from "react"
import { useCart } from "../../providers/cart"
export default function SystemDefaultPayment () {
export default function SystemDefaultPayment() {
const { cart, refreshCart } = useCart()
const [loading, setLoading] = useState(false)
@@ -87,7 +87,7 @@ export default function SystemDefaultPayment () {
`http://localhost:9000/store/carts/${cart.id}/complete`,
{
credentials: "include",
method: "POST"
method: "POST",
}
)
.then((res) => res.json())
@@ -26,11 +26,11 @@ For example:
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
email
})
email,
}),
})
.then((res) => res.json())
.then(({ cart }) => {
@@ -55,7 +55,7 @@ export const highlights = [
import { useState } from "react"
import { useCart } from "../../../providers/cart"
export default function CheckoutEmailStep () {
export default function CheckoutEmailStep() {
const { cart, setCart } = useCart()
const [email, setEmail] = useState("")
const [loading, setLoading] = useState(false)
@@ -80,11 +80,11 @@ export const highlights = [
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
email
})
email,
}),
})
.then((res) => res.json())
.then(({ cart: updatedCart }) => {
@@ -57,7 +57,7 @@ export const fetchHighlights = [
`http://localhost:9000/store/payment-providers?region_id=${
cart.region_id
}`, {
credentials: "include"
credentials: "include",
})
.then((res) => res.json())
@@ -77,14 +77,14 @@ export const fetchHighlights = [
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
cart_id: cart.id,
region_id: cart.region_id,
currency_code: cart.currency_code,
amount: cart.total
})
amount: cart.total,
}),
}
)
.then((res) => res.json())
@@ -100,21 +100,21 @@ export const fetchHighlights = [
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
provider_id: selectedPaymentProviderId
})
provider_id: selectedPaymentProviderId,
}),
})
.then((res) => res.json())
// re-fetch cart
const {
cart: updatedCart
cart: updatedCart,
} = await fetch(
`http://localhost:9000/store/carts/${cart.id}`,
{
credentials: "include"
credentials: "include",
}
)
.then((res) => res.json())
@@ -171,14 +171,14 @@ export const highlights = [
import { useCart } from "../../../providers/cart"
import { HttpTypes } from "@medusajs/types"
export default function CheckoutPaymentStep () {
export default function CheckoutPaymentStep() {
const { cart, setCart } = useCart()
const [paymentProviders, setPaymentProviders] = useState<
HttpTypes.StorePaymentProvider[]
>([])
const [
selectedPaymentProvider,
setSelectedPaymentProvider
setSelectedPaymentProvider,
] = useState<string | undefined>()
const [loading, setLoading] = useState(false)
@@ -190,7 +190,7 @@ export const highlights = [
fetch(`http://localhost:9000/store/payment-providers?region_id=${
cart.region_id
}`, {
credentials: "include"
credentials: "include",
})
.then((res) => res.json())
.then(({ payment_providers }) => {
@@ -221,14 +221,14 @@ export const highlights = [
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
cart_id: cart.id,
region_id: cart.region_id,
currency_code: cart.currency_code,
amount: cart.total
})
amount: cart.total,
}),
}
)
.then((res) => res.json())
@@ -244,21 +244,21 @@ export const highlights = [
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
provider_id: selectedPaymentProvider
})
provider_id: selectedPaymentProvider,
}),
})
.then((res) => res.json())
// re-fetch cart
const {
cart: updatedCart
cart: updatedCart,
} = await fetch(
`http://localhost:9000/store/carts/${cart.id}`,
{
credentials: "include"
credentials: "include",
}
)
.then((res) => res.json())
@@ -75,7 +75,7 @@ import {
CardElement,
Elements,
useElements,
useStripe
useStripe,
} from "@stripe/react-stripe-js"
import { loadStripe } from "@stripe/stripe-js"
import { useCart } from "../../providers/cart"
@@ -102,7 +102,7 @@ export default function StripePayment() {
}
const StripeForm = ({
clientSecret
clientSecret,
}: {
clientSecret: string | undefined
}) => {
@@ -157,7 +157,7 @@ const StripeForm = ({
`http://localhost:9000/store/carts/${cart.id}/complete`,
{
credentials: "include",
method: "POST"
method: "POST",
}
)
.then((res) => res.json())
@@ -32,7 +32,7 @@ export const fetchHighlights = [
`http://localhost:9000/store/shipping-options?cart_id=${
cart.id
}`, {
credentials: "include"
credentials: "include",
}
)
.then((res) => res.json())
@@ -49,15 +49,15 @@ export const fetchHighlights = [
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
option_id: selectedShippingOptionId,
data: {
// TODO add any data necessary for
// fulfillment provider
}
})
},
}),
})
.then((res) => res.json())
.then(({ cart }) => {
@@ -84,7 +84,7 @@ export const highlights = [
import { useCart } from "../../../providers/cart"
import { HttpTypes } from "@medusajs/types"
export default function CheckoutShippingStep () {
export default function CheckoutShippingStep() {
const { cart, setCart } = useCart()
const [loading, setLoading] = useState(false)
const [shippingOptions, setShippingOptions] = useState<
@@ -92,7 +92,7 @@ export const highlights = [
>([])
const [
selectedShippingOption,
setSelectedShippingOption
setSelectedShippingOption,
] = useState<string | undefined>()
useEffect(() => {
@@ -102,7 +102,7 @@ export const highlights = [
fetch(`http://localhost:9000/store/shipping-options?cart_id=${
cart.id
}`, {
credentials: "include"
credentials: "include",
})
.then((res) => res.json())
.then(({ shipping_options }) => {
@@ -126,15 +126,15 @@ export const highlights = [
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
option_id: selectedShippingOption,
data: {
// TODO add any data necessary for
// fulfillment provider
}
})
},
}),
})
.then((res) => res.json())
.then(({ cart: updatedCart }) => {
@@ -0,0 +1,570 @@
import { CodeTabs, CodeTab } from "docs-ui"
export const metadata = {
title: `Manage Customer Addresses in Storefront`,
}
# {metadata.title}
In this document, you'll learn how to manage a customer's addresses in a storefront.
## List Customer Addresses
To retrieve the list of customer addresses, send a request to the [List Customer Addresses API route](!api!/store#customers_getcustomersmeaddressesaddress_id):
<CodeTabs group="store-request">
<CodeTab label="Fetch API" value="fetch">
export const fetchHighlights = [
["2", "limit", "The number of addresses to retrieve"],
["3", "offset", "The number of addresses to skip before those retrieved."],
]
```ts highlights={fetchHighlights}
const searchParams = new URLSearchParams({
limit: `${limit}`,
offset: `${offset}`,
})
fetch(`http://localhost:9000/store/customers/me/addresses?${
searchParams.toString()
}`, {
credentials: "include",
})
.then((res) => res.json())
.then(({ addresses, count }) => {
// use addresses...
console.log(addresses, count)
})
```
</CodeTab>
<CodeTab label="React" value="react">
export const highlights = [
["20", "offset", "Calculate the number of addresses to skip based on the current page and limit."],
["27", "fetch", "Send a request to retrieve the addresses."],
["28", "searchParams.toString()", "Pass the pagination parameters in the query."],
["33", "count", "The total number of addresses in the Medusa application."],
["45", "setHasMorePages", "Set whether there are more pages based on the total count."],
["59", "", "Using only two address fields for simplicity."],
["65", "button", "Show a button to load more addresses if there are more pages."]
]
```tsx highlights={highlights} collapsibleLines="50-77" expandButtonLabel="Show render"
"use client" // include with Next.js 13+
import { HttpTypes } from "@medusajs/types"
import { useEffect, useState } from "react"
export default function Addresses() {
const [addresses, setAddresses] = useState<
HttpTypes.StoreCustomerAddress[]
>([])
const [loading, setLoading] = useState(true)
const limit = 20
const [currentPage, setCurrentPage] = useState(1)
const [hasMorePages, setHasMorePages] = useState(false)
useEffect(() => {
if (!loading) {
return
}
const offset = (currentPage - 1) * limit
const searchParams = new URLSearchParams({
limit: `${limit}`,
offset: `${offset}`,
})
fetch(`http://localhost:9000/store/customers/me/addresses?${
searchParams.toString()
}`, {
credentials: "include",
})
.then((res) => res.json())
.then(({ addresses: addressesData, count }) => {
setAddresses((prev) => {
if (prev.length > offset) {
// addresses already added because
// the same request has already been sent
return prev
}
return [
...prev,
...addressesData,
]
})
setHasMorePages(count > limit * currentPage)
})
.finally(() => setLoading(false))
}, [loading])
return (
<div>
{loading && <span>Loading...</span>}
{!loading && !addresses.length && (
<span>You have no addresses</span>
)}
<ul>
{addresses.map((address) => (
<li key={address.id}>
City: {address.city} -
Country: {address.country_code}
</li>
))}
</ul>
{!loading && hasMorePages && (
<button
onClick={() => {
setCurrentPage((prev) => prev + 1)
setLoading(true)
}}
disabled={loading}
>
Load More
</button>
)}
</div>
)
}
```
</CodeTab>
</CodeTabs>
The List Customer Addresses API route accepts pagination parameters to paginate the address.
{/* TODO add a link to the address object */}
It returns in the response the `addresses` field, which is an array of addresses.
---
## Add Customer Address
To add a new address for the customer, send a request to the [Add Customer Address API route](!api!/store#customers_postcustomersmeaddresses):
<CodeTabs group="store-request">
<CodeTab label="Fetch API" value="fetch">
```ts
fetch(`http://localhost:9000/store/customers/me/addresses`, {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
first_name: firstName,
last_name: lastName,
address_1: address1,
company,
postal_code: postalCode,
city,
country_code: countryCode,
province,
phone: phoneNumber,
}),
})
.then((res) => res.json())
.then(({ customer }) => {
// use customer
console.log(customer)
})
```
</CodeTab>
<CodeTab label="React" value="react">
export const addHighlights = [
["4", "useRegion", "Use the hook defined in the Region Context guide."],
["5", "useCustomer", "Use the hook defined in the Customer Context guide."],
["28"], ["29"], ["30"], ["31"], ["32"], ["33"], ["34"], ["35"], ["36"], ["37"],
["38"], ["39"], ["40"], ["41"], ["42"], ["43"], ["44"], ["45"], ["46"], ["47"],
["48"], ["49"]
]
```tsx highlights={addHighlights} collapsibleLines="53-124" expandButtonLabel="Show form"
"use client" // include with Next.js 13+
import { useState } from "react"
import { useRegion } from "../../../../providers/region"
import { useCustomer } from "../../../../providers/customer"
export default function AddAddress() {
const { region } = useRegion()
const { setCustomer } = useCustomer()
const [loading, setLoading] = useState(false)
const [firstName, setFirstName] = useState("")
const [lastName, setLastName] = useState("")
const [address1, setAddress1] = useState("")
const [company, setCompany] = useState("")
const [postalCode, setPostalCode] = useState("")
const [city, setCity] = useState("")
const [countryCode, setCountryCode] = useState("")
const [province, setProvince] = useState("")
const [phoneNumber, setPhoneNumber] = useState("")
const handleAdd = (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
e.preventDefault()
setLoading(false)
fetch(`http://localhost:9000/store/customers/me/addresses`, {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
first_name: firstName,
last_name: lastName,
address_1: address1,
company,
postal_code: postalCode,
city,
country_code: countryCode,
province,
phone: phoneNumber,
}),
})
.then((res) => res.json())
.then(({ customer }) => {
setCustomer(customer)
})
.finally(() => setLoading(false))
}
return (
<form>
<input
type="text"
placeholder="First Name"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
<input
type="text"
placeholder="Last Name"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
<input
type="text"
placeholder="Address Line"
value={address1}
onChange={(e) => setAddress1(e.target.value)}
/>
<input
type="text"
placeholder="Company"
value={company}
onChange={(e) => setCompany(e.target.value)}
/>
<input
type="text"
placeholder="Postal Code"
value={postalCode}
onChange={(e) => setPostalCode(e.target.value)}
/>
<input
type="text"
placeholder="City"
value={city}
onChange={(e) => setCity(e.target.value)}
/>
<select
value={countryCode}
onChange={(e) => setCountryCode(e.target.value)}
>
{region?.countries?.map((country) => (
<option
key={country.iso_2}
value={country.iso_2}
>
{country.display_name}
</option>
))}
</select>
<input
type="text"
placeholder="Province"
value={province}
onChange={(e) => setProvince(e.target.value)}
/>
<input
type="tel"
placeholder="Phone Number"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
/>
<button
disabled={loading}
onClick={handleAdd}
>
Add
</button>
</form>
)
}
```
</CodeTab>
</CodeTabs>
The Add Address API route returns in the response a `customer` field, which is a [customer object](!api!/store#customers_customer_schema).
---
## Edit an Address
To edit an address, send a request to the [Update Customer Address API route](!api!/store#customers_postcustomersmeaddressesaddress_id):
<CodeTabs group="store-request">
<CodeTab label="Fetch API" value="fetch">
```ts
fetch(
`http://localhost:9000/store/customers/me/addresses/${
address.id
}`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
first_name: firstName,
last_name: lastName,
address_1: address1,
company,
postal_code: postalCode,
city,
country_code: countryCode,
province,
phone: phoneNumber,
}),
}
)
.then((res) => res.json())
.then(({ customer }) => {
// use customer...
console.log(customer)
})
```
</CodeTab>
<CodeTab label="React" value="react">
export const editHighlights = [
["4", "useRegion", "Use the hook defined in the Region Context guide."],
["5", "useCustomer", "Use the hook defined in the Customer Context guide."],
["14", "{ params: { id }}", "This is based on Next.js which passes the path parameters as a prop."],
["19", "address", "Retrieve the address from the customer's `addresses` property."],
["60"], ["61"], ["62"], ["63"], ["64"], ["65"], ["66"], ["67"], ["68"], ["69"],
["70"], ["71"], ["72"], ["73"], ["74"], ["75"], ["76"], ["77"], ["78"], ["79"],
["80"], ["81"]
]
```tsx highlights={editHighlights} collapsibleLines="90-161" expandButtonLabel="Show form"
"use client" // include with Next.js 13+
import { useState } from "react"
import { useRegion } from "../../../../../providers/region"
import { useCustomer } from "../../../../../providers/customer"
type Params = {
params: {
id: string
}
}
export default function EditAddress(
{ params: { id } }: Params
) {
const { customer, setCustomer } = useCustomer()
const { region } = useRegion()
const address = customer?.addresses.find(
(address) => address.id === id
)
const [loading, setLoading] = useState(false)
const [firstName, setFirstName] = useState(
address?.first_name || ""
)
const [lastName, setLastName] = useState(
address?.last_name || ""
)
const [address1, setAddress1] = useState(
address?.address_1 || ""
)
const [company, setCompany] = useState(
address?.company || ""
)
const [postalCode, setPostalCode] = useState(
address?.postal_code || ""
)
const [city, setCity] = useState(
address?.city || ""
)
const [countryCode, setCountryCode] = useState(
address?.country_code || ""
)
const [province, setProvince] = useState(
address?.province || ""
)
const [phoneNumber, setPhoneNumber] = useState(
address?.phone || ""
)
const handleEdit = (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
e.preventDefault()
if (!customer || !address) {
return
}
setLoading(true)
fetch(
`http://localhost:9000/store/customers/me/addresses/${
address.id
}`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
first_name: firstName,
last_name: lastName,
address_1: address1,
company,
postal_code: postalCode,
city,
country_code: countryCode,
province,
phone: phoneNumber,
}),
}
)
.then((res) => res.json())
.then(({ customer }) => {
setCustomer(customer)
})
.finally(() => setLoading(false))
}
return (
<form>
<input
type="text"
placeholder="First Name"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
<input
type="text"
placeholder="Last Name"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
<input
type="text"
placeholder="Address Line"
value={address1}
onChange={(e) => setAddress1(e.target.value)}
/>
<input
type="text"
placeholder="Company"
value={company}
onChange={(e) => setCompany(e.target.value)}
/>
<input
type="text"
placeholder="Postal Code"
value={postalCode}
onChange={(e) => setPostalCode(e.target.value)}
/>
<input
type="text"
placeholder="City"
value={city}
onChange={(e) => setCity(e.target.value)}
/>
<select
value={countryCode}
onChange={(e) => setCountryCode(e.target.value)}
>
{region?.countries?.map((country) => (
<option
key={country.iso_2}
value={country.iso_2}
>
{country.display_name}
</option>
))}
</select>
<input
type="text"
placeholder="Province"
value={province}
onChange={(e) => setProvince(e.target.value)}
/>
<input
type="tel"
placeholder="Phone Number"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
/>
<button
disabled={loading}
onClick={handleEdit}
>
Add
</button>
</form>
)
}
```
</CodeTab>
</CodeTabs>
The Update Address API route returns in the response a `customer` field, which is a [customer object](!api!/store#customers_customer_schema).
---
## Delete Customer Address
To delete a customer's address, send a request to the [Delete Customer Address API route](!api!/store#customers_deletecustomersmeaddressesaddress_id):
export const deleteHighlights = [
["3", "addrId", "The ID of the address to delete."]
]
```ts highlights={deleteHighlights}
fetch(
`http://localhost:9000/store/customers/me/addresses/${
addrId
}`,
{
credentials: "include",
method: "DELETE",
}
)
.then((res) => res.json())
.then(({ parent: customer }) => {
// use customer...
console.log(customer)
})
```
The Delete Customer Address API route returns a `parent` field in the response, which is a [customer object](!api!/store#customers_customer_schema).
@@ -0,0 +1,160 @@
export const metadata = {
title: `Customer Context in Storefront`,
}
# {metadata.title}
Throughout your storefront, you'll need to access the logged-in customer to perform different actions, such as associate it with a cart.
So, if your storefront is React-based, create a customer context and add it at the top of your components tree. Then, you can access the logged-in customer anywhere in your storefront.
## Create Customer Context Provider
For example, create the following file that exports a `CustomerProvider` component and a `useCustomer` hook:
export const highlights = [
["12", "customer", "Expose customer to children of the context provider."],
["13", "setCustomer", "Allow the context provider's\nchildren to change the logged-in customer."],
["24", "CustomerProvider", "The provider component to use in your component tree."],
["36", "fetch", "Try to retrieve the customer's details,\nif the customer is authentiated."],
["37", `credentials: "include"`, "Important to include this option for cookie session authentication.\nFor token authentication, pass the authorization header instead."],
["58", "useCustomer", "The hook that child components of the provider use to access the customer."]
]
```tsx highlights={highlights}
"use client" // include with Next.js 13+
import {
createContext,
useContext,
useEffect,
useState,
} from "react"
import { HttpTypes } from "@medusajs/types"
type CustomerContextType = {
customer: HttpTypes.StoreCustomer | undefined
setCustomer: React.Dispatch<
React.SetStateAction<HttpTypes.StoreCustomer | undefined>
>
}
const CustomerContext = createContext<CustomerContextType | null>(null)
type CustomerProviderProps = {
children: React.ReactNode
}
export const CustomerProvider = ({
children,
}: CustomerProviderProps) => {
const [customer, setCustomer] = useState<
HttpTypes.StoreCustomer
>()
useEffect(() => {
if (customer) {
return
}
fetch(`http://localhost:9000/store/customers/me`, {
credentials: "include",
})
.then((res) => res.json())
.then(({ customer }) => {
setCustomer(customer)
})
.catch((err) => {
// customer isn't logged in
})
}, [])
return (
<CustomerContext.Provider value={{
customer,
setCustomer,
}}>
{children}
</CustomerContext.Provider>
)
}
export const useCustomer = () => {
const context = useContext(CustomerContext)
if (!context) {
throw new Error("useCustomer must be used within a CustomerProvider")
}
return context
}
```
The `CustomerProvider` handles retrieving the authenticated customer from the Medusa application.
The `useCustomer` hook returns the value of the `CustomerContext`. Child components of `CustomerProvider` use this hook to access `customer` or `setCustomer`.
---
## Use CustomerProvider in Component Tree
To use the customer context's value, add the `CustomerProvider` high in your component tree.
For example, if you're using Next.js, add it to the `app/layout.tsx` or `src/app/layout.tsx` file:
```tsx title="app/layout.tsx" collapsibleLines="1-14" highlights={[["24"]]}
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import "./globals.css"
import { CartProvider } from "../providers/cart"
import { RegionProvider } from "../providers/region"
import { CustomerProvider } from "../providers/customer"
const inter = Inter({ subsets: ["latin"] })
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<RegionProvider>
<CustomerProvider>
{/* Other providers... */}
<CartProvider>
{children}
</CartProvider>
</CustomerProvider>
</RegionProvider>
</body>
</html>
)
}
```
---
## Use useCustomer Hook
Now, you can use the `useCustomer` hook in child components of `CustomerProvider`.
For example:
```tsx
"use client" // include with Next.js 13+
// ...
import { useCustomer } from "../providers/customer"
export default function Profile() {
const { customer } = useCustomer()
// ...
}
```
@@ -0,0 +1,89 @@
import { CodeTabs, CodeTab } from "docs-ui"
export const metadata = {
title: `Log-out Customer in Storefront`,
}
# {metadata.title}
In this document, you'll learn how to log-out a customer in the storefront based on the authentication method.
## Log-Out for JWT Token
If you're authenticating the customer with their JWT token, remove the stored token from the browser.
For example, if you've stored the JWT token in the `localStorage`, remove the item from it:
```ts
localStorage.removeItem(`token`)
```
---
## Log-Out for Cookie Session
If you're authenticating the customer with their cookie session ID, send a `DELETE` request to the `/auth/session`.
For example:
<CodeTabs group="store-request">
<CodeTab label="Fetch API" value="fetch">
```ts
fetch(`http://localhost:9000/auth/session`, {
credentials: "include",
method: "DELETE",
})
.then((res) => res.json())
.then(() => {
// TODO redirect customer to login page
})
```
</CodeTab>
<CodeTab label="React" value="react">
export const highlights = [
["3", "useCustomer", "Use the hook defined in the Customer Context guide."],
["9"], ["10"], ["11"], ["12"], ["13"], ["14"], ["15"], ["16"], ["17"], ["18"],
["19"],
]
```tsx highlights={highlights}
"use client" // include with Next.js 13+
import { useCustomer } from "../../../providers/customer"
export default function Profile() {
const { setCustomer } = useCustomer()
const handleLogOut = () => {
fetch(`http://localhost:9000/auth/session`, {
credentials: "include",
method: "DELETE",
})
.then((res) => res.json())
.then(() => {
setCustomer(undefined)
// TODO redirect to login page
alert("Logged out")
})
}
return (
<div>
{/* Profile details... */}
<button
onClick={handleLogOut}
>
Log Out
</button>
</div>
)
}
```
</CodeTab>
</CodeTabs>
The API route returns nothing in the response. If the request was successful, you can perform any necessary work to unset the customer and redirect them to the login page.
@@ -0,0 +1,347 @@
import { CodeTabs, CodeTab } from "docs-ui"
export const metadata = {
title: `Login Customer in Storefront`,
}
# {metadata.title}
In this document, you'll learn about the two ways to login a customer in a storefront.
## 1. Using a JWT Token
Using the `/auth/customer/emailpass` API route, you obtain a JSON Web Token (JWT) for the customer. Then, use that token as a bearer token in the authorization header of subsequent requests, and the customer is considered authenticated.
For example:
<CodeTabs group="store-request">
<CodeTab label="Fetch API" value="fetch">
export const fetchHighlights = [
["3", "fetch", "Send a request to obtain a JWT token."],
["21", "fetch", "Send a request as an authenticated customer."],
["27", "token", "Pass as a Bearer token in the authorization header."],
]
```ts highlights={fetchHighlights}
const handleLogin = async () => {
// obtain JWT token
const { token } = await fetch(
`http://localhost:9000/auth/customer/emailpass`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
}
)
.then((res) => res.json())
// use token in the authorization header of
// all follow up requests. For example:
const { customer } = await fetch(
`http://localhost:9000/store/customers/me`,
{
credentials: "include",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
}
)
.then((res) => res.json())
console.log(customer)
}
```
</CodeTab>
<CodeTab label="React" value="react">
export const highlights = [
["21", "fetch", "Send a request to obtain a JWT token."],
["39", "fetch", "Send a request as an authenticated customer."],
["45", "token", "Pass as a Bearer token in the authorization header."],
]
```tsx highlights={highlights} collapsibleLines="55-79" expandButtonLabel="Show form"
"use client" // include with Next.js 13+
import { useState } from "react"
export default function Login() {
const [loading, setLoading] = useState(false)
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const handleLogin = async (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
e.preventDefault()
if (!email || !password) {
return
}
setLoading(true)
// obtain JWT token
const { token } = await fetch(
`http://localhost:9000/auth/customer/emailpass`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
}
)
.then((res) => res.json())
// use token in the authorization header of
// all follow up requests. For example:
const { customer } = await fetch(
`http://localhost:9000/store/customers/me`,
{
credentials: "include",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
}
)
.then((res) => res.json())
console.log(customer)
setLoading(false)
}
return (
<form>
<input
type="email"
name="email"
value={email}
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
name="password"
value={password}
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
/>
<button
disabled={loading}
onClick={handleLogin}
>
Login
</button>
</form>
)
}
```
</CodeTab>
</CodeTabs>
In the example above, you:
1. Create a `handleLogin` function that logs in a customer.
2. In the function, you obtain a JWT token by sending a request to the `/auth/customer/emailpass`.
3. You can then use that token in the authorization header of subsequent requests, and the customer is considered authenticated. As an example, you send a request to obtain the customer's details.
---
## 2. Using a Cookie Session
Authenticating the customer with a cookie session means the customer is authenticated in subsequent requests that use that cookie.
If you're using the Fetch API, using the `credentials: include` option ensures that your cookie session is passed in every request.
For example:
<CodeTabs group="store-request">
<CodeTab label="Fetch API" value="fetch">
export const fetchSessionHighlights = [
["3", "fetch", "Send a request to obtain a JWT token."],
["20", "fetch", "Send a request to set the authenticated session ID in the cookies."],
["27", "token", "Pass as a Bearer token in the authorization header."],
["35", "fetch", "Retrieve the customer's details as an example of testing authentication."],
]
```ts highlights={fetchSessionHighlights}
const handleLogin = async () => {
// obtain JWT token
const { token } = await fetch(
`http://localhost:9000/auth/customer/emailpass`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
}
)
.then((res) => res.json())
// set session
await fetch(
`http://localhost:9000/auth/session`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
}
)
.then((res) => res.json())
// customer is now authenticated using the
// cookie session. For example
const { customer } = await fetch(
`http://localhost:9000/store/customers/me`,
{
credentials: "include",
headers: {
"Content-Type": "application/json",
},
}
)
.then((res) => res.json())
console.log(customer)
setLoading(false)
}
```
</CodeTab>
<CodeTab label="React" value="react">
export const sessionHighlights = [
["21", "fetch", "Send a request to obtain a JWT token."],
["38", "fetch", "Send a request to set the authenticated session ID in the cookies."],
["45", "token", "Pass as a Bearer token in the authorization header."],
["53", "fetch", "Retrieve the customer's details as an example of testing authentication."],
]
```tsx highlights={sessionHighlights} collapsibleLines="68-92" expandButtonLabel="Show form"
"use client" // include with Next.js 13+
import { useState } from "react"
export default function Login() {
const [loading, setLoading] = useState(false)
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const handleLogin = async (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
e.preventDefault()
if (!email || !password) {
return
}
setLoading(true)
// obtain JWT token
const { token } = await fetch(
`http://localhost:9000/auth/customer/emailpass`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
}
)
.then((res) => res.json())
// set session
await fetch(
`http://localhost:9000/auth/session`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
}
)
.then((res) => res.json())
// customer is now authenticated using the
// cookie session. For example
const { customer } = await fetch(
`http://localhost:9000/store/customers/me`,
{
credentials: "include",
headers: {
"Content-Type": "application/json",
},
}
)
.then((res) => res.json())
console.log(customer)
setLoading(false)
}
return (
<form>
<input
type="email"
name="email"
value={email}
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
name="password"
value={password}
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
/>
<button
disabled={loading}
onClick={handleLogin}
>
Login
</button>
</form>
)
}
```
</CodeTab>
</CodeTabs>
In the example above, you:
1. Create a `handleLogin` function that logs in a customer.
2. In the function, you obtain a JWT token by sending a request to the `/auth/customer/emailpass`.
3. You send a request to the `/auth/session` API route passing in the authorization header the token as a Bearer token. This sets the authenticated session ID in the cookies.
4. You can now send authenticated requests, as long as you include the `credentials: include` option in your fetch requests. For example, you send a request to retrieve the customer's details.
@@ -0,0 +1,11 @@
import { ChildDocs } from "docs-ui"
export const metadata = {
title: `Customers in Storefront`,
}
# {metadata.title}
A customer can register, manage their account, keep track of their orders, and more.
<ChildDocs type="item" onlyTopLevel={true} />
@@ -0,0 +1,145 @@
import { CodeTabs, CodeTab } from "docs-ui"
export const metadata = {
title: `Edit Customer Profile in Storefront`,
}
# {metadata.title}
To edit the customer's profile in the storefront, send a request to the [Update Customer API route](!api!/store#customers_postcustomersme).
For example:
<CodeTabs group="store-request">
<CodeTab label="Fetch API" value="fetch">
```ts
fetch(`http://localhost:9000/store/customers/me`, {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
first_name,
last_name,
company_name,
phone,
}),
})
.then((res) => res.json())
.then(({ customer }) => {
// use customer...
console.log(customer)
})
```
</CodeTab>
<CodeTab label="React" value="react">
export const highlights = [
["4", "useCustomer", "Use the hook defined in the Customer Context guide."],
["33"], ["34"], ["35"], ["36"], ["37"], ["38"], ["39"], ["40"], ["41"], ["42"],
["43"], ["44"], ["45"], ["46"], ["47"], ["48"], ["49"]
]
```tsx highlights={highlights} collapsibleLines="53-91" expandButtonLabel="Show form"
"use client" // include with Next.js 13+
import { useState } from "react"
import { useCustomer } from "../../../providers/customer"
export default function EditProfile() {
const { customer, setCustomer } = useCustomer()
const [firstName, setFirstName] = useState(
customer?.first_name || ""
)
const [lastName, setLastName] = useState(
customer?.last_name || ""
)
const [company, setCompany] = useState(
customer?.company_name || ""
)
const [phone, setPhone] = useState(
customer?.phone || ""
)
const [loading, setLoading] = useState(false)
const handleEdit = (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
e.preventDefault()
if (!customer) {
return
}
setLoading(true)
fetch(`http://localhost:9000/store/customers/me`, {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
first_name: firstName,
last_name: lastName,
company_name: company,
phone,
}),
})
.then((res) => res.json())
.then(({ customer: updatedCustomer }) => {
setCustomer(updatedCustomer)
})
.finally(() => setLoading(false))
}
return (
<form>
<input
type="text"
name="first_name"
value={firstName}
placeholder="First Name"
onChange={(e) => setFirstName(e.target.value)}
/>
<input
type="text"
name="last_name"
value={lastName}
placeholder="Last Name"
onChange={(e) => setLastName(e.target.value)}
/>
<input
type="text"
name="company"
value={company}
placeholder="Company"
onChange={(e) => setCompany(e.target.value)}
/>
<input
type="text"
name="phone"
value={phone}
placeholder="Phone Number"
onChange={(e) => setPhone(e.target.value)}
/>
<button
disabled={loading}
onClick={handleEdit}
>
Edit
</button>
</form>
)
}
```
</CodeTab>
</CodeTabs>
In the example above, you send a request to the Update Customer API route to update the customer's details.
The response of the request has a `customer` field which is a [customer object](!api!/store#customers_customer_schema).
@@ -0,0 +1,190 @@
import { CodeTabs, CodeTab } from "docs-ui"
export const metadata = {
title: `Register Customer in Storefront`,
}
# {metadata.title}
To register a customer, you implement the following steps:
1. Show the customer a form to enter their details.
2. Send a `POST` request to the `/auth/customer/emailpass` API route to obtain a JWT token.
3. Send a request to the [Create Customer API route](!api!/store#customers_postcustomers) pass the JWT token in the header.
For example:
<CodeTabs group="store-request">
<CodeTab label="Fetch API" value="fetch">
export const fetchHighlights = [
["3", "fetch", "Send a request to obtain a JWT token."],
["20", "fetch", "Send a request to create the customer."],
["27", "token", "Pass as a Bearer token in the authorization header."],
["39", "TODO", "Redirect the customer to the log in page."]
]
```ts highlights={fetchHighlights}
const handleRegistration = async () => {
// obtain JWT token
const { token } = await fetch(
`http://localhost:9000/auth/customer/emailpass`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
}
)
.then((res) => res.json())
// create customer
const { customer } = await fetch(
`http://localhost:9000/store/customers`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify({
first_name: firstName,
last_name: lastName,
email,
}),
}
)
.then((res) => res.json())
console.log(customer)
// TODO redirect to login page
}
```
</CodeTab>
<CodeTab label="React" value="react">
export const highlights = [
["22", "fetch", "Send a request to obtain a JWT token."],
["39", "fetch", "Send a request to create the customer."],
["46", "token", "Pass as a Bearer token in the authorization header."],
["59", "TODO", "Redirect the customer to the log in page."]
]
```tsx highlights={highlights} collapsibleLines="61-100" expandButtonLabel="Show form"
"use client" // include with Next.js 13+
import { useState } from "react"
export default function Register() {
const [loading, setLoading] = useState(false)
const [firstName, setFirstName] = useState("")
const [lastName, setLastName] = useState("")
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const handleRegistration = async (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
e.preventDefault()
if (!firstName || !lastName || !email || !password) {
return
}
setLoading(true)
// obtain JWT token
const { token } = await fetch(
`http://localhost:9000/auth/customer/emailpass`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
}
)
.then((res) => res.json())
// create customer
const { customer } = await fetch(
`http://localhost:9000/store/customers`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify({
first_name: firstName,
last_name: lastName,
email,
}),
}
)
.then((res) => res.json())
console.log(customer)
setLoading(false)
// TODO redirect to login page
}
return (
<form>
<input
type="text"
name="first_name"
value={firstName}
placeholder="First Name"
onChange={(e) => setFirstName(e.target.value)}
/>
<input
type="text"
name="last_name"
value={lastName}
placeholder="Last Name"
onChange={(e) => setLastName(e.target.value)}
/>
<input
type="email"
name="email"
value={email}
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
name="password"
value={password}
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
/>
<button
disabled={loading}
onClick={handleRegistration}
>
Register
</button>
</form>
)
}
```
</CodeTab>
</CodeTabs>
In the above example, you create a `handleRegistration` function that:
- Obtains a JWT token from the `/auth/customer/emailpass` API route.
- Send a request to the Create Customer API route, and pass the JWT token as a Bearer token in the authorization header.
- Once the customer is registered successfully, you can either redirect the customer to the login page or log them in automatically as explained in this guide.
@@ -0,0 +1,66 @@
import { CodeTabs, CodeTab } from "docs-ui"
export const metadata = {
title: `Retrieve Customer in Storefront`,
}
# {metadata.title}
To retrieve a customer after it's been authenticated in your storefront, send a request to the [Get Customer API route](!api!/store#customers_getcustomersme):
<CodeTabs group="authenticated-request">
<CodeTab label="Using Bearer Token" value="bearer">
export const bearerHighlights = [
["7", "", "Pass JWT token as bearer token in authorization header."],
]
```ts highlights={bearerHighlights}
fetch(
`http://localhost:9000/store/customers/me`,
{
credentials: "include",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
}
)
.then((res) => res.json())
.then(({ customer }) => {
// use customer...
console.log(customer)
})
```
</CodeTab>
<CodeTab label="Using Cookie Session" value="session">
export const sessionHighlights = [
["4", "", "Pass this option to ensure the cookie session is passed in the request."],
]
```ts highlights={sessionHighlights}
fetch(
`http://localhost:9000/store/customers/me`,
{
credentials: "include",
headers: {
"Content-Type": "application/json",
},
}
)
.then((res) => res.json())
.then(({ customer }) => {
// use customer...
console.log(customer)
})
```
</CodeTab>
</CodeTabs>
- If you authenticate the customer with bearer authorization, pass the token in the authorization header of the request.
- If you authenticate the customer with cookie session, pass the `credentials: include` option to the `fetch` function.
The Get Customer API route returns a `customer` field, which is a [customer object](!api!/store#customers_customer_schema).
@@ -37,7 +37,7 @@ export const highlights = [
```tsx highlights={highlights}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react";
import { useEffect, useState } from "react"
import { HttpTypes } from "@medusajs/types"
export default function Categories() {
@@ -75,7 +75,7 @@ export const highlights = [
</ul>
)}
</div>
);
)
}
```
@@ -108,7 +108,7 @@ export const paginateHighlights = [
```tsx highlights={paginateHighlights}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react";
import { useEffect, useState } from "react"
import { HttpTypes } from "@medusajs/types"
export default function Categories() {
@@ -147,7 +147,7 @@ export default function Categories() {
}
return [
...prev,
...product_categories
...product_categories,
]
})
setHasMorePages(count > limit * currentPage)
@@ -180,7 +180,7 @@ export default function Categories() {
</button>
)}
</div>
);
)
}
```
@@ -196,7 +196,7 @@ For example, to run a query on the product categories:
```ts
const searchParams = new URLSearchParams({
q: "Shirt"
q: "Shirt",
})
fetch(`http://localhost:9000/store/product-categories?${
@@ -21,7 +21,7 @@ export const fetchHighlights = [
```ts highlights={fetchHighlights}
const searchParams = new URLSearchParams({
fields: "*category_children"
fields: "*category_children",
})
fetch(`http://localhost:9000/store/product-categories/${id}?${
@@ -71,7 +71,7 @@ export const highlights = [
}
const searchParams = new URLSearchParams({
fields: "*category_children"
fields: "*category_children",
})
fetch(`http://localhost:9000/store/product-categories/${id}?${
@@ -19,14 +19,14 @@ export const fetchHighlights = [
```ts highlights={fetchHighlights}
const searchParams = new URLSearchParams({
// other query params...
"category_id[]": categoryId
"category_id[]": categoryId,
})
fetch(`http://localhost:9000/store/products?${searchParams.toString()}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ products, count }) => {
@@ -60,7 +60,7 @@ export const highlights = [
}
export default function CategoryProducts({
params: { categoryId }
params: { categoryId },
}: Params) {
const [loading, setLoading] = useState(true)
const [products, setProducts] = useState<
@@ -80,14 +80,14 @@ export const highlights = [
const searchParams = new URLSearchParams({
limit: `${limit}`,
offset: `${offset}`,
"category_id[]": categoryId
"category_id[]": categoryId,
})
fetch(`http://localhost:9000/store/products?${searchParams.toString()}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ products: dataProducts, count }) => {
@@ -98,7 +98,7 @@ export const highlights = [
}
return [
...prev,
...dataProducts
...dataProducts,
]
})
setHasMorePages(count > limit * currentPage)
@@ -131,7 +131,7 @@ export const highlights = [
</button>
)}
</div>
);
)
}
```
@@ -37,7 +37,7 @@ export const highlights = [
```tsx highlights={highlights}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react";
import { useEffect, useState } from "react"
import { HttpTypes } from "@medusajs/types"
export default function Collections() {
@@ -75,7 +75,7 @@ export const highlights = [
</ul>
)}
</div>
);
)
}
```
@@ -110,7 +110,7 @@ export const paginateHighlights = [
```tsx highlights={paginateHighlights}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react";
import { useEffect, useState } from "react"
import { HttpTypes } from "@medusajs/types"
export default function Collections() {
@@ -149,7 +149,7 @@ export default function Collections() {
}
return [
...prev,
...dataCollections
...dataCollections,
]
})
setHasMorePages(count > limit * currentPage)
@@ -182,7 +182,7 @@ export default function Collections() {
</button>
)}
</div>
);
)
}
```
@@ -198,7 +198,7 @@ For example:
```ts
const searchParams = new URLSearchParams({
title: "test"
title: "test",
})
fetch(`http://localhost:9000/store/collections?${
@@ -19,14 +19,14 @@ export const fetchHighlights = [
```ts highlights={fetchHighlights}
const searchParams = new URLSearchParams({
// other query params...
"collection_id[]": collectionId
"collection_id[]": collectionId,
})
fetch(`http://localhost:9000/store/products?${searchParams.toString()}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ products, count }) => {
@@ -60,7 +60,7 @@ export const highlights = [
}
export default function CollectionProducts({
params: { collectionId }
params: { collectionId },
}: Params) {
const [loading, setLoading] = useState(true)
const [products, setProducts] = useState<
@@ -80,7 +80,7 @@ export const highlights = [
const searchParams = new URLSearchParams({
limit: `${limit}`,
offset: `${offset}`,
"collection_id[]": collectionId
"collection_id[]": collectionId,
})
fetch(`http://localhost:9000/store/products?${
@@ -88,8 +88,8 @@ export const highlights = [
}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ products: dataProducts, count }) => {
@@ -100,7 +100,7 @@ export const highlights = [
}
return [
...prev,
...dataProducts
...dataProducts,
]
})
setHasMorePages(count > limit * currentPage)
@@ -133,7 +133,7 @@ export const highlights = [
</button>
)}
</div>
);
)
}
```
@@ -23,8 +23,8 @@ export const fetchHighlights = [
fetch(`http://localhost:9000/store/products`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then((data) => {
@@ -45,7 +45,7 @@ export const highlights = [
```tsx highlights={highlights}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react";
import { useEffect, useState } from "react"
import { HttpTypes } from "@medusajs/types"
export default function Products() {
@@ -62,8 +62,8 @@ export const highlights = [
fetch(`http://localhost:9000/store/products`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then((data) => {
@@ -84,7 +84,7 @@ export const highlights = [
</ul>
)}
</div>
);
)
}
```
@@ -119,7 +119,7 @@ export const paginateHighlights = [
```tsx highlights={paginateHighlights}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react";
import { useEffect, useState } from "react"
import { HttpTypes } from "@medusajs/types"
export default function Products() {
@@ -146,8 +146,8 @@ export default function Products() {
fetch(`http://localhost:9000/store/products?${searchParams.toString()}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ products: dataProducts, count }) => {
@@ -158,7 +158,7 @@ export default function Products() {
}
return [
...prev,
...dataProducts
...dataProducts,
]
})
setHasMorePages(count > limit * currentPage)
@@ -189,7 +189,7 @@ export default function Products() {
</button>
)}
</div>
);
)
}
```
@@ -206,14 +206,14 @@ For example, to run a query on the products:
```ts
const searchParams = new URLSearchParams({
// other params...
q: "Shirt"
q: "Shirt",
})
fetch(`http://localhost:9000/store/products?${searchParams.toString()}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ products: dataProducts, count }) => {
@@ -74,14 +74,14 @@ export default function Product({ params: { id } }: Params) {
const queryParams = new URLSearchParams({
fields: `*variants.calculated_price`,
region_id: region.id
region_id: region.id,
})
fetch(`http://localhost:9000/store/products/${id}?${queryParams.toString()}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ product: dataProduct }) => {
@@ -152,7 +152,7 @@ export default function Product({ params: { id } }: Params) {
setSelectedOptions((prev) => {
return {
...prev,
[option.id!]: optionValue.value!
[option.id!]: optionValue.value!,
}
})
}}
@@ -254,14 +254,14 @@ export default function Product({ params: { id } }: Params) {
const queryParams = new URLSearchParams({
fields: `*variants.calculated_price`,
region_id: region.id
region_id: region.id,
})
fetch(`http://localhost:9000/store/products/${id}?${queryParams.toString()}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ product: dataProduct }) => {
@@ -350,7 +350,7 @@ export default function Product({ params: { id } }: Params) {
setSelectedOptions((prev) => {
return {
...prev,
[option.id!]: optionValue.value!
[option.id!]: optionValue.value!,
}
})
}}
@@ -29,8 +29,8 @@ export const fetchHighlights = [
fetch(`http://localhost:9000/store/products/${id}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ product }) => {
@@ -75,8 +75,8 @@ export const highlights = [
fetch(`http://localhost:9000/store/products/${id}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ product: dataProduct }) => {
@@ -140,8 +140,8 @@ export const handleFetchHighlights = [
fetch(`http://localhost:9000/store/products?handle=${handle}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ products }) => {
@@ -190,8 +190,8 @@ export const handleHighlights = [
fetch(`http://localhost:9000/store/products?handle=${handle}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ products }) => {
@@ -57,8 +57,8 @@ export default function Product({ params: { id } }: Params) {
fetch(`http://localhost:9000/store/products/${id}`, {
credentials: "include",
headers: {
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp"
}
"x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp",
},
})
.then((res) => res.json())
.then(({ product: dataProduct }) => {
@@ -99,7 +99,7 @@ export default function Product({ params: { id } }: Params) {
setSelectedOptions((prev) => {
return {
...prev,
[option.id!]: optionValue.value!
[option.id!]: optionValue.value!,
}
})
}}
@@ -32,7 +32,7 @@ import {
createContext,
useContext,
useEffect,
useState
useState,
} from "react"
import { HttpTypes } from "@medusajs/types"
@@ -68,7 +68,7 @@ export const RegionProvider = (
if (!regionId) {
// retrieve regions and select the first one
fetch(`http://localhost:9000/store/regions`, {
credentials: "include"
credentials: "include",
})
.then((res) => res.json())
.then(({ regions }) => {
@@ -77,7 +77,7 @@ export const RegionProvider = (
} else {
// retrieve selected region
fetch(`http://localhost:9000/store/regions/${regionId}`, {
credentials: "include"
credentials: "include",
})
.then((res) => res.json())
.then(({ region: dataRegion }) => {
@@ -89,7 +89,7 @@ export const RegionProvider = (
return (
<RegionContext.Provider value={{
region,
setRegion
setRegion,
}}>
{children}
</RegionContext.Provider>
@@ -126,12 +126,12 @@ import "./globals.css"
import { CartProvider } from "../providers/cart"
import { RegionProvider } from "../providers/region"
const inter = Inter({ subsets: ["latin"] });
const inter = Inter({ subsets: ["latin"] })
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
}
export default function RootLayout({
children,
@@ -147,7 +147,7 @@ export default function RootLayout({
</RegionProvider>
</body>
</html>
);
)
}
```
@@ -13,7 +13,7 @@ To list regions in your storefront, send a request to the [List Regions API rout
```ts
fetch(`http://localhost:9000/store/regions`, {
credentials: "include"
credentials: "include",
})
.then((res) => res.json())
.then(({ regions }) => {
@@ -32,7 +32,7 @@ export const highlights = [
```tsx highlights={highlights}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react";
import { useEffect, useState } from "react"
import { HttpTypes } from "@medusajs/types"
export default function Regions() {
@@ -47,7 +47,7 @@ export const highlights = [
}
fetch(`http://localhost:9000/store/regions`, {
credentials: "include"
credentials: "include",
})
.then((res) => res.json())
.then(({ regions: dataRegions }) => {
@@ -68,7 +68,7 @@ export const highlights = [
</ul>
)}
</div>
);
)
}
```
@@ -32,7 +32,7 @@ To retrieve the selected region, use the [Retrieve Region API route](!api!/store
const regionId = localStorage.getItem("region_id")
fetch(`http://localhost:9000/store/regions/${regionId}`, {
credentials: "include"
credentials: "include",
})
.then((res) => res.json())
.then(({ region }) => {
@@ -46,8 +46,8 @@ module.exports = defineConfig({
http: {
storeCors: "http://localhost:3000",
// ...
}
}
},
},
// ...
})
```