Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
4c9a146
feat: Implement return management features including return creation …
mateo-agilo Feb 6, 2026
b0be47f
feat: Add guest return creation page and template for order returns
mateo-agilo Feb 9, 2026
5aa2d43
feat: Add guest return tracking page and template for tracking returns
mateo-agilo Feb 9, 2026
24b4e54
fix: Update Tag component styles for improved text visibility
mateo-agilo Feb 10, 2026
d35e5ce
feat: Integrate return reasons into return creation and tracking temp…
mateo-agilo Feb 10, 2026
37425db
Merge branch 'master' into feat/returns
mateo-agilo Feb 13, 2026
438f923
feat: Enhance return functionality with returnable item checks and ne…
mateo-agilo Feb 16, 2026
add4247
feat: implement return item selection and shipping options
mateo-agilo Feb 16, 2026
cdd6bdc
feat: implement guest return order verification and search template
mateo-agilo Feb 16, 2026
bcf6a37
feat: implement return creation and details templates, update return …
mateo-agilo Feb 17, 2026
0691dc1
feat: implement guest return creation and tracking forms, enhance ret…
mateo-agilo Feb 19, 2026
61888a8
feat: refactor return page components to streamline props handling
mateo-agilo Feb 23, 2026
4735633
feat: enhance order retrieval and listing to include return details
mateo-agilo Feb 23, 2026
218a47e
feat: implement OrderStatusTag component for enhanced order status di…
mateo-agilo Feb 23, 2026
e709884
feat: enhance return management with detailed return views and improv…
mateo-agilo Feb 25, 2026
920c6e9
feat: refactor OrderItem component to simplify props and enhance item…
mateo-agilo Feb 25, 2026
59ed69c
feat: enhance order display with clearer order numbering and improved…
mateo-agilo Mar 4, 2026
5a3bbc4
feat: enhance return handling with improved order verification and re…
mateo-agilo Mar 4, 2026
8b34600
feat: enhance returns functionality with improved refund calculations…
mateo-agilo Mar 4, 2026
2af0171
feat: improve refund calculations and update return details display
mateo-agilo Mar 4, 2026
d9834e2
feat: update OrderStatusTag component to improve return status displa…
mateo-agilo Mar 4, 2026
f7dc2bf
feat: improve return coverage logic to accurately track returned item…
mateo-agilo Mar 4, 2026
fb604c9
feat: implement return details page and enhance return handling with …
mateo-agilo Mar 6, 2026
264172d
feat: wrap ReturnDetailsTemplate in Layout component and adjust class…
mateo-agilo Mar 7, 2026
f65987c
feat: enhance guest order fetching with additional query fields and i…
mateo-agilo Mar 9, 2026
396c4db
Merge branch 'master' into feat/returns
mateo-agilo Mar 10, 2026
54b5fab
feat: update email validation in form schemas to use z.email() for co…
mateo-agilo Mar 10, 2026
12b1f89
Merge branch 'feat/returns' of github.com:Agilo/fashion-starter into …
mateo-agilo Mar 10, 2026
c33f90b
feat: add React import to OrderStatusTag component
mateo-agilo Mar 10, 2026
a0645be
feat: update HttpTypes import path for consistency
mateo-agilo Mar 10, 2026
af36891
feat: update calculatedPricesMap initialization and access for better…
mateo-agilo Mar 10, 2026
5cbfd47
feat: simplify currencyCode access in ItemsTemplate component
mateo-agilo Mar 10, 2026
c1e637f
feat: remove unnecessary quantity state and effect in Item component
mateo-agilo Mar 10, 2026
02c4614
feat: update ReturnDetailsTemplate to accept returns prop and simplif…
mateo-agilo Mar 10, 2026
4b61af3
feat: implement guest return creation and tracking functionality
mateo-agilo Mar 10, 2026
aed5abe
feat: unify order fetching logic by replacing fetchAndVerifyGuestOrde…
mateo-agilo Mar 10, 2026
110727c
feat: enhance createReturnRequest validation for items and error hand…
mateo-agilo Mar 10, 2026
5dfc201
feat: ignore canceled returns when calculating returned quantities in…
mateo-agilo Mar 10, 2026
649a4e6
feat: rename GuestReturnsPage to GuestReturnsCreatePage for clarity
mateo-agilo Mar 11, 2026
fa9a9ab
fix(returns): safe optional chaining for location_id extraction
JosipVojkovicAgilo Mar 20, 2026
31639fd
fix(order-item): guard against division by zero in discounted price
JosipVojkovicAgilo Mar 20, 2026
9e6d973
fix(returns): rethrow NEXT_REDIRECT to allow navigation on guest access
JosipVojkovicAgilo Mar 20, 2026
099086a
fix: estimated amount on order return and discount amount on order
Jun 2, 2026
261c864
fix: correct discount calculation in order item and totals
JosipVojkovicAgilo Jun 3, 2026
f1cb3f8
fix: distinguish partially received returns and refunds in ReturnStatus
JosipVojkovicAgilo Jun 3, 2026
e2d91cb
fix: include partially_shipped in OrderStatusTag delivering state
JosipVojkovicAgilo Jun 3, 2026
307b8f7
Merge branch 'master' into feat/returns
anteprimorac Jun 3, 2026
61adf54
fix: encode seed image content as base64 for S3 upload
JosipVojkovicAgilo Jun 8, 2026
630961f
fix: change return statuses to only support requested, received and r…
JosipVojkovicAgilo Jun 9, 2026
c5ac3be
Merge branch 'master' into feat/returns
JosipVojkovicAgilo Oct 6, 2026
4932f26
fix: import Loader from @medusajs/icons package root
JosipVojkovicAgilo Oct 6, 2026
4914e94
fix: expose order returns on store order routes after Medusa 2.21 upg…
JosipVojkovicAgilo Oct 6, 2026
11c7c97
fix: send return reason as reason_id so Medusa stores it
JosipVojkovicAgilo Oct 6, 2026
7ab379c
fix: show Medusa order totals and paid/refunded/net after returns
JosipVojkovicAgilo Oct 7, 2026
36ea071
fix: show Medusa order totals and paid/refunded/net after returns
JosipVojkovicAgilo Oct 7, 2026
adc10ee
fix: show per-unit discount scaled to returned quantity on return det…
JosipVojkovicAgilo Oct 7, 2026
e349ee0
fix: count only received returns in order return tags
JosipVojkovicAgilo Oct 7, 2026
bf982d7
fix: return guest return lookup errors instead of throwing
JosipVojkovicAgilo Oct 7, 2026
d193743
fix: return guest return lookup errors instead of throwing
JosipVojkovicAgilo Oct 7, 2026
d1bd951
fix: avoid hydration mismatch on header account link
JosipVojkovicAgilo Oct 7, 2026
be0eed5
fix: let Medusa resolve the return stock location
JosipVojkovicAgilo Oct 7, 2026
6bed4b1
fix: use one refund calculation across return pages
JosipVojkovicAgilo Oct 7, 2026
e3703cb
fix: calculate return details refund from the return's items
JosipVojkovicAgilo Oct 7, 2026
f37c8e9
fix: handle orders without a cart on return creation pages
JosipVojkovicAgilo Oct 8, 2026
3b7a661
fix: refresh cached orders after creating a return
JosipVojkovicAgilo Oct 8, 2026
833673c
fix: show order prices consistently in the item's tax mode
JosipVojkovicAgilo Oct 8, 2026
e152aa2
fix: show refund pending and net paid correctly after returns
JosipVojkovicAgilo Oct 8, 2026
4480d7a
fix: subtract return shipping from expected refunds
JosipVojkovicAgilo Oct 8, 2026
f1737ed
fix: keep the return confirmation when nothing is left to return
JosipVojkovicAgilo Oct 8, 2026
47cef11
fix: render one header and footer on shop 404 pages
JosipVojkovicAgilo Oct 8, 2026
35fcfec
fix: label refund amounts as including tax
JosipVojkovicAgilo Oct 8, 2026
546fae2
fix: make cart and checkout totals add up
JosipVojkovicAgilo Oct 8, 2026
5b1781e
fix: keep applied promotion codes when adding another
JosipVojkovicAgilo Oct 8, 2026
753e232
fix: show the cart line price in the checkout summary
JosipVojkovicAgilo Oct 8, 2026
6a96ab9
feat: show applied discount codes and why a code wasn't applied
JosipVojkovicAgilo Oct 9, 2026
14d216b
style: show the discount total in red with a line per code
JosipVojkovicAgilo Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion medusa/src/api/middlewares.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { configureStoreSearch, defineMiddlewares } from '@medusajs/framework/http';
import {
allowFields,
configureStoreSearch,
defineMiddlewares,
} from '@medusajs/framework/http';
import { adminProductTypeRoutesMiddlewares } from './store/custom/product-types/middlewares';
import { authenticate } from '@medusajs/framework';

Expand All @@ -12,6 +16,11 @@ export default defineMiddlewares([
}),
],
},
{
method: 'GET',
matcher: '/store/orders*',
middlewares: [allowFields('returns', 'returns.items')],
},
{
method: 'ALL',
matcher: '/store/custom/customer/*',
Expand Down
2 changes: 1 addition & 1 deletion medusa/src/scripts/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async function getImageUrlContent(url: string) {

const arrayBuffer = await response.arrayBuffer();

return Buffer.from(arrayBuffer).toString('binary');
return Buffer.from(arrayBuffer).toString('base64');
}

export default async function seedDemoData({ container }: ExecArgs) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,90 +1,29 @@
import * as React from "react"
import { Metadata } from "next"
import Image from "next/image"
import { HttpTypes } from "@medusajs/types"

import { convertToLocale } from "@lib/util/money"
import { retrieveOrder } from "@lib/data/orders"
import { OrderTotals } from "@modules/order/components/OrderTotals"
import { UiTag } from "@/components/ui/Tag"
import { UiTagList, UiTagListDivider } from "@/components/ui/TagList"
import { OrderItem } from "@modules/order/components/item/OrderItem"
import { Icon } from "@/components/Icon"
import { LocalizedLink } from "@/components/LocalizedLink"
import { getCustomer } from "@lib/data/customer"
import { redirect } from "next/navigation"
import {
calcExpectedRefundAmount,
getOrderReturns,
hasReturnableItems,
type OrderWithReturns,
} from "@lib/util/returns"
import { getDisplayDiscountPerUnit } from "@lib/util/line-item-price"
import { convertToLocale } from "@lib/util/money"
import { OrderStatus } from "@modules/order/components/OrderStatus"
import { LocalizedButtonLink, LocalizedLink } from "@/components/LocalizedLink"
import Image from "next/image"

export const metadata: Metadata = {
title: "Account - Order",
title: "Account - Order Details",
description: "Check your order history",
}

const OrderStatus: React.FC<{ order: HttpTypes.StoreOrder }> = ({ order }) => {
if (order.fulfillment_status === "canceled") {
return (
<UiTagList>
<UiTag iconName="close" isActive className="self-start mt-auto">
Canceled
</UiTag>
</UiTagList>
)
}

if (order.fulfillment_status === "delivered") {
return (
<UiTagList>
<UiTag isActive iconName="package" className="self-start mt-auto">
Packing
</UiTag>
<UiTagListDivider />
<UiTag isActive iconName="truck" className="self-start mt-auto">
Delivering
</UiTag>
<UiTagListDivider />
<UiTag isActive iconName="check" className="self-start mt-auto">
Delivered
</UiTag>
</UiTagList>
)
}

if (
order.fulfillment_status === "shipped" ||
order.fulfillment_status === "partially_delivered"
) {
return (
<UiTagList>
<UiTag isActive iconName="package" className="self-start mt-auto">
Packing
</UiTag>
<UiTagListDivider />
<UiTag isActive iconName="truck" className="self-start mt-auto">
Delivering
</UiTag>
<UiTagListDivider />
<UiTag iconName="check" className="self-start mt-auto">
Delivered
</UiTag>
</UiTagList>
)
}

return (
<UiTagList>
<UiTag isActive iconName="package" className="self-start mt-auto">
Packing
</UiTag>
<UiTagListDivider />
<UiTag iconName="truck" className="self-start mt-auto">
Delivering
</UiTag>
<UiTagListDivider />
<UiTag iconName="check" className="self-start mt-auto">
Delivered
</UiTag>
</UiTagList>
)
}

export default async function AccountOrderPage({
params,
}: {
Expand All @@ -97,12 +36,15 @@ export default async function AccountOrderPage({
}

const { orderId } = await params
const order = await retrieveOrder(orderId)
const order = (await retrieveOrder(orderId)) as OrderWithReturns

const hasReturnableItemsInOrder = hasReturnableItems(order)
const orderReturns = getOrderReturns(order)

return (
<>
<h1 className="text-md md:text-lg mb-8 md:mb-16">
Order: {order.display_id}
<h1 className="text-md md:text-lg mb-8 md:mb-13">
Order #{order.display_id}
</h1>
<div className="flex flex-col gap-6">
<div className="rounded-xs border border-grayscale-200 flex flex-wrap justify-between p-4">
Expand All @@ -114,10 +56,8 @@ export default async function AccountOrderPage({
<p>{new Date(order.created_at).toLocaleDateString()}</p>
</div>
</div>
<div className="rounded-xs border border-grayscale-200 p-4">
<div className="flex flex-wrap gap-x-10 gap-y-8 justify-between items-end w-full">
<OrderStatus order={order} />
</div>
<div className="rounded-xs border border-grayscale-200 p-4 flex flex-wrap gap-x-10 gap-y-8 justify-between items-end w-full">
<OrderStatus order={order} />
</div>
<div className="flex max-sm:flex-col gap-x-4 gap-y-6 md:flex-col lg:flex-row">
<div className="flex-1 overflow-hidden rounded-xs border border-grayscale-200 p-4">
Expand Down Expand Up @@ -197,71 +137,109 @@ export default async function AccountOrderPage({
</div>
<div className="rounded-xs border border-grayscale-200 p-4 flex flex-col gap-6">
{order.items?.map((item) => (
<div
<OrderItem
key={item.id}
thumbnail={item.thumbnail || ""}
product_handle={item.product_handle || ""}
product_title={item.product_title || ""}
title={item.title || ""}
quantity={item.quantity}
variant={item.variant}
discount_total={getDisplayDiscountPerUnit(item) * item.quantity}
unit_price={item.unit_price || 0}
currencyCode={order.currency_code}
className="flex gap-x-4 sm:gap-x-8 gap-y-6 pb-6 border-b border-grayscale-100 last:border-0 last:pb-0"
>
{item.thumbnail && (
<LocalizedLink
href={`/products/${item.product_handle}`}
className="max-w-25 sm:max-w-37 aspect-[3/4] w-full relative overflow-hidden"
/>
))}
</div>
<div className="rounded-xs border border-grayscale-200 p-4 flex max-sm:flex-col gap-y-4 gap-x-10 md:flex-wrap justify-between">
<div className="flex items-center self-baseline gap-4">
<Icon name="credit-card" />
<div>
<p className="text-grayscale-500">Payment</p>
</div>
</div>
<OrderTotals order={order} />
</div>
<div className="rounded-xs border border-grayscale-200 p-4">
<div className="flex gap-4 items-center mb-6">
<Icon name="undo" />
<p className="text-grayscale-500">Returns</p>
</div>
{orderReturns && orderReturns.length > 0 && (
<div className="flex flex-col gap-6 mb-4">
{orderReturns.map((eachReturn, index) => (
<div
key={eachReturn.id || `return-${index}`}
className="flex flex-col gap-4 p-4 rounded-xs border border-grayscale-100"
>
<Image
src={item.thumbnail}
alt={item.title}
fill
className="object-cover"
/>
</LocalizedLink>
)}
<div className="flex flex-col flex-1">
<p className="mb-2 sm:text-md">
<LocalizedLink href={`/products/${item.product_handle}`}>
{item.product_title}
</LocalizedLink>
</p>
<div className="text-xs flex flex-col flex-1">
<div>
{item.variant?.options?.map((option) => (
<p className="mb-1" key={option.id}>
<span className="text-grayscale-500 mr-2">
{option.option?.title}:
</span>
{option.value}
</p>
))}
</div>
<div className="mt-auto flex max-xs:flex-col gap-x-10 gap-y-6.5 xs:items-center justify-between relative">
<div className="xs:self-end sm:mb-1">
<p>
<span className="text-grayscale-500 mr-2">
Quantity:
</span>
{item.quantity}
<div className="flex items-center justify-between">
<div>
{eachReturn.display_id && (
<p className="font-medium">
Return #{eachReturn.display_id}
</p>
)}
<p className="text-xs text-grayscale-500">
{new Date(eachReturn.created_at).toLocaleDateString()}
</p>
</div>
<div className="sm:text-md">
<p>
<div className="text-right">
<p className="font-medium">
{convertToLocale({
currency_code: order.currency_code,
amount: item.total,
currency_code: eachReturn.currency_code,
amount: calcExpectedRefundAmount(eachReturn),
})}
</p>
<p className="text-xs text-grayscale-500">incl. tax</p>
</div>
</div>
<div className="flex overflow-x-auto gap-3">
{eachReturn.items
?.filter((item) => item.item.thumbnail)
.map((item) => (
<LocalizedLink
key={item.id}
href={`/products/${item.item.product_handle}`}
className="shrink-0 w-19 aspect-[3/4] rounded-2xs relative overflow-hidden"
>
<Image
src={item.item.thumbnail!}
alt={item.item.title}
fill
className="object-cover"
/>
</LocalizedLink>
))}
</div>
{eachReturn.id && (
<LocalizedButtonLink
href={`/account/my-orders/${order.id}/return/${eachReturn.id}`}
variant="outline"
size="sm"
>
View Details
</LocalizedButtonLink>
)}
</div>
</div>
</div>
))}
</div>
<div className="rounded-xs border border-grayscale-200 p-4 flex max-sm:flex-col gap-y-4 gap-x-10 md:flex-wrap justify-between">
<div className="flex items-center self-baseline gap-4">
<Icon name="credit-card" />
<div>
<p className="text-grayscale-500">Payment</p>
))}
</div>
)}
<div className="flex justify-between items-center gap-8">
<p className="text-xs">
Returns are available for the first 30 days after receiving your
items.
</p>
{hasReturnableItemsInOrder && (
<LocalizedButtonLink
href={`/account/my-orders/${order.id}/return`}
variant="outline"
size="sm"
>
Start Return
</LocalizedButtonLink>
)}
</div>
<OrderTotals order={order} />
</div>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Metadata } from "next"
import { notFound } from "next/navigation"

import { getCustomer } from "@lib/data/customer"
import { ReturnDetailsTemplate } from "@modules/returns/templates/ReturnDetailsTemplate"
import { getOrderReturns, type OrderWithReturns } from "@lib/util/returns"
import { fetchAndVerifyOrder } from "@lib/data/returns"

export const metadata: Metadata = {
title: "Account - Return Details",
description: "View your return request details",
}

export default async function AccountReturnDetailsPage({
params,
}: {
params: Promise<{ orderId: string; returnId: string }>
}) {
const customer = await getCustomer().catch(() => null)

const { orderId, returnId } = await params

if (!customer || !orderId || !returnId) {
notFound()
}

const order = (await fetchAndVerifyOrder(orderId, customer.email)) as
| (OrderWithReturns & { cart: { id: string } })
| null

if (!order) {
notFound()
}

const orderReturns = getOrderReturns(order)

const returnEntity = orderReturns?.find((r) => r.id === returnId)

if (!returnEntity) {
notFound()
}

return <ReturnDetailsTemplate order={order} returns={[returnEntity]} />
}
Loading
Loading