From 2b27ebb33fff5c55fc57edf625fce407c13fd00f Mon Sep 17 00:00:00 2001 From: Fachri Hawari Date: Sun, 22 Sep 2024 14:52:22 +0700 Subject: [PATCH] add to cart done --- .env.example | 1 + src/app/(main)/cart/components/CartItem.tsx | 35 +++++++ .../(main)/cart/components/CartItemAction.tsx | 44 +++++++++ src/app/(main)/cart/components/CartItems.tsx | 72 +++----------- src/app/(main)/cart/page.tsx | 13 ++- .../products/components/ProductsList.tsx | 2 +- src/app/api/cart/[productId]/route.ts | 92 +++++++++++++++++ src/app/api/cart/route.ts | 16 +++ src/app/api/carts/route.ts | 99 ------------------- src/components/AddToCartButton.tsx | 22 +++-- src/components/Navbar.tsx | 10 +- src/components/SectionProducts.tsx | 2 +- src/lib/actions/cart.ts | 64 ++++++++---- src/lib/db/cart_collection.ts | 10 +- src/lib/db/product_collection.ts | 8 +- src/lib/db/seeds.ts | 6 +- src/lib/db/user_collection.ts | 8 +- src/lib/utils/auth.ts | 5 + src/middleware.ts | 15 +-- 19 files changed, 303 insertions(+), 221 deletions(-) create mode 100644 src/app/(main)/cart/components/CartItem.tsx create mode 100644 src/app/(main)/cart/components/CartItemAction.tsx create mode 100644 src/app/api/cart/[productId]/route.ts create mode 100644 src/app/api/cart/route.ts delete mode 100644 src/app/api/carts/route.ts create mode 100644 src/lib/utils/auth.ts diff --git a/.env.example b/.env.example index 0bc1145..85deef8 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,4 @@ MONGO_DATABASE=dbname TOTAL_PRODUCTS=2000 PORT=3000 NEXT_PUBLIC_URL=http://localhost:3000 +JWT_SECRET=secret \ No newline at end of file diff --git a/src/app/(main)/cart/components/CartItem.tsx b/src/app/(main)/cart/components/CartItem.tsx new file mode 100644 index 0000000..da1a43f --- /dev/null +++ b/src/app/(main)/cart/components/CartItem.tsx @@ -0,0 +1,35 @@ +import Image from "next/image"; +import { formatCurrency } from "@/lib/utils/number"; +import { CartItem as CartItemType } from '@/lib/db/cart_collection'; +import { CartItemAction } from "./CartItemAction"; + +type CartItemProps = { + item: CartItemType +} + +export function CartItem({ item }: CartItemProps) { + return ( +
+
+ {item.name} +
+
+

{item.name}

+
+

Unit Price: {formatCurrency(item.price)}

+

Quantity: {item.quantity}

+
+

Total: {formatCurrency(item.price * item.quantity)}

+
+
+ +
+
+ ); +} diff --git a/src/app/(main)/cart/components/CartItemAction.tsx b/src/app/(main)/cart/components/CartItemAction.tsx new file mode 100644 index 0000000..d5ec550 --- /dev/null +++ b/src/app/(main)/cart/components/CartItemAction.tsx @@ -0,0 +1,44 @@ +'use client' + +import { FiMinus, FiPlus, FiTrash2 } from "react-icons/fi"; +import type { CartItem } from "@/lib/db/cart_collection"; +import { removeFromCart, updateCartItemQuantity } from "@/lib/actions/cart"; + +type CartItemActionProps = { + item: CartItem +} + +export function CartItemAction({ item }: CartItemActionProps) { + return ( +
+ + {item.quantity} + + +
+ ) +} \ No newline at end of file diff --git a/src/app/(main)/cart/components/CartItems.tsx b/src/app/(main)/cart/components/CartItems.tsx index 9335202..e86c972 100644 --- a/src/app/(main)/cart/components/CartItems.tsx +++ b/src/app/(main)/cart/components/CartItems.tsx @@ -1,72 +1,22 @@ -'use client'; - -import { useState } from 'react'; import { formatCurrency } from "@/lib/utils/number"; -import Image from "next/image"; -import { FiMinus, FiPlus, FiTrash2 } from "react-icons/fi"; -import { updateCartItemQuantity, removeFromCart } from '@/lib/actions/cart'; -import { Product } from '@/lib/db/product_collection'; +import type { CartItem as CartItemType } from "@/lib/db/cart_collection"; +import { CartItem } from "./CartItem"; -type CartItem = { - product: Product; - quantity: number; +type CartItemsProps = { + cartItems: CartItemType[] } -export default function CartItems({ initialCart }: { initialCart: CartItem[] }) { - const [cart, setCart] = useState(initialCart); - - const handleUpdateQuantity = async (productId: string, newQuantity: number) => { - if (newQuantity === 0) { - await removeFromCart(productId); - setCart(cart.filter(item => item.product._id?.toString() !== productId)); - } else { - await updateCartItemQuantity(productId, newQuantity); - setCart(cart.map(item => - item.product._id?.toString() === productId ? { ...item, quantity: newQuantity } : item - )); - } - }; - - const handleRemoveItem = async (productId: string) => { - await removeFromCart(productId); - setCart(cart.filter(item => item.product._id?.toString() !== productId)); - }; - - const totalPrice = cart.reduce((total, item) => total + item.product.price * item.quantity, 0); - +export default function CartItems({ cartItems }: CartItemsProps) { + const totalPrice = cartItems.reduce((total, item) => total + item.price * item.quantity, 0); return (

Products

- {cart.map((item) => ( -
- {item.product.name} -
-

{item.product.name}

-

{formatCurrency(item.product.price)}

-
-
- - {item.quantity} - -
- -
+ {cartItems.map((item) => ( + ))}
diff --git a/src/app/(main)/cart/page.tsx b/src/app/(main)/cart/page.tsx index ab83dfc..b748ccb 100644 --- a/src/app/(main)/cart/page.tsx +++ b/src/app/(main)/cart/page.tsx @@ -1,21 +1,20 @@ -import { cookies } from 'next/headers'; import CartNotLoggedIn from './components/CartNotLoggedIn'; import CartEmpty from './components/CartEmpty'; import CartItems from './components/CartItems'; import { getCart } from '@/lib/actions/cart'; +import { isLoggedIn } from '@/lib/utils/auth'; export default async function CartPage() { - const isLoggedIn = cookies().get('token'); // Replace this with actual authentication check const cart = await getCart(); - + let content; - - if (!isLoggedIn) { + + if (!isLoggedIn()) { content = - } else if (cart.length === 0) { + } else if (cart.items.length === 0) { content = } else { - content = + content = } return ( diff --git a/src/app/(main)/products/components/ProductsList.tsx b/src/app/(main)/products/components/ProductsList.tsx index 6fc3b97..150bc23 100644 --- a/src/app/(main)/products/components/ProductsList.tsx +++ b/src/app/(main)/products/components/ProductsList.tsx @@ -20,7 +20,7 @@ async function ProductsList({ searchParams }: ProductsListProps) { {products.length > 0 ?
{products.map((product) => ( - + ))}
: diff --git a/src/app/api/cart/[productId]/route.ts b/src/app/api/cart/[productId]/route.ts new file mode 100644 index 0000000..cbb9850 --- /dev/null +++ b/src/app/api/cart/[productId]/route.ts @@ -0,0 +1,92 @@ +import { CartItemSchema, cartsCollection } from "@/lib/db/cart_collection"; +import { productsCollection } from "@/lib/db/product_collection"; +import { NextRequest, NextResponse } from "next/server"; +import { ObjectId } from "mongodb"; +import { z } from "zod"; + +const CartItemDto = z.object({ + quantity: z.number() +}) +const CartParamsDto = z.object({ + productId: z.string() +}) + +type NextRouteParams = { + params: { + productId: string + } +} + +export async function POST(req: NextRequest, { params }: NextRouteParams) { + try { + const { productId } = CartParamsDto.parse(params) + const userId = String(req.headers.get("x-user-id")) + const rawBody = await req.json(); + const { quantity } = CartItemDto.parse(rawBody) + + const product = await productsCollection.findOne({ _id: new ObjectId(productId) }); + if (!product) { + return NextResponse.json({ errors: ["Product not found"] }, { status: 404 }); + } + + const cartItem = CartItemSchema.parse({ + productId: new ObjectId(product._id), + name: product.name, + price: product.price, + thumbnail: product.thumbnail, + quantity: quantity, + }); + + await cartsCollection.updateOne( + { userId: new ObjectId(userId) }, + { $push: { items: cartItem } }, + { upsert: true } + ); + + return NextResponse.json({ message: "Product added to cart successfully" }); + } catch (error) { + return NextResponse.json({ errors: ["An unexpected error occurred"] }, { status: 500 }); + } +} + +export async function PUT(req: NextRequest, { params }: NextRouteParams) { + try { + const { productId } = CartParamsDto.parse(params) + const userId = String(req.headers.get("x-user-id")); + const rawBody = await req.json(); + const { quantity } = CartItemDto.parse(rawBody) + + await cartsCollection.updateOne( + { + userId: new ObjectId(userId), + "items.productId": new ObjectId(productId) + }, + { + $set: { + "items.$.quantity": quantity + } + } + ); + + return NextResponse.json({ message: "Product qty updated in cart successfully" }); + } catch (error) { + return NextResponse.json({ errors: ["An unexpected error occurred"] }, { status: 500 }); + } +} + +export async function DELETE(req: NextRequest, { params }: NextRouteParams) { + try { + const userId = String(req.headers.get("x-user-id")); + + const { productId } = CartParamsDto.parse(params) + + await cartsCollection.updateOne( + { userId: new ObjectId(userId) }, + { $pull: { items: { productId: new ObjectId(productId) } } } + ); + + return NextResponse.json({ message: "Product removed from cart successfully" }); + } catch (error) { + return NextResponse.json({ errors: ["An unexpected error occurred"] }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/cart/route.ts b/src/app/api/cart/route.ts new file mode 100644 index 0000000..6e027fe --- /dev/null +++ b/src/app/api/cart/route.ts @@ -0,0 +1,16 @@ +import { cartsCollection } from "@/lib/db/cart_collection"; +import { NextRequest, NextResponse } from "next/server"; +import { ObjectId } from "mongodb"; + +export async function GET(req: NextRequest) { + try { + const userId = String(req.headers.get("x-user-id")) + const cart = await cartsCollection.findOne({ userId: new ObjectId(userId) }); + if (!cart) { + return NextResponse.json({ errors: ["Cart not found"] }, { status: 404 }); + } + return NextResponse.json(cart); + } catch (error) { + return NextResponse.json({ errors: ["An unexpected error occurred"] }, { status: 500 }); + } +} diff --git a/src/app/api/carts/route.ts b/src/app/api/carts/route.ts deleted file mode 100644 index 811d4b9..0000000 --- a/src/app/api/carts/route.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { CartItemSchema, cartsCollection } from "@/lib/db/cart_collection"; -import { NextRequest, NextResponse } from "next/server"; -import { ObjectId } from "mongodb"; -import { productsCollection } from "@/lib/db/product_collection"; - -const CartItemFormSchema = CartItemSchema.pick({ - productId: true, - quantity: true -}); -const DeleteCartItemFormSchema = CartItemFormSchema.pick({ - productId: true -}); - -export async function GET(req: NextRequest) { - try { - const userId = String(req.headers.get("x-user-id")) - const cart = await cartsCollection.findOne({ userId: new ObjectId(userId) }); - if (!cart) { - return NextResponse.json({ errors: ["Cart not found"] }, { status: 404 }); - } - return NextResponse.json(cart); - } catch (error) { - return NextResponse.json({ errors: ["An unexpected error occurred"] }, { status: 500 }); - } -} - -export async function POST(req: NextRequest) { - try { - const userId = String(req.headers.get("x-user-id")) - const rawBody = await req.json(); - const body = CartItemFormSchema.parse(rawBody) - - const product = await productsCollection.findOne({ _id: new ObjectId(body.productId) }); - if (!product) { - return NextResponse.json({ errors: ["Product not found"] }, { status: 404 }); - } - - const cartItem = CartItemSchema.parse({ - productId: new ObjectId(product._id), - name: product.name, - price: product.price, - thumbnail: product.thumbnail, - quantity: body.quantity, - }); - - await cartsCollection.updateOne( - { userId: new ObjectId(userId) }, - { $push: { items: cartItem } }, - { upsert: true } - ); - - return NextResponse.json({ message: "Product added to cart successfully" }); - } catch (error) { - return NextResponse.json({ errors: ["An unexpected error occurred"] }, { status: 500 }); - } -} - -export async function PUT(req: NextRequest) { - try { - const userId = String(req.headers.get("x-user-id")); - const rawBody = await req.json(); - - const body = CartItemFormSchema.parse(rawBody) - - await cartsCollection.updateOne( - { - userId: new ObjectId(userId), - "items.productId": new ObjectId(body.productId) - }, - { - $set: { - "items.$.quantity": body.quantity - } - } - ); - - return NextResponse.json({ message: "Product qty updated in cart successfully" }); - } catch (error) { - return NextResponse.json({ errors: ["An unexpected error occurred"] }, { status: 500 }); - } -} - -export async function DELETE(req: NextRequest) { - try { - const userId = String(req.headers.get("x-user-id")); - const rawBody = await req.json(); - - const body = DeleteCartItemFormSchema.parse(rawBody) - - await cartsCollection.updateOne( - { userId: new ObjectId(userId) }, - { $pull: { items: { productId: new ObjectId(body.productId) } } } - ); - - return NextResponse.json({ message: "Product removed from cart successfully" }); - } catch (error) { - return NextResponse.json({ errors: ["An unexpected error occurred"] }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/components/AddToCartButton.tsx b/src/components/AddToCartButton.tsx index 86ec000..415484b 100644 --- a/src/components/AddToCartButton.tsx +++ b/src/components/AddToCartButton.tsx @@ -1,8 +1,8 @@ -'use client'; +'use client' -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { FiShoppingCart } from "react-icons/fi"; -import { addToCart } from "@/lib/actions/cart"; +import { addToCart, getCart } from "@/lib/actions/cart"; import type { Product } from "@/lib/db/product_collection"; interface AddToCartButtonProps { @@ -12,10 +12,20 @@ interface AddToCartButtonProps { export default function AddToCartButton({ product, size = 'small' }: AddToCartButtonProps) { const [isLoading, setIsLoading] = useState(false); + const [isInCart, setIsInCart] = useState(false); + + useEffect(() => { + const checkIfInCart = async () => { + const cart = await getCart(); + setIsInCart(cart.items.some(item => item.productId.toString() === product._id.toString())); + }; + + checkIfInCart(); + }, [product._id]); const handleAddToCart = async () => { setIsLoading(true); - await addToCart(product._id?.toString() as string, 1); + await addToCart(product._id.toString(), 1); setIsLoading(false); }; @@ -28,11 +38,11 @@ export default function AddToCartButton({ product, size = 'small' }: AddToCartBu return ( ); } \ No newline at end of file diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index d020fc1..1cee14a 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,4 +1,4 @@ -import { cookies, headers } from 'next/headers'; +import { headers } from 'next/headers'; import Link from 'next/link'; import { redirect } from 'next/navigation'; import { FiSearch, FiShoppingCart, FiX } from 'react-icons/fi'; @@ -7,15 +7,13 @@ import { buildSearchParams, setQueryParams } from '@/lib/utils/url'; import TokoPakEdiLogo from './TokoPakEdiLogo'; import { logout } from '@/lib/actions/users'; import { getCart } from '@/lib/actions/cart'; +import { isLoggedIn } from '@/lib/utils/auth'; async function Navbar() { const searchParams = buildSearchParams(headers().get('x-current-url')) // HACK: get current url from headers const search = searchParams.get('q') ?? '' - - const isLoggedIn = cookies().get('token') const cart = await getCart(); - console.log(cart) - const cartItemCount = isLoggedIn ? cart.items.reduce((total, item) => total + item.quantity, 0) : 0 + const cartItemCount = cart.items.reduce((total, item) => total + item.quantity, 0) const handleSearch = async (formData: FormData) => { 'use server' @@ -70,7 +68,7 @@ async function Navbar() {
{ - isLoggedIn ? ( + isLoggedIn() ? (
{products.map((product) => ( - + ))}
diff --git a/src/lib/actions/cart.ts b/src/lib/actions/cart.ts index 107cd60..23f1e56 100644 --- a/src/lib/actions/cart.ts +++ b/src/lib/actions/cart.ts @@ -1,38 +1,68 @@ -import { Cart } from "../db/cart_collection"; +'use server' + +import { cookies } from "next/headers"; +import { Cart } from "@/lib/db/cart_collection"; +import { isLoggedIn } from "@/lib/utils/auth"; +import { ObjectId } from "mongodb"; +import { revalidateTag } from "next/cache"; +import { redirect } from "next/navigation"; +import { setQueryParams } from "../utils/url"; const NEXT_PUBLIC_URL = process.env.NEXT_PUBLIC_URL; export async function getCart(): Promise { - const res = await fetch(`${NEXT_PUBLIC_URL}/api/carts`, { - cache: 'no-store', + if (!isLoggedIn()) { + return { items: [], _id: new ObjectId(), userId: new ObjectId(), totalAmount: 0 } + } + const res = await fetch(`${NEXT_PUBLIC_URL}/api/cart`, { + headers: { + Cookie: cookies().toString() + }, + next: { + tags: ['cart'] + } }); - return res.json(); + + return res.json() } export async function addToCart(productId: string, quantity: number) { - const res = await fetch(`${NEXT_PUBLIC_URL}/api/carts`, { + await fetch(`${NEXT_PUBLIC_URL}/api/cart/${productId}`, { method: 'POST', - body: JSON.stringify({ productId, quantity }), - cache: 'no-store', + body: JSON.stringify({ quantity }), + headers: { + Cookie: cookies().toString() + } }); - return res.json(); + + revalidateTag('cart') + + const successQuery = setQueryParams({ type: 'success', title: "Added to Cart", message: "The product has been added to your cart." }); + redirect('/cart?' + successQuery); } export async function updateCartItemQuantity(productId: string, quantity: number) { - const res = await fetch(`${NEXT_PUBLIC_URL}/api/carts`, { + await fetch(`${NEXT_PUBLIC_URL}/api/cart/${productId}`, { method: 'PUT', - body: JSON.stringify({ quantity, productId }), - cache: 'no-store', - + body: JSON.stringify({ quantity }), + headers: { + Cookie: cookies().toString() + } }); - return res.json(); + + revalidateTag('cart') } export async function removeFromCart(productId: string) { - const res = await fetch(`${NEXT_PUBLIC_URL}/api/carts`, { + await fetch(`${NEXT_PUBLIC_URL}/api/cart/${productId}`, { method: 'DELETE', - body: JSON.stringify({ productId }), - cache: 'no-store', + headers: { + Cookie: cookies().toString() + } }); - return res.json(); + + revalidateTag('cart') + + const successQuery = setQueryParams({ type: 'success', title: "Removed from Cart", message: "The product has been removed from your cart." }); + redirect('/cart?' + successQuery); } \ No newline at end of file diff --git a/src/lib/db/cart_collection.ts b/src/lib/db/cart_collection.ts index 2359e8b..be08596 100644 --- a/src/lib/db/cart_collection.ts +++ b/src/lib/db/cart_collection.ts @@ -1,4 +1,4 @@ -import { ObjectId } from "mongodb"; +import { ObjectId, WithId } from "mongodb"; import { z } from "zod"; import { db } from "./client"; @@ -7,17 +7,17 @@ export const CartItemSchema = z.object({ name: z.string(), price: z.number().positive(), quantity: z.number().int().min(1), - thumbnail: z.string().url().optional(), + thumbnail: z.string().url(), }); export const CartSchema = z.object({ - _id: z.instanceof(ObjectId).optional(), userId: z.instanceof(ObjectId), items: z.array(CartItemSchema), totalAmount: z.number().positive(), }); export type CartItem = z.infer; -export type Cart = z.infer; +export type CartForm = z.infer +export type Cart = WithId -export const cartsCollection = db.collection("carts"); +export const cartsCollection = db.collection("carts"); diff --git a/src/lib/db/product_collection.ts b/src/lib/db/product_collection.ts index 34fb8d6..0f8527c 100644 --- a/src/lib/db/product_collection.ts +++ b/src/lib/db/product_collection.ts @@ -1,9 +1,8 @@ -import { ObjectId } from "mongodb"; +import { ObjectId, WithId } from "mongodb"; import { z } from "zod"; import { db } from "./client"; export const ProductSchema = z.object({ - _id: z.instanceof(ObjectId).optional(), name: z.string(), slug: z.string(), description: z.string(), @@ -22,6 +21,7 @@ export const ProductSchema = z.object({ createdAt: z.date().default(new Date()), }); -export type Product = z.infer; +export type ProductForm = z.infer +export type Product = WithId -export const productsCollection = db.collection("products"); +export const productsCollection = db.collection("products"); diff --git a/src/lib/db/seeds.ts b/src/lib/db/seeds.ts index 728406d..bcd6845 100644 --- a/src/lib/db/seeds.ts +++ b/src/lib/db/seeds.ts @@ -1,6 +1,6 @@ import { faker } from "@faker-js/faker"; import slug from 'slug' -import { Product, productsCollection } from "@/lib/db/product_collection"; +import { ProductForm, productsCollection } from "@/lib/db/product_collection"; const BASE_PRODUCTS = 1_000 const TOTAL_PRODUCTS = BASE_PRODUCTS * process.env.TOTAL_PRODUCTS; @@ -10,13 +10,13 @@ async function seed() { const categories = new Array(8).fill(0).map((_, i) => faker.commerce.department()); - let products: Product[] = []; + let products: ProductForm[] = []; for (let i = 1; i <= TOTAL_PRODUCTS; i++) { let productName = faker.commerce.productName(); const randomCategory = categories[Math.floor(Math.random() * categories.length)]; - const newProduct: Product = { + const newProduct: ProductForm = { name: productName, slug: slug(productName), description: faker.commerce.productDescription(), diff --git a/src/lib/db/user_collection.ts b/src/lib/db/user_collection.ts index aad7181..6d1e38e 100644 --- a/src/lib/db/user_collection.ts +++ b/src/lib/db/user_collection.ts @@ -1,15 +1,15 @@ -import { ObjectId } from "mongodb"; +import { WithId } from "mongodb"; import { z } from "zod"; import { db } from "./client"; export const UserSchema = z.object({ - _id: z.instanceof(ObjectId).optional(), name: z.string().min(3, { message: "Name must be at least 3 characters long" }), email: z.string().email({ message: "Invalid email address" }), password: z.string().min(6, { message: "Password must be at least 6 characters long" }), createdAt: z.date().default(new Date()), }); -export type User = z.infer; +export type UserForm = z.infer +export type User = WithId -export const usersCollection = db.collection("users"); +export const usersCollection = db.collection("users"); diff --git a/src/lib/utils/auth.ts b/src/lib/utils/auth.ts new file mode 100644 index 0000000..38541fd --- /dev/null +++ b/src/lib/utils/auth.ts @@ -0,0 +1,5 @@ +import { cookies } from "next/headers" + +export const isLoggedIn = () => { + return cookies().get('token') +} \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts index bc24b8d..e62208b 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { verifyToken } from "./lib/utils/jwt"; +import { cookies } from "next/headers"; function captureCurrentUrl(request: NextRequest) { const headers = new Headers(request.headers) @@ -7,24 +8,24 @@ function captureCurrentUrl(request: NextRequest) { return headers } -async function auth(request: NextRequest) { - const token = request.cookies.get('token') +async function auth(headers: Headers) { + const token = cookies().get('token') if (!token) { throw new Error("Unauthorized") } const payload = await verifyToken(token.value) - const headers = new Headers(request.headers) - headers.set('x-user-id', String(payload.sub)) - return headers + const newHeaders = new Headers(headers) + newHeaders.set('x-user-id', String(payload.sub)) + return newHeaders } export async function middleware(request: NextRequest) { try { let headers = captureCurrentUrl(request) - if (request.nextUrl.pathname.startsWith('/api/carts')) { - headers = await auth(request) + if (request.nextUrl.pathname.startsWith('/api/cart')) { + headers = await auth(headers) } return NextResponse.next({