quickbooks-medusa-plugin 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/admin/i18n/index.js +1 -0
- package/.medusa/server/src/admin/index.js +5 -1832
- package/.medusa/server/src/admin/index.mjs +3 -2
- package/.medusa/server/src/admin/routes/settings/quickbooks/page.js +116 -0
- package/package.json +5 -4
- package/src/admin/README.md +31 -0
- package/src/admin/i18n/README.md +58 -0
- package/src/admin/i18n/index.ts +1 -0
- package/src/admin/index.ts +7 -0
- package/{.medusa/server/src → src}/admin/routes/settings/quickbooks/page.tsx +5 -2
- package/src/admin/tsconfig.json +30 -0
- package/src/admin/vite-env.d.ts +1 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { defineRouteConfig } from "@medusajs/admin-sdk";
|
|
3
3
|
import { toast, Text, Container, Heading, Badge, Button, Label, Input, Switch } from "@medusajs/ui";
|
|
4
|
+
import { CurrencyDollar } from "@medusajs/icons";
|
|
4
5
|
import React, { useState, useEffect } from "react";
|
|
5
6
|
var isCheckBoxInput = (element) => element.type === "checkbox";
|
|
6
7
|
var isDateObject = (value) => value instanceof Date;
|
|
@@ -1567,7 +1568,7 @@ function useForm(props = {}) {
|
|
|
1567
1568
|
return _formControl.current;
|
|
1568
1569
|
}
|
|
1569
1570
|
const QuickBooksSettingsPage = () => {
|
|
1570
|
-
const [
|
|
1571
|
+
const [_, setSettings] = useState(null);
|
|
1571
1572
|
const [connection, setConnection] = useState(null);
|
|
1572
1573
|
const [isLoading, setIsLoading] = useState(true);
|
|
1573
1574
|
const [isSaving, setIsSaving] = useState(false);
|
|
@@ -1789,7 +1790,7 @@ const QuickBooksSettingsPage = () => {
|
|
|
1789
1790
|
};
|
|
1790
1791
|
const config = defineRouteConfig({
|
|
1791
1792
|
label: "QuickBooks",
|
|
1792
|
-
icon:
|
|
1793
|
+
icon: CurrencyDollar
|
|
1793
1794
|
});
|
|
1794
1795
|
const i18nTranslations0 = {};
|
|
1795
1796
|
const widgetModule = { widgets: [] };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { defineRouteConfig } from "@medusajs/admin-sdk";
|
|
3
|
+
import { Container, Heading, Text, Input, Button, Label, Switch, toast, Badge } from "@medusajs/ui";
|
|
4
|
+
import { CurrencyDollar } from "@medusajs/icons";
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
import { useForm, Controller } from "react-hook-form";
|
|
7
|
+
const QuickBooksSettingsPage = () => {
|
|
8
|
+
// defaults to null
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
10
|
+
const [_, setSettings] = useState(null);
|
|
11
|
+
const [connection, setConnection] = useState(null);
|
|
12
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
13
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
14
|
+
const { control, handleSubmit, reset } = useForm({
|
|
15
|
+
defaultValues: {
|
|
16
|
+
auto_sync_customers: false,
|
|
17
|
+
auto_sync_products: false,
|
|
18
|
+
auto_sync_orders: false,
|
|
19
|
+
reverse_sync_customers: false,
|
|
20
|
+
reverse_sync_products: false,
|
|
21
|
+
reverse_sync_orders: false,
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
fetchData();
|
|
26
|
+
}, []);
|
|
27
|
+
const fetchData = async () => {
|
|
28
|
+
try {
|
|
29
|
+
setIsLoading(true);
|
|
30
|
+
// Fetch connection status
|
|
31
|
+
const connRes = await fetch("/admin/quickbooks/connection");
|
|
32
|
+
const connData = await connRes.json();
|
|
33
|
+
setConnection(connData);
|
|
34
|
+
// Fetch settings
|
|
35
|
+
const setRes = await fetch("/admin/quickbooks/settings");
|
|
36
|
+
const setData = await setRes.json();
|
|
37
|
+
if (setData.settings) {
|
|
38
|
+
setSettings(setData.settings);
|
|
39
|
+
reset(setData.settings);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
toast.error("Failed to load QuickBooks data");
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
setIsLoading(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const onSubmit = async (data) => {
|
|
50
|
+
try {
|
|
51
|
+
setIsSaving(true);
|
|
52
|
+
const res = await fetch("/admin/quickbooks/settings", {
|
|
53
|
+
method: "PUT",
|
|
54
|
+
headers: { "Content-Type": "application/json" },
|
|
55
|
+
body: JSON.stringify(data)
|
|
56
|
+
});
|
|
57
|
+
if (res.ok) {
|
|
58
|
+
toast.success("Settings saved successfully");
|
|
59
|
+
const updated = await res.json();
|
|
60
|
+
setSettings(updated.settings);
|
|
61
|
+
reset(updated.settings);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
throw new Error("Failed to save");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
toast.error("Failed to save settings");
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
setIsSaving(false);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const handleConnect = async () => {
|
|
75
|
+
try {
|
|
76
|
+
const res = await fetch("/admin/quickbooks/connect");
|
|
77
|
+
const data = await res.json();
|
|
78
|
+
if (data.url) {
|
|
79
|
+
window.location.href = data.url;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
toast.error("Failed to get authorization URL");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
toast.error("Connection error");
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const handleDisconnect = async () => {
|
|
90
|
+
if (!confirm("Are you sure you want to disconnect?"))
|
|
91
|
+
return;
|
|
92
|
+
try {
|
|
93
|
+
const res = await fetch("/admin/quickbooks/disconnect", {
|
|
94
|
+
method: "POST"
|
|
95
|
+
});
|
|
96
|
+
if (res.ok) {
|
|
97
|
+
toast.success("Disconnected from QuickBooks");
|
|
98
|
+
fetchData();
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
toast.error("Failed to disconnect");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
toast.error("Disconnection error");
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
if (isLoading)
|
|
109
|
+
return _jsx(Text, { children: "Loading..." });
|
|
110
|
+
return (_jsxs(Container, { className: "p-8 flex flex-col gap-y-8", children: [_jsxs("div", { children: [_jsx(Heading, { level: "h1", children: "QuickBooks Integration" }), _jsx(Text, { className: "text-ui-fg-subtle", children: "Manage your connection and synchronization settings." })] }), _jsx("div", { className: "flex flex-col gap-y-4 p-4 border rounded-lg bg-ui-bg-base", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex flex-col gap-y-1", children: [_jsx(Heading, { level: "h2", children: "Connection Status" }), _jsxs("div", { className: "flex items-center gap-x-2", children: [_jsx(Text, { children: "Status:" }), connection?.is_connected ? (_jsx(Badge, { color: "green", children: "Connected" })) : (_jsx(Badge, { color: "red", children: "Disconnected" }))] }), connection?.is_connected && (_jsxs(Text, { className: "text-ui-fg-subtle text-small", children: ["Connected to: ", connection.company_name, " (Realm ID: ", connection.realm_id, ")"] }))] }), _jsx("div", { children: connection?.is_connected ? (_jsx(Button, { variant: "danger", onClick: handleDisconnect, children: "Disconnect" })) : (_jsx(Button, { onClick: handleConnect, children: "Connect to QuickBooks" })) })] }) }), _jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "flex flex-col gap-y-6", children: [_jsxs("div", { className: "flex flex-col gap-y-4", children: [_jsx(Heading, { level: "h2", children: "Account Mappings" }), _jsx(Text, { className: "text-ui-fg-subtle", children: "Enter the QuickBooks Account IDs (Numeric) for transactions." }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-y-2", children: [_jsx(Label, { children: "Income Account ID" }), _jsx(Controller, { control: control, name: "income_account_id", render: ({ field }) => _jsx(Input, { ...field, placeholder: "e.g. 1" }) })] }), _jsxs("div", { className: "flex flex-col gap-y-2", children: [_jsx(Label, { children: "COGS Account ID" }), _jsx(Controller, { control: control, name: "cogs_account_id", render: ({ field }) => _jsx(Input, { ...field, placeholder: "e.g. 50" }) })] }), _jsxs("div", { className: "flex flex-col gap-y-2", children: [_jsx(Label, { children: "Asset Account ID" }), _jsx(Controller, { control: control, name: "asset_account_id", render: ({ field }) => _jsx(Input, { ...field, placeholder: "e.g. 80" }) })] })] })] }), _jsxs("div", { className: "flex flex-col gap-y-4", children: [_jsx(Heading, { level: "h2", children: "Synchronization Settings" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-8", children: [_jsxs("div", { className: "flex flex-col gap-y-4", children: [_jsx(Text, { weight: "plus", children: "Auto-Sync (Medusa to QuickBooks)" }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { children: "Sync Customers" }), _jsx(Controller, { control: control, name: "auto_sync_customers", render: ({ field: { value, onChange } }) => (_jsx(Switch, { checked: value, onCheckedChange: onChange })) })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { children: "Sync Products" }), _jsx(Controller, { control: control, name: "auto_sync_products", render: ({ field: { value, onChange } }) => (_jsx(Switch, { checked: value, onCheckedChange: onChange })) })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { children: "Sync Orders" }), _jsx(Controller, { control: control, name: "auto_sync_orders", render: ({ field: { value, onChange } }) => (_jsx(Switch, { checked: value, onCheckedChange: onChange })) })] })] }), _jsxs("div", { className: "flex flex-col gap-y-4", children: [_jsx(Text, { weight: "plus", children: "Reverse Sync (QuickBooks to Medusa)" }), _jsx(Text, { className: "text-ui-fg-subtle text-small", children: "Requires Webhooks to be configured in QuickBooks." }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { children: "Sync Customers (Recieve Updates)" }), _jsx(Controller, { control: control, name: "reverse_sync_customers", render: ({ field: { value, onChange } }) => (_jsx(Switch, { checked: value, onCheckedChange: onChange })) })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { children: "Sync Products (Receive Updates)" }), _jsx(Controller, { control: control, name: "reverse_sync_products", render: ({ field: { value, onChange } }) => (_jsx(Switch, { checked: value, onCheckedChange: onChange })) })] })] })] })] }), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { type: "submit", isLoading: isSaving, children: "Save Settings" }) })] })] }));
|
|
111
|
+
};
|
|
112
|
+
export const config = defineRouteConfig({
|
|
113
|
+
label: "QuickBooks",
|
|
114
|
+
icon: CurrencyDollar,
|
|
115
|
+
});
|
|
116
|
+
export default QuickBooksSettingsPage;
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quickbooks-medusa-plugin",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Medusa v2 plugin for QuickBooks Online bi-directional sync (Customers, Products, Orders, Inventory).",
|
|
5
5
|
"author": "bootssecurity",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"files": [
|
|
8
|
-
".medusa/server"
|
|
8
|
+
".medusa/server",
|
|
9
|
+
"src/admin"
|
|
9
10
|
],
|
|
10
11
|
"exports": {
|
|
11
12
|
"./package.json": "./package.json",
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
"./providers/*": "./.medusa/server/src/providers/*/index.js",
|
|
16
17
|
"./*": "./.medusa/server/src/*.js",
|
|
17
18
|
"./admin": {
|
|
18
|
-
"import": "./.medusa/server/src/admin/index.
|
|
19
|
+
"import": "./.medusa/server/src/admin/index.js",
|
|
19
20
|
"require": "./.medusa/server/src/admin/index.js",
|
|
20
21
|
"default": "./.medusa/server/src/admin/index.js"
|
|
21
22
|
}
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"intuit"
|
|
31
32
|
],
|
|
32
33
|
"scripts": {
|
|
33
|
-
"build": "medusa plugin:build &&
|
|
34
|
+
"build": "medusa plugin:build && tsc --project src/admin/tsconfig.json",
|
|
34
35
|
"dev": "medusa plugin:develop",
|
|
35
36
|
"prepublishOnly": "npm run build"
|
|
36
37
|
},
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Admin Customizations
|
|
2
|
+
|
|
3
|
+
You can extend the Medusa Admin to add widgets and new pages. Your customizations interact with API routes to provide merchants with custom functionalities.
|
|
4
|
+
|
|
5
|
+
## Example: Create a Widget
|
|
6
|
+
|
|
7
|
+
A widget is a React component that can be injected into an existing page in the admin dashboard.
|
|
8
|
+
|
|
9
|
+
For example, create the file `src/admin/widgets/product-widget.tsx` with the following content:
|
|
10
|
+
|
|
11
|
+
```tsx title="src/admin/widgets/product-widget.tsx"
|
|
12
|
+
import { defineWidgetConfig } from "@medusajs/admin-sdk"
|
|
13
|
+
|
|
14
|
+
// The widget
|
|
15
|
+
const ProductWidget = () => {
|
|
16
|
+
return (
|
|
17
|
+
<div>
|
|
18
|
+
<h2>Product Widget</h2>
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The widget's configurations
|
|
24
|
+
export const config = defineWidgetConfig({
|
|
25
|
+
zone: "product.details.after",
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export default ProductWidget
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This inserts a widget with the text “Product Widget” at the end of a product’s details page.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Admin Customizations Translations
|
|
2
|
+
|
|
3
|
+
The Medusa Admin dashboard supports multiple languages for its interface. Medusa uses [react-i18next](https://react.i18next.com/) to manage translations in the admin dashboard.
|
|
4
|
+
|
|
5
|
+
To add translations, create JSON translation files for each language under the `src/admin/i18n/json` directory. For example, create the `src/admin/i18n/json/en.json` file with the following content:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"brands": {
|
|
10
|
+
"title": "Brands",
|
|
11
|
+
"description": "Manage your product brands"
|
|
12
|
+
},
|
|
13
|
+
"done": "Done"
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then, export the translations in `src/admin/i18n/index.ts`:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import en from "./json/en.json" with { type: "json" }
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
en: {
|
|
24
|
+
translation: en,
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Finally, use translations in your admin widgets and routes using the `useTranslation` hook:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { defineWidgetConfig } from "@medusajs/admin-sdk"
|
|
33
|
+
import { Button, Container, Heading } from "@medusajs/ui"
|
|
34
|
+
import { useTranslation } from "react-i18next"
|
|
35
|
+
|
|
36
|
+
const ProductWidget = () => {
|
|
37
|
+
const { t } = useTranslation()
|
|
38
|
+
return (
|
|
39
|
+
<Container className="p-0">
|
|
40
|
+
<div className="flex items-center justify-between px-6 py-4">
|
|
41
|
+
<Heading level="h2">{t("brands.title")}</Heading>
|
|
42
|
+
<p>{t("brands.description")}</p>
|
|
43
|
+
</div>
|
|
44
|
+
<div className="flex justify-end px-6 py-4">
|
|
45
|
+
<Button variant="primary">{t("done")}</Button>
|
|
46
|
+
</div>
|
|
47
|
+
</Container>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const config = defineWidgetConfig({
|
|
52
|
+
zone: "product.details.before",
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
export default ProductWidget
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Learn more about translating admin extensions in the [Translate Admin Customizations](https://docs.medusajs.com/learn/fundamentals/admin/translations) documentation.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default {}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineRouteConfig } from "@medusajs/admin-sdk"
|
|
2
2
|
import { Container, Heading, Text, Input, Button, Label, Switch, toast, Badge } from "@medusajs/ui"
|
|
3
|
+
import { CurrencyDollar } from "@medusajs/icons"
|
|
3
4
|
import { useEffect, useState } from "react"
|
|
4
5
|
import { useForm, Controller } from "react-hook-form"
|
|
5
6
|
|
|
@@ -24,7 +25,9 @@ type ConnectionStatus = {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const QuickBooksSettingsPage = () => {
|
|
27
|
-
|
|
28
|
+
// defaults to null
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
30
|
+
const [_, setSettings] = useState<QuickBooksSettings | null>(null)
|
|
28
31
|
const [connection, setConnection] = useState<ConnectionStatus | null>(null)
|
|
29
32
|
const [isLoading, setIsLoading] = useState(true)
|
|
30
33
|
const [isSaving, setIsSaving] = useState(false)
|
|
@@ -278,7 +281,7 @@ const QuickBooksSettingsPage = () => {
|
|
|
278
281
|
|
|
279
282
|
export const config = defineRouteConfig({
|
|
280
283
|
label: "QuickBooks",
|
|
281
|
-
icon:
|
|
284
|
+
icon: CurrencyDollar,
|
|
282
285
|
})
|
|
283
286
|
|
|
284
287
|
export default QuickBooksSettingsPage
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2020",
|
|
7
|
+
"DOM",
|
|
8
|
+
"DOM.Iterable"
|
|
9
|
+
],
|
|
10
|
+
"module": "ESNext",
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"noEmit": false,
|
|
16
|
+
"outDir": "../../.medusa/server/src/admin",
|
|
17
|
+
"jsx": "react-jsx",
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true
|
|
22
|
+
},
|
|
23
|
+
"include": [
|
|
24
|
+
"."
|
|
25
|
+
],
|
|
26
|
+
"exclude": [
|
|
27
|
+
"node_modules",
|
|
28
|
+
"../../.medusa"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|