diff --git a/src/app/[countryCode]/(main)/account/@dashboard/orders/page.tsx b/src/app/[countryCode]/(main)/account/@dashboard/orders/page.tsx index 99a3abbdf..5d65e32bd 100644 --- a/src/app/[countryCode]/(main)/account/@dashboard/orders/page.tsx +++ b/src/app/[countryCode]/(main)/account/@dashboard/orders/page.tsx @@ -3,6 +3,8 @@ import { Metadata } from "next" import OrderOverview from "@modules/account/components/order-overview" import { notFound } from "next/navigation" import { listOrders } from "@lib/data/orders" +import Divider from "@modules/common/components/divider" +import TransferRequestForm from "@modules/account/components/transfer-request-form" export const metadata: Metadata = { title: "Orders", @@ -27,6 +29,8 @@ export default async function Orders() {
+ +
) diff --git a/src/app/[countryCode]/(main)/order/confirmed/[id]/loading.tsx b/src/app/[countryCode]/(main)/order/[id]/confirmed/loading.tsx similarity index 100% rename from src/app/[countryCode]/(main)/order/confirmed/[id]/loading.tsx rename to src/app/[countryCode]/(main)/order/[id]/confirmed/loading.tsx diff --git a/src/app/[countryCode]/(main)/order/confirmed/[id]/page.tsx b/src/app/[countryCode]/(main)/order/[id]/confirmed/page.tsx similarity index 100% rename from src/app/[countryCode]/(main)/order/confirmed/[id]/page.tsx rename to src/app/[countryCode]/(main)/order/[id]/confirmed/page.tsx diff --git a/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/accept/page.tsx b/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/accept/page.tsx new file mode 100644 index 000000000..943248d1a --- /dev/null +++ b/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/accept/page.tsx @@ -0,0 +1,41 @@ +import { acceptTransferRequest } from "@lib/data/orders" +import { Heading, Text } from "@medusajs/ui" +import TransferImage from "@modules/order/components/transfer-image" + +export default async function TransferPage({ + params, +}: { + params: { id: string; token: string } +}) { + const { id, token } = params + + const { success, error } = await acceptTransferRequest(id, token) + + return ( +
+ +
+ {success && ( + <> + + Order transfered! + + + Order {id} has been successfully transfered to the new owner. + + + )} + {!success && ( + <> + + There was an error accepting the transfer. Please try again. + + {error && ( + Error message: {error} + )} + + )} +
+
+ ) +} diff --git a/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/decline/page.tsx b/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/decline/page.tsx new file mode 100644 index 000000000..09b5f99e1 --- /dev/null +++ b/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/decline/page.tsx @@ -0,0 +1,41 @@ +import { declineTransferRequest } from "@lib/data/orders" +import { Heading, Text } from "@medusajs/ui" +import TransferImage from "@modules/order/components/transfer-image" + +export default async function TransferPage({ + params, +}: { + params: { id: string; token: string } +}) { + const { id, token } = params + + const { success, error } = await declineTransferRequest(id, token) + + return ( +
+ +
+ {success && ( + <> + + Order transfer declined! + + + Transfer of order {id} has been successfully declined. + + + )} + {!success && ( + <> + + There was an error declining the transfer. Please try again. + + {error && ( + Error message: {error} + )} + + )} +
+
+ ) +} diff --git a/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/page.tsx b/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/page.tsx new file mode 100644 index 000000000..f6d440649 --- /dev/null +++ b/src/app/[countryCode]/(main)/order/[id]/transfer/[token]/page.tsx @@ -0,0 +1,38 @@ +import { Heading, Text } from "@medusajs/ui" +import TransferActions from "@modules/order/components/transfer-actions" +import TransferImage from "@modules/order/components/transfer-image" + +export default async function TransferPage({ + params, +}: { + params: { id: string; token: string } +}) { + const { id, token } = params + + return ( +
+ +
+ + Transfer request for order {id} + + + You've received a request to transfer ownership of your order ({id}). + If you agree to this request, you can approve the transfer by clicking + the button below. + +
+ + If you accept, the new owner will take over all responsibilities and + permissions associated with this order. + + + If you do not recognize this request or wish to retain ownership, no + further action is required. + +
+ +
+
+ ) +} diff --git a/src/lib/data/cart.ts b/src/lib/data/cart.ts index 4f69f09af..7a4362d93 100644 --- a/src/lib/data/cart.ts +++ b/src/lib/data/cart.ts @@ -371,7 +371,7 @@ export async function placeOrder() { const countryCode = cartRes.order.shipping_address?.country_code?.toLowerCase() removeCartId() - redirect(`/${countryCode}/order/confirmed/${cartRes?.order.id}`) + redirect(`/${countryCode}/order/${cartRes?.order.id}/confirmed`) } return cartRes.cart diff --git a/src/lib/data/orders.ts b/src/lib/data/orders.ts index 37c91904a..94c788375 100644 --- a/src/lib/data/orders.ts +++ b/src/lib/data/orders.ts @@ -4,6 +4,7 @@ import { sdk } from "@lib/config" import medusaError from "@lib/util/medusa-error" import { cache } from "react" import { getAuthHeaders } from "./cookies" +import { HttpTypes } from "@medusajs/types" export const retrieveOrder = cache(async function (id: string) { return sdk.store.order @@ -25,3 +26,54 @@ export const listOrders = cache(async function ( .then(({ orders }) => orders) .catch((err) => medusaError(err)) }) + +export const createTransferRequest = async ( + state: { + success: boolean + error: string | null + order: HttpTypes.StoreOrder | null + }, + formData: FormData +): Promise<{ + success: boolean + error: string | null + order: HttpTypes.StoreOrder | null +}> => { + const id = formData.get("order_id") as string + + if (!id) { + return { success: false, error: "Order ID is required", order: null } + } + + const headers = getAuthHeaders() + + return await sdk.store.order + .requestTransfer( + id, + {}, + { + fields: "id, email", + }, + headers + ) + .then(({ order }) => ({ success: true, error: null, order })) + .catch((err) => ({ success: false, error: err.message, order: null })) +} + +export const acceptTransferRequest = async (id: string, token: string) => { + const headers = getAuthHeaders() + + return await sdk.store.order + .acceptTransfer(id, { token }, {}, headers) + .then(({ order }) => ({ success: true, error: null, order })) + .catch((err) => ({ success: false, error: err.message, order: null })) +} + +export const declineTransferRequest = async (id: string, token: string) => { + const headers = getAuthHeaders() + + return await sdk.store.order + .declineTransfer(id, { token }, {}, headers) + .then(({ order }) => ({ success: true, error: null, order })) + .catch((err) => ({ success: false, error: err.message, order: null })) +} diff --git a/src/modules/account/components/transfer-request-form/index.tsx b/src/modules/account/components/transfer-request-form/index.tsx new file mode 100644 index 000000000..66af02cda --- /dev/null +++ b/src/modules/account/components/transfer-request-form/index.tsx @@ -0,0 +1,81 @@ +"use client" + +import { useFormState } from "react-dom" +import { createTransferRequest } from "@lib/data/orders" +import { Text, Heading, Input, Button, IconButton, Toaster } from "@medusajs/ui" +import { SubmitButton } from "@modules/checkout/components/submit-button" +import { CheckCircleMiniSolid, XCircleSolid } from "@medusajs/icons" +import { useEffect, useState } from "react" + +export default function TransferRequestForm() { + const [showSuccess, setShowSuccess] = useState(false) + + const [state, formAction] = useFormState(createTransferRequest, { + success: false, + error: null, + order: null, + }) + + useEffect(() => { + if (state.success && state.order) { + setShowSuccess(true) + } + }, [state.success, state.order]) + + return ( +
+
+
+ + Order transfers + + + Can't find the order you are looking for? +
Connect an order to your account. +
+
+
+
+ + + Request transfer + +
+
+
+ {!state.success && state.error && ( + + {state.error} + + )} + {showSuccess && ( +
+
+ +
+ + Transfer for order {state.order?.id} requested + + + Transfer request email sent to {state.order?.email} + +
+
+ setShowSuccess(false)} + > + + +
+ )} +
+ ) +} diff --git a/src/modules/order/components/transfer-actions/index.tsx b/src/modules/order/components/transfer-actions/index.tsx new file mode 100644 index 000000000..e6148a848 --- /dev/null +++ b/src/modules/order/components/transfer-actions/index.tsx @@ -0,0 +1,81 @@ +"use client" + +import { acceptTransferRequest, declineTransferRequest } from "@lib/data/orders" +import { Button, Text } from "@medusajs/ui" +import { useState } from "react" + +type TransferStatus = "pending" | "success" | "error" + +const TransferActions = ({ id, token }: { id: string; token: string }) => { + const [errorMessage, setErrorMessage] = useState(null) + const [status, setStatus] = useState<{ + accept: TransferStatus | null + decline: TransferStatus | null + } | null>({ + accept: null, + decline: null, + }) + + const acceptTransfer = async () => { + setStatus({ accept: "pending", decline: null }) + setErrorMessage(null) + + const { success, error } = await acceptTransferRequest(id, token) + + if (error) setErrorMessage(error) + setStatus({ accept: success ? "success" : "error", decline: null }) + } + + const declineTransfer = async () => { + setStatus({ accept: null, decline: "pending" }) + setErrorMessage(null) + + const { success, error } = await declineTransferRequest(id, token) + + if (error) setErrorMessage(error) + setStatus({ accept: null, decline: success ? "success" : "error" }) + } + + return ( +
+ {status?.accept === "success" && ( + + Order transferred successfully! + + )} + {status?.decline === "success" && ( + + Order transfer declined successfully! + + )} + {status?.accept !== "success" && status?.decline !== "success" && ( +
+ + +
+ )} + {errorMessage && {errorMessage}} +
+ ) +} + +export default TransferActions diff --git a/src/modules/order/components/transfer-image/index.tsx b/src/modules/order/components/transfer-image/index.tsx new file mode 100644 index 000000000..aaaba5bea --- /dev/null +++ b/src/modules/order/components/transfer-image/index.tsx @@ -0,0 +1,275 @@ +import { SVGProps } from "react" + +const TransferImage = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +) + +export default TransferImage