Skip to content

Commit a0fca16

Browse files
authored
feat: Add Draft Order plugin (medusajs#13291)
* feat: Add draft order plugin * version draft order plugin * update readme * chore: Update scripts * Create purple-dolls-cheer.md * port over latest changes * chore: Make package public
1 parent 7c96bc4 commit a0fca16

File tree

93 files changed

+14526
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+14526
-4
lines changed

.changeset/purple-dolls-cheer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@medusajs/draft-order": patch
3+
---
4+
5+
feat: Add Draft Order plugin

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"packages/medusa-test-utils",
88
"packages/modules/*",
99
"packages/modules/providers/*",
10+
"packages/plugins/*",
1011
"packages/core/*",
1112
"packages/framework/*",
1213
"packages/cli/*",
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<p align="center">
2+
<a href="https://www.medusajs.com">
3+
<picture>
4+
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/59018053/229103275-b5e482bb-4601-46e6-8142-244f531cebdb.svg">
5+
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/59018053/229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg">
6+
<img alt="Medusa logo" src="https://user-images.githubusercontent.com/59018053/229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg">
7+
</picture>
8+
</a>
9+
</p>
10+
<h1 align="center">
11+
Draft Order Plugin
12+
</h1>
13+
14+
<h4 align="center">
15+
<a href="https://docs.medusajs.com">Documentation</a> |
16+
<a href="https://www.medusajs.com">Website</a>
17+
</h4>
18+
19+
<p align="center">
20+
Create and manage draft orders on behalf of customers in Medusa
21+
</p>
22+
<p align="center">
23+
<a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md">
24+
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" alt="PRs welcome!" />
25+
</a>
26+
<a href="https://www.producthunt.com/posts/medusa"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-%23DA552E" alt="Product Hunt"></a>
27+
<a href="https://discord.gg/xpCwq3Kfn8">
28+
<img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
29+
</a>
30+
<a href="https://twitter.com/intent/follow?screen_name=medusajs">
31+
<img src="https://img.shields.io/twitter/follow/medusajs.svg?label=Follow%20@medusajs" alt="Follow @medusajs" />
32+
</a>
33+
</p>
34+
35+
## Overview
36+
37+
The Draft Order Plugin enables admin users to create and manage orders on behalf of customers. This is particularly useful for customer support scenarios or when customers place orders offline.
38+
39+
## Features
40+
41+
- **Create draft orders** from the Medusa Admin dashboard
42+
- **Manage items** in draft orders (add, update, remove)
43+
- **Add shipping methods** to draft orders
44+
- **Associate customers** with draft orders
45+
- **Convert draft orders** to regular orders for purchase completion
46+
47+
48+
## Installation
49+
50+
1. Install the Draft Order plugin
51+
```
52+
yarn add @medusajs/draft-order
53+
```
54+
2. Configure the plugin in your medusa-config.ts
55+
```
56+
module.exports = defineConfig({
57+
projectConfig: {
58+
...
59+
},
60+
plugins: [
61+
{
62+
resolve: "@medusajs/draft-order",
63+
options: {},
64+
},
65+
],
66+
})
67+
```
68+
3. Start your server
69+
70+
## Requirements
71+
- Medusa application version >= 2.4.0
72+
73+
## Support
74+
75+
## Community & Contributions
76+
77+
The community and core team are available in [GitHub Discussions](https://github.com/medusajs/medusa/discussions), where you can ask for support, discuss roadmap, and share ideas.
78+
79+
Join our [Discord server](https://discord.com/invite/medusajs) to meet other community members.
80+
81+
## Other channels
82+
83+
- [GitHub Issues](https://github.com/medusajs/medusa/issues)
84+
- [Twitter](https://twitter.com/medusajs)
85+
- [LinkedIn](https://www.linkedin.com/company/medusajs)
86+
- [Medusa Blog](https://medusajs.com/blog/)
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
{
2+
"name": "@medusajs/draft-order",
3+
"version": "2.9.0",
4+
"description": "A starter for Medusa plugins.",
5+
"author": "Medusa (https://medusajs.com)",
6+
"license": "MIT",
7+
"files": [
8+
".medusa/server"
9+
],
10+
"exports": {
11+
"./package.json": "./package.json",
12+
"./workflows": "./.medusa/server/src/workflows/index.js",
13+
"./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
14+
"./modules/*": "./.medusa/server/src/modules/*/index.js",
15+
"./providers/*": "./.medusa/server/src/providers/*/index.js",
16+
"./*": "./.medusa/server/src/*.js",
17+
"./admin": {
18+
"import": "./.medusa/server/src/admin/index.mjs",
19+
"require": "./.medusa/server/src/admin/index.js",
20+
"default": "./.medusa/server/src/admin/index.js"
21+
}
22+
},
23+
"keywords": [
24+
"medusa",
25+
"plugin",
26+
"medusa-plugin-other",
27+
"medusa-plugin",
28+
"medusa-v2"
29+
],
30+
"scripts": {
31+
"build": "medusa plugin:build",
32+
"dev": "medusa plugin:develop",
33+
"prepare": "cross-env NODE_ENV=production yarn run build",
34+
"link:watch": "medusa plugin:publish && medusa plugin:develop"
35+
},
36+
"dependencies": {
37+
"@ariakit/react": "^0.4.15",
38+
"@hookform/resolvers": "3.4.2",
39+
"@medusajs/js-sdk": "2.9.0",
40+
"@tanstack/react-query": "5.64.2",
41+
"@uiw/react-json-view": "^2.0.0-alpha.17",
42+
"date-fns": "^3.6.0",
43+
"match-sorter": "^6.3.4",
44+
"radix-ui": "1.1.2",
45+
"react-hook-form": "7.49.1"
46+
},
47+
"devDependencies": {
48+
"@medusajs/admin-sdk": "2.9.0",
49+
"@medusajs/cli": "2.9.0",
50+
"@medusajs/framework": "2.9.0",
51+
"@medusajs/icons": "2.9.0",
52+
"@medusajs/medusa": "2.9.0",
53+
"@medusajs/test-utils": "2.9.0",
54+
"@medusajs/types": "2.9.0",
55+
"@medusajs/ui": "4.0.19",
56+
"@medusajs/ui-preset": "2.9.0",
57+
"@mikro-orm/cli": "6.4.3",
58+
"@mikro-orm/core": "6.4.3",
59+
"@mikro-orm/knex": "6.4.3",
60+
"@mikro-orm/migrations": "6.4.3",
61+
"@mikro-orm/postgresql": "6.4.3",
62+
"@swc/core": "1.5.7",
63+
"@types/lodash": "^4.17.15",
64+
"@types/node": "^20.0.0",
65+
"@types/react": "^18.3.2",
66+
"@types/react-dom": "^18.2.25",
67+
"awilix": "^8.0.1",
68+
"lodash": "^4.17.21",
69+
"pg": "^8.13.0",
70+
"react": "^18.2.0",
71+
"react-dom": "^18.2.0",
72+
"react-router-dom": "6.20.1",
73+
"ts-node": "^10.9.2",
74+
"tsup": "^8.4.0",
75+
"typescript": "^5.6.2",
76+
"vite": "^5.2.11",
77+
"yalc": "^1.0.0-pre.53"
78+
},
79+
"peerDependencies": {
80+
"@medusajs/admin-sdk": "2.9.0",
81+
"@medusajs/cli": "2.9.0",
82+
"@medusajs/framework": "2.9.0",
83+
"@medusajs/icons": "2.9.0",
84+
"@medusajs/medusa": "2.9.0",
85+
"@medusajs/test-utils": "2.9.0",
86+
"@medusajs/ui": "4.0.19",
87+
"@mikro-orm/cli": "6.4.3",
88+
"@mikro-orm/core": "6.4.3",
89+
"@mikro-orm/knex": "6.4.3",
90+
"@mikro-orm/migrations": "6.4.3",
91+
"@mikro-orm/postgresql": "6.4.3",
92+
"awilix": "^8.0.1",
93+
"lodash": "^4.17.21",
94+
"pg": "^8.13.0",
95+
"react-router-dom": "6.20.1"
96+
},
97+
"engines": {
98+
"node": ">=20"
99+
},
100+
"packageManager": "[email protected]"
101+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { DropdownMenu, IconButton, clx } from "@medusajs/ui"
2+
3+
import { EllipsisHorizontal } from "@medusajs/icons"
4+
import { PropsWithChildren, ReactNode } from "react"
5+
import { Link } from "react-router-dom"
6+
import { ConditionalTooltip } from "./conditional-tooltip"
7+
8+
export type Action = {
9+
icon: ReactNode
10+
label: string
11+
disabled?: boolean
12+
/**
13+
* Optional tooltip to display when a disabled action is hovered.
14+
*/
15+
disabledTooltip?: string | ReactNode
16+
} & (
17+
| {
18+
to: string
19+
onClick?: never
20+
}
21+
| {
22+
onClick: () => void
23+
to?: never
24+
}
25+
)
26+
27+
export type ActionGroup = {
28+
actions: Action[]
29+
}
30+
31+
type ActionMenuProps = PropsWithChildren<{
32+
groups: ActionGroup[]
33+
variant?: "transparent" | "primary"
34+
}>
35+
36+
export const ActionMenu = ({
37+
groups,
38+
variant = "transparent",
39+
children,
40+
}: ActionMenuProps) => {
41+
const inner = children ?? (
42+
<IconButton size="small" variant={variant}>
43+
<EllipsisHorizontal />
44+
</IconButton>
45+
)
46+
47+
return (
48+
<DropdownMenu>
49+
<DropdownMenu.Trigger asChild>{inner}</DropdownMenu.Trigger>
50+
<DropdownMenu.Content>
51+
{groups.map((group, index) => {
52+
if (!group.actions.length) {
53+
return null
54+
}
55+
56+
const isLast = index === groups.length - 1
57+
58+
return (
59+
<DropdownMenu.Group key={index}>
60+
{group.actions.map((action, index) => {
61+
const Wrapper = action.disabledTooltip
62+
? ({ children }: { children: ReactNode }) => (
63+
<ConditionalTooltip
64+
showTooltip={action.disabled}
65+
content={action.disabledTooltip}
66+
side="right"
67+
>
68+
<div>{children}</div>
69+
</ConditionalTooltip>
70+
)
71+
: "div"
72+
73+
if (action.onClick) {
74+
return (
75+
<Wrapper key={index}>
76+
<DropdownMenu.Item
77+
disabled={action.disabled}
78+
onClick={(e) => {
79+
e.stopPropagation()
80+
action.onClick()
81+
}}
82+
className={clx(
83+
"[&_svg]:text-ui-fg-subtle flex items-center gap-x-2",
84+
{
85+
"[&_svg]:text-ui-fg-disabled": action.disabled,
86+
}
87+
)}
88+
>
89+
{action.icon}
90+
<span>{action.label}</span>
91+
</DropdownMenu.Item>
92+
</Wrapper>
93+
)
94+
}
95+
96+
return (
97+
<Wrapper key={index}>
98+
<DropdownMenu.Item
99+
className={clx(
100+
"[&_svg]:text-ui-fg-subtle flex items-center gap-x-2",
101+
{
102+
"[&_svg]:text-ui-fg-disabled": action.disabled,
103+
}
104+
)}
105+
asChild
106+
disabled={action.disabled}
107+
>
108+
<Link to={action.to} onClick={(e) => e.stopPropagation()}>
109+
{action.icon}
110+
<span>{action.label}</span>
111+
</Link>
112+
</DropdownMenu.Item>
113+
</Wrapper>
114+
)
115+
})}
116+
{!isLast && <DropdownMenu.Separator />}
117+
</DropdownMenu.Group>
118+
)
119+
})}
120+
</DropdownMenu.Content>
121+
</DropdownMenu>
122+
)
123+
}

0 commit comments

Comments
 (0)