@lodashventure/medusa-product-content 1.1.12 → 1.1.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lodashventure/medusa-product-content",
3
- "version": "1.1.12",
3
+ "version": "1.1.13",
4
4
  "description": "Medusa Admin plugin for enhanced product content management with rich text editor and i18n specifications",
5
5
  "author": "Product Core Team",
6
6
  "license": "MIT",
@@ -15,10 +15,9 @@
15
15
  "./package.json": "./package.json"
16
16
  },
17
17
  "scripts": {
18
- "build": "tsc --build",
19
- "watch": "tsc --watch",
20
- "dev": "tsc --watch",
21
- "test": "jest"
18
+ "build": "medusa plugin:build",
19
+ "dev": "medusa plugin:develop",
20
+ "prepublishOnly": "medusa plugin:build"
22
21
  },
23
22
  "dependencies": {
24
23
  "@tiptap/core": "^2.1.13",
@@ -1,25 +0,0 @@
1
- /**
2
- * Custom hook for product content management
3
- */
4
- import { ProductContentMetadata, LongDescription, LongDescriptionVersion, ProductSpecs } from "../../types";
5
- export interface UseProductContentOptions {
6
- productId: string;
7
- maxVersions?: number;
8
- autoSanitize?: boolean;
9
- }
10
- export interface UseProductContentReturn {
11
- product: any;
12
- isLoading: boolean;
13
- error: Error | null;
14
- metadata: ProductContentMetadata;
15
- longDescription: LongDescription | null;
16
- specs: ProductSpecs | null;
17
- versions: LongDescriptionVersion[];
18
- updateLongDescription: (html: string, richjson?: any, locale?: string) => Promise<void>;
19
- updateSpecs: (specs: ProductSpecs) => Promise<void>;
20
- createVersion: (description: LongDescription) => Promise<void>;
21
- restoreVersion: (version: number) => Promise<void>;
22
- deleteVersion: (version: number) => Promise<void>;
23
- updateMetadata: (updates: Partial<ProductContentMetadata>) => Promise<void>;
24
- }
25
- export declare function useProductContent(options: UseProductContentOptions): UseProductContentReturn;
@@ -1,165 +0,0 @@
1
- "use strict";
2
- /**
3
- * Custom hook for product content management
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.useProductContent = useProductContent;
7
- const react_1 = require("react");
8
- const react_query_1 = require("@tanstack/react-query");
9
- const sdk_1 = require("../lib/sdk");
10
- const sanitize_1 = require("../utils/sanitize");
11
- function useProductContent(options) {
12
- const { productId, maxVersions = 10, autoSanitize = true } = options;
13
- // Fetch product with expanded metadata
14
- const { data: product, isLoading, error, } = (0, react_query_1.useQuery)({
15
- queryKey: ["product", productId],
16
- queryFn: async () => {
17
- const response = await sdk_1.sdk.admin.product.retrieve(productId, {
18
- fields: "*variants,*variants.prices,*variants.options,*options,*images,*collection,*categories,*metadata,*tags",
19
- });
20
- return response.product;
21
- },
22
- });
23
- // Update mutation
24
- const updateProduct = (0, react_query_1.useMutation)({
25
- mutationFn: async (data) => {
26
- return await sdk_1.sdk.admin.product.update(productId, data);
27
- },
28
- });
29
- // Extract metadata
30
- const metadata = (0, react_1.useMemo)(() => {
31
- if (!product || !product.metadata)
32
- return {};
33
- return product.metadata;
34
- }, [product]);
35
- const longDescription = (0, react_1.useMemo)(() => {
36
- return metadata.long_description || null;
37
- }, [metadata.long_description]);
38
- const specs = (0, react_1.useMemo)(() => {
39
- return metadata.specs || null;
40
- }, [metadata.specs]);
41
- const versions = (0, react_1.useMemo)(() => {
42
- return metadata.long_description_versions || [];
43
- }, [metadata.long_description_versions]);
44
- // Update long description
45
- const updateLongDescription = (0, react_1.useCallback)(async (html, richjson, locale = "th-TH") => {
46
- const sanitizedHtml = autoSanitize ? (0, sanitize_1.sanitizeHTML)(html) : html;
47
- const newDescription = {
48
- html: sanitizedHtml,
49
- richjson,
50
- locale,
51
- updated_at: new Date().toISOString(),
52
- updated_by: "current_user", // TODO: Get from auth context
53
- };
54
- // Create version if exists
55
- let newVersions = [...versions];
56
- if (longDescription) {
57
- const version = {
58
- ...longDescription,
59
- version: versions.length + 1,
60
- created_at: longDescription.updated_at || new Date().toISOString(),
61
- };
62
- newVersions = [version, ...versions].slice(0, maxVersions);
63
- }
64
- await updateProduct.mutateAsync({
65
- metadata: {
66
- ...metadata,
67
- long_description: newDescription,
68
- long_description_versions: newVersions,
69
- content_last_updated: new Date().toISOString(),
70
- content_updated_by: "current_user",
71
- },
72
- });
73
- }, [
74
- autoSanitize,
75
- longDescription,
76
- maxVersions,
77
- metadata,
78
- updateProduct,
79
- versions,
80
- ]);
81
- // Update specifications
82
- const updateSpecs = (0, react_1.useCallback)(async (newSpecs) => {
83
- await updateProduct.mutateAsync({
84
- metadata: {
85
- ...metadata,
86
- specs: newSpecs,
87
- content_last_updated: new Date().toISOString(),
88
- content_updated_by: "current_user",
89
- },
90
- });
91
- }, [metadata, updateProduct]);
92
- // Create version
93
- const createVersion = (0, react_1.useCallback)(async (description) => {
94
- const version = {
95
- ...description,
96
- version: versions.length + 1,
97
- created_at: new Date().toISOString(),
98
- };
99
- const newVersions = [version, ...versions].slice(0, maxVersions);
100
- await updateProduct.mutateAsync({
101
- metadata: {
102
- ...metadata,
103
- long_description_versions: newVersions,
104
- },
105
- });
106
- }, [maxVersions, metadata, updateProduct, versions]);
107
- // Restore version
108
- const restoreVersion = (0, react_1.useCallback)(async (versionNumber) => {
109
- const version = versions.find((v) => v.version === versionNumber);
110
- if (!version) {
111
- throw new Error(`Version ${versionNumber} not found`);
112
- }
113
- const restoredDescription = {
114
- html: version.html,
115
- richjson: version.richjson,
116
- locale: version.locale,
117
- updated_at: new Date().toISOString(),
118
- updated_by: "current_user",
119
- };
120
- await updateProduct.mutateAsync({
121
- metadata: {
122
- ...metadata,
123
- long_description: restoredDescription,
124
- content_last_updated: new Date().toISOString(),
125
- content_updated_by: "current_user",
126
- },
127
- });
128
- }, [metadata, updateProduct, versions]);
129
- // Delete version
130
- const deleteVersion = (0, react_1.useCallback)(async (versionNumber) => {
131
- const newVersions = versions.filter((v) => v.version !== versionNumber);
132
- await updateProduct.mutateAsync({
133
- metadata: {
134
- ...metadata,
135
- long_description_versions: newVersions,
136
- },
137
- });
138
- }, [metadata, updateProduct, versions]);
139
- // Update metadata
140
- const updateMetadata = (0, react_1.useCallback)(async (updates) => {
141
- await updateProduct.mutateAsync({
142
- metadata: {
143
- ...metadata,
144
- ...updates,
145
- content_last_updated: new Date().toISOString(),
146
- content_updated_by: "current_user",
147
- },
148
- });
149
- }, [metadata, updateProduct]);
150
- return {
151
- product,
152
- isLoading,
153
- error,
154
- metadata,
155
- longDescription,
156
- specs,
157
- versions,
158
- updateLongDescription,
159
- updateSpecs,
160
- createVersion,
161
- restoreVersion,
162
- deleteVersion,
163
- updateMetadata,
164
- };
165
- }
@@ -1,2 +0,0 @@
1
- import Medusa from "@medusajs/js-sdk";
2
- export declare const sdk: Medusa;
@@ -1,15 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.sdk = void 0;
7
- // @ts-nocheck
8
- const js_sdk_1 = __importDefault(require("@medusajs/js-sdk"));
9
- exports.sdk = new js_sdk_1.default({
10
- baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
11
- debug: import.meta.env.DEV,
12
- auth: {
13
- type: "session",
14
- },
15
- });
@@ -1,8 +0,0 @@
1
- /**
2
- * Product Content Management Page
3
- * Main listing page accessible from admin menu
4
- */
5
- import React from "react";
6
- declare const ProductContentPage: () => React.JSX.Element;
7
- export declare const config: import("@medusajs/admin-sdk").RouteConfig;
8
- export default ProductContentPage;
@@ -1,158 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.config = void 0;
37
- /**
38
- * Product Content Management Page
39
- * Main listing page accessible from admin menu
40
- */
41
- // @ts-nocheck
42
- const react_1 = __importStar(require("react"));
43
- const ui_1 = require("@medusajs/ui");
44
- const icons_1 = require("@medusajs/icons");
45
- const admin_sdk_1 = require("@medusajs/admin-sdk");
46
- const sdk_1 = require("../../lib/sdk");
47
- const ProductContentPage = () => {
48
- const [products, setProducts] = (0, react_1.useState)([]);
49
- const [loading, setLoading] = (0, react_1.useState)(true);
50
- const [error, setError] = (0, react_1.useState)(null);
51
- const [searchQuery, setSearchQuery] = (0, react_1.useState)("");
52
- (0, react_1.useEffect)(() => {
53
- fetchProducts();
54
- }, []);
55
- const fetchProducts = async () => {
56
- setLoading(true);
57
- setError(null);
58
- try {
59
- const response = await sdk_1.sdk.client.fetch("/admin/products?limit=100", {
60
- credentials: "include",
61
- });
62
- if (response.ok) {
63
- const data = await response.json();
64
- setProducts(data.products || []);
65
- }
66
- else {
67
- setError("Failed to fetch products");
68
- }
69
- }
70
- catch (err) {
71
- setError("Error fetching products");
72
- console.error("Error fetching products:", err);
73
- }
74
- finally {
75
- setLoading(false);
76
- }
77
- };
78
- const handleManageContent = (productId) => {
79
- window.location.href = `/app/products/${productId}`;
80
- };
81
- const filteredProducts = products.filter((product) => {
82
- if (!searchQuery)
83
- return true;
84
- const query = searchQuery.toLowerCase();
85
- return (product.title?.toLowerCase().includes(query) ||
86
- product.handle?.toLowerCase().includes(query));
87
- });
88
- const hasContent = (product) => {
89
- return product.metadata?.long_description || product.metadata?.specs;
90
- };
91
- const getContentStatus = (product) => {
92
- const hasLongDesc = !!product.metadata?.long_description;
93
- const hasSpecs = !!product.metadata?.specs;
94
- if (hasLongDesc && hasSpecs)
95
- return { label: "Complete", color: "green" };
96
- if (hasLongDesc || hasSpecs)
97
- return { label: "Partial", color: "orange" };
98
- return { label: "Empty", color: "grey" };
99
- };
100
- if (loading) {
101
- return (react_1.default.createElement(ui_1.Container, null,
102
- react_1.default.createElement("div", { className: "flex h-64 items-center justify-center" },
103
- react_1.default.createElement(ui_1.Text, null, "Loading products..."))));
104
- }
105
- if (error) {
106
- return (react_1.default.createElement(ui_1.Container, null,
107
- react_1.default.createElement(ui_1.Alert, { variant: "error", dismissible: true }, error)));
108
- }
109
- return (react_1.default.createElement(ui_1.Container, null,
110
- react_1.default.createElement("div", { className: "mb-8" },
111
- react_1.default.createElement("div", { className: "flex items-center justify-between mb-4" },
112
- react_1.default.createElement("div", null,
113
- react_1.default.createElement(ui_1.Heading, { level: "h1", className: "mb-2" }, "Product Content"),
114
- react_1.default.createElement(ui_1.Text, { className: "text-ui-fg-subtle" }, "Manage long descriptions and specifications for all products"))),
115
- react_1.default.createElement("div", { className: "flex items-center gap-4" },
116
- react_1.default.createElement("div", { className: "relative flex-1 max-w-md" },
117
- react_1.default.createElement(ui_1.Input, { placeholder: "Search products...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "pl-10" }),
118
- react_1.default.createElement(icons_1.MagnifyingGlass, { className: "absolute left-3 top-1/2 -translate-y-1/2 text-ui-fg-muted" })))),
119
- filteredProducts.length === 0 ? (react_1.default.createElement("div", { className: "flex h-64 flex-col items-center justify-center rounded-lg border-2 border-dashed border-ui-border-base" },
120
- react_1.default.createElement(icons_1.SquareTwoStack, { className: "mb-4 text-ui-fg-subtle", style: { width: "3rem", height: "3rem" } }),
121
- react_1.default.createElement(ui_1.Text, { className: "mb-2 text-lg font-medium" }, searchQuery ? "No products found" : "No products yet"),
122
- react_1.default.createElement(ui_1.Text, { className: "mb-4 text-ui-fg-subtle" }, searchQuery
123
- ? "Try adjusting your search query"
124
- : "Create products to manage their content"))) : (react_1.default.createElement("div", { className: "overflow-hidden rounded-lg border" },
125
- react_1.default.createElement(ui_1.Table, null,
126
- react_1.default.createElement(ui_1.Table.Header, null,
127
- react_1.default.createElement(ui_1.Table.Row, null,
128
- react_1.default.createElement(ui_1.Table.HeaderCell, null, "Product"),
129
- react_1.default.createElement(ui_1.Table.HeaderCell, null, "Handle"),
130
- react_1.default.createElement(ui_1.Table.HeaderCell, null, "Status"),
131
- react_1.default.createElement(ui_1.Table.HeaderCell, null, "Content Status"),
132
- react_1.default.createElement(ui_1.Table.HeaderCell, { className: "text-right" }, "Actions"))),
133
- react_1.default.createElement(ui_1.Table.Body, null, filteredProducts.map((product) => {
134
- const contentStatus = getContentStatus(product);
135
- return (react_1.default.createElement(ui_1.Table.Row, { key: product.id },
136
- react_1.default.createElement(ui_1.Table.Cell, null,
137
- react_1.default.createElement(ui_1.Text, { className: "font-medium" }, product.title)),
138
- react_1.default.createElement(ui_1.Table.Cell, null,
139
- react_1.default.createElement(ui_1.Badge, { color: "blue" }, product.handle || "—")),
140
- react_1.default.createElement(ui_1.Table.Cell, null,
141
- react_1.default.createElement(ui_1.Badge, { color: product.status === "published" ? "green" : "grey" }, product.status || "draft")),
142
- react_1.default.createElement(ui_1.Table.Cell, null,
143
- react_1.default.createElement("div", { className: "flex items-center gap-2" },
144
- react_1.default.createElement(ui_1.Badge, { color: contentStatus.color }, contentStatus.label),
145
- product.metadata?.long_description && (react_1.default.createElement(ui_1.Badge, { color: "green", size: "xsmall" }, "Description")),
146
- product.metadata?.specs && (react_1.default.createElement(ui_1.Badge, { color: "green", size: "xsmall" }, "Specs")))),
147
- react_1.default.createElement(ui_1.Table.Cell, null,
148
- react_1.default.createElement("div", { className: "flex items-center justify-end" },
149
- react_1.default.createElement(ui_1.Button, { variant: "secondary", size: "small", onClick: () => handleManageContent(product.id) },
150
- react_1.default.createElement(icons_1.PencilSquare, { className: "mr-1" }),
151
- "View Product")))));
152
- })))))));
153
- };
154
- exports.config = (0, admin_sdk_1.defineRouteConfig)({
155
- label: "Product Content",
156
- icon: icons_1.SquareTwoStack,
157
- });
158
- exports.default = ProductContentPage;
@@ -1,30 +0,0 @@
1
- /**
2
- * Import/Export utility for specifications
3
- */
4
- import { ProductSpecs, ImportValidationResult } from '../../types';
5
- /**
6
- * Export specifications to CSV
7
- */
8
- export declare function exportSpecsToCSV(specs: ProductSpecs): string;
9
- /**
10
- * Export specifications to JSON
11
- */
12
- export declare function exportSpecsToJSON(specs: ProductSpecs): string;
13
- /**
14
- * Import specifications from CSV
15
- */
16
- export declare function importSpecsFromCSV(csvContent: string, defaultLocale?: string): Promise<{
17
- specs: ProductSpecs | null;
18
- validation: ImportValidationResult;
19
- }>;
20
- /**
21
- * Import specifications from JSON
22
- */
23
- export declare function importSpecsFromJSON(jsonContent: string): Promise<{
24
- specs: ProductSpecs | null;
25
- validation: ImportValidationResult;
26
- }>;
27
- /**
28
- * Merge imported specs with existing specs
29
- */
30
- export declare function mergeSpecs(existing: ProductSpecs, imported: ProductSpecs, strategy?: 'replace' | 'merge' | 'append'): ProductSpecs;