@powerhousedao/contributor-billing 0.1.46 → 0.1.47
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/dist/editors/builder-team-admin/components/FolderTree.d.ts.map +1 -1
- package/dist/editors/builder-team-admin/components/FolderTree.js +24 -10
- package/dist/editors/builder-team-admin/components/ResourcesServices.d.ts +2 -2
- package/dist/editors/builder-team-admin/components/ResourcesServices.d.ts.map +1 -1
- package/dist/editors/builder-team-admin/components/ResourcesServices.js +25 -17
- package/dist/editors/builder-team-admin/hooks/useResourcesServicesAutoPlacement.d.ts +18 -9
- package/dist/editors/builder-team-admin/hooks/useResourcesServicesAutoPlacement.d.ts.map +1 -1
- package/dist/editors/builder-team-admin/hooks/useResourcesServicesAutoPlacement.js +176 -35
- package/dist/style.css +0 -8
- package/dist/subgraphs/resources-services/resolvers.js +8 -8
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FolderTree.d.ts","sourceRoot":"","sources":["../../../../editors/builder-team-admin/components/FolderTree.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FolderTree.d.ts","sourceRoot":"","sources":["../../../../editors/builder-team-admin/components/FolderTree.tsx"],"names":[],"mappings":"AAiCA,iEAAiE;AACjE,MAAM,MAAM,UAAU,GAClB,cAAc,GACd,iBAAiB,GACjB,kBAAkB,GAClB,oBAAoB,GACpB,IAAI,CAAC;AA0GT,KAAK,eAAe,GAAG;IACrB,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EAAE,kBAAkB,EAAE,EAAE,eAAe,kDA0ajE"}
|
|
@@ -6,7 +6,8 @@ import { useMemo, useState } from "react";
|
|
|
6
6
|
const ICON_SIZE = 16;
|
|
7
7
|
const EXPENSE_REPORTS_FOLDER_NAME = "Expense Reports";
|
|
8
8
|
const SNAPSHOT_REPORTS_FOLDER_NAME = "Snapshot Reports";
|
|
9
|
-
const
|
|
9
|
+
const SERVICES_AND_OFFERINGS_FOLDER_NAME = "Services And Offerings";
|
|
10
|
+
const RESOURCE_TEMPLATES_FOLDER_NAME = "Products";
|
|
10
11
|
const SERVICE_OFFERINGS_FOLDER_NAME = "Service Offerings";
|
|
11
12
|
/**
|
|
12
13
|
* Maps navigation section IDs to their corresponding document types.
|
|
@@ -52,7 +53,7 @@ const BASE_NAVIGATION_SECTIONS = [
|
|
|
52
53
|
},
|
|
53
54
|
{
|
|
54
55
|
id: "resources-services",
|
|
55
|
-
title: "
|
|
56
|
+
title: "Service Offerings",
|
|
56
57
|
icon: _jsx(Layers, { size: ICON_SIZE }),
|
|
57
58
|
},
|
|
58
59
|
{
|
|
@@ -122,20 +123,33 @@ export function FolderTree({ onCustomViewChange }) {
|
|
|
122
123
|
const nodes = driveDocument.state.global.nodes;
|
|
123
124
|
return nodes.find((node) => isFolderNodeKind(node) && node.name === SNAPSHOT_REPORTS_FOLDER_NAME);
|
|
124
125
|
}, [driveDocument]);
|
|
125
|
-
// Find the "
|
|
126
|
-
const
|
|
126
|
+
// Find the "Services And Offerings" parent folder in the drive (at root level)
|
|
127
|
+
const servicesAndOfferingsFolder = useMemo(() => {
|
|
127
128
|
if (!driveDocument)
|
|
128
129
|
return null;
|
|
129
130
|
const nodes = driveDocument.state.global.nodes;
|
|
130
|
-
return nodes.find((node) => isFolderNodeKind(node) &&
|
|
131
|
+
return nodes.find((node) => isFolderNodeKind(node) &&
|
|
132
|
+
node.name === SERVICES_AND_OFFERINGS_FOLDER_NAME &&
|
|
133
|
+
!node.parentFolder);
|
|
131
134
|
}, [driveDocument]);
|
|
132
|
-
// Find the "
|
|
135
|
+
// Find the "Products" folder (inside Services And Offerings folder)
|
|
136
|
+
const resourceTemplatesFolder = useMemo(() => {
|
|
137
|
+
if (!driveDocument || !servicesAndOfferingsFolder)
|
|
138
|
+
return null;
|
|
139
|
+
const nodes = driveDocument.state.global.nodes;
|
|
140
|
+
return nodes.find((node) => isFolderNodeKind(node) &&
|
|
141
|
+
node.name === RESOURCE_TEMPLATES_FOLDER_NAME &&
|
|
142
|
+
node.parentFolder === servicesAndOfferingsFolder.id);
|
|
143
|
+
}, [driveDocument, servicesAndOfferingsFolder]);
|
|
144
|
+
// Find the "Service Offerings" folder (inside Services And Offerings folder)
|
|
133
145
|
const serviceOfferingsFolder = useMemo(() => {
|
|
134
|
-
if (!driveDocument)
|
|
146
|
+
if (!driveDocument || !servicesAndOfferingsFolder)
|
|
135
147
|
return null;
|
|
136
148
|
const nodes = driveDocument.state.global.nodes;
|
|
137
|
-
return nodes.find((node) => isFolderNodeKind(node) &&
|
|
138
|
-
|
|
149
|
+
return nodes.find((node) => isFolderNodeKind(node) &&
|
|
150
|
+
node.name === SERVICE_OFFERINGS_FOLDER_NAME &&
|
|
151
|
+
node.parentFolder === servicesAndOfferingsFolder.id);
|
|
152
|
+
}, [driveDocument, servicesAndOfferingsFolder]);
|
|
139
153
|
// Build a set of all node IDs that are within the Expense Reports folder tree
|
|
140
154
|
const expenseReportsNodeIds = useMemo(() => {
|
|
141
155
|
const nodeIds = new Set();
|
|
@@ -425,7 +439,7 @@ export function FolderTree({ onCustomViewChange }) {
|
|
|
425
439
|
showCreateDocumentModal(documentType);
|
|
426
440
|
}
|
|
427
441
|
};
|
|
428
|
-
return (_jsx(SidebarProvider, { nodes: navigationSections, children: _jsx(Sidebar, { className: "pt-1", nodes: navigationSections, activeNodeId: activeNodeId, onActiveNodeChange: handleActiveNodeChange, sidebarTitle: "Builder Team Admin", showSearchBar: false, resizable: true, allowPinning: false, showStatusFilter: false, initialWidth: 256, defaultLevel: 2, handleOnTitleClick: () => {
|
|
442
|
+
return (_jsx(SidebarProvider, { nodes: navigationSections, children: _jsx(Sidebar, { className: "pt-1", nodes: navigationSections, activeNodeId: activeNodeId, onActiveNodeChange: handleActiveNodeChange, sidebarTitle: isOperator ? "Operator Team Admin" : "Builder Team Admin", showSearchBar: false, resizable: true, allowPinning: false, showStatusFilter: false, initialWidth: 256, defaultLevel: 2, handleOnTitleClick: () => {
|
|
429
443
|
onCustomViewChange?.(null);
|
|
430
444
|
setSelectedNode("");
|
|
431
445
|
} }) }));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Component for the Resources & Services custom view.
|
|
3
|
-
* Shows
|
|
4
|
-
* Users can create powerhouse/resource-template docs in
|
|
3
|
+
* Shows folder structure: Services And Offerings > Products / Service Offerings.
|
|
4
|
+
* Users can create powerhouse/resource-template docs in Products
|
|
5
5
|
* and powerhouse/service-offering docs in Service Offerings.
|
|
6
6
|
*/
|
|
7
7
|
export declare function ResourcesServices(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourcesServices.d.ts","sourceRoot":"","sources":["../../../../editors/builder-team-admin/components/ResourcesServices.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ResourcesServices.d.ts","sourceRoot":"","sources":["../../../../editors/builder-team-admin/components/ResourcesServices.tsx"],"names":[],"mappings":"AAoBA;;;;;GAKG;AACH,wBAAgB,iBAAiB,4CAwQhC"}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { FileItem } from "@powerhousedao/design-system/connect";
|
|
3
3
|
import { FolderItem } from "@powerhousedao/design-system/connect";
|
|
4
4
|
import { isFolderNodeKind, isFileNodeKind, setSelectedNode, useSelectedNodePath, useNodesInSelectedDriveOrFolder, useUserPermissions, showCreateDocumentModal, } from "@powerhousedao/reactor-browser";
|
|
5
|
-
import { useEffect, useRef
|
|
5
|
+
import { useEffect, useRef } from "react";
|
|
6
6
|
import { Plus, FileText, Package } from "lucide-react";
|
|
7
7
|
import { useResourcesServicesAutoPlacement } from "../hooks/useResourcesServicesAutoPlacement.js";
|
|
8
|
-
const
|
|
8
|
+
const SERVICES_AND_OFFERINGS_FOLDER_NAME = "Services And Offerings";
|
|
9
|
+
const RESOURCE_TEMPLATES_FOLDER_NAME = "Products";
|
|
9
10
|
const SERVICE_OFFERINGS_FOLDER_NAME = "Service Offerings";
|
|
10
11
|
/**
|
|
11
12
|
* Component for the Resources & Services custom view.
|
|
12
|
-
* Shows
|
|
13
|
-
* Users can create powerhouse/resource-template docs in
|
|
13
|
+
* Shows folder structure: Services And Offerings > Products / Service Offerings.
|
|
14
|
+
* Users can create powerhouse/resource-template docs in Products
|
|
14
15
|
* and powerhouse/service-offering docs in Service Offerings.
|
|
15
16
|
*/
|
|
16
17
|
export function ResourcesServices() {
|
|
@@ -19,9 +20,11 @@ export function ResourcesServices() {
|
|
|
19
20
|
const nodesInCurrentFolder = useNodesInSelectedDriveOrFolder();
|
|
20
21
|
const { isAllowedToCreateDocuments } = useUserPermissions();
|
|
21
22
|
// Use the shared auto-placement hook - this handles:
|
|
22
|
-
// 1. Creating the "
|
|
23
|
-
// 2. Creating the "
|
|
24
|
-
|
|
23
|
+
// 1. Creating the "Services And Offerings" parent folder if it doesn't exist
|
|
24
|
+
// 2. Creating the "Products" subfolder if it doesn't exist
|
|
25
|
+
// 3. Creating the "Service Offerings" subfolder if it doesn't exist
|
|
26
|
+
// 4. Migrating existing documents from old folder structure
|
|
27
|
+
const { servicesAndOfferingsFolder, resourceTemplatesFolder, serviceOfferingsFolder, resourceTemplateDocuments, serviceOfferingDocuments, } = useResourcesServicesAutoPlacement();
|
|
25
28
|
// Determine which folder we're currently in (if any)
|
|
26
29
|
const currentFolderId = selectedNodePath.at(-1)?.id;
|
|
27
30
|
const isInResourceTemplates = currentFolderId === resourceTemplatesFolder?.id;
|
|
@@ -29,17 +32,24 @@ export function ResourcesServices() {
|
|
|
29
32
|
const isInRootView = !isInResourceTemplates && !isInServiceOfferings;
|
|
30
33
|
// Navigate to root view initially (deselect any node)
|
|
31
34
|
useEffect(() => {
|
|
32
|
-
if (
|
|
35
|
+
if (servicesAndOfferingsFolder &&
|
|
36
|
+
resourceTemplatesFolder &&
|
|
33
37
|
serviceOfferingsFolder &&
|
|
34
38
|
!hasNavigatedToFolder.current) {
|
|
35
39
|
hasNavigatedToFolder.current = true;
|
|
36
40
|
// Don't select any node so we show the root view with both folders
|
|
37
41
|
setSelectedNode("");
|
|
38
42
|
}
|
|
39
|
-
}, [
|
|
43
|
+
}, [
|
|
44
|
+
servicesAndOfferingsFolder,
|
|
45
|
+
resourceTemplatesFolder,
|
|
46
|
+
serviceOfferingsFolder,
|
|
47
|
+
]);
|
|
40
48
|
// Show loading state while folders are being created
|
|
41
|
-
if (!
|
|
42
|
-
|
|
49
|
+
if (!servicesAndOfferingsFolder ||
|
|
50
|
+
!resourceTemplatesFolder ||
|
|
51
|
+
!serviceOfferingsFolder) {
|
|
52
|
+
return (_jsx("div", { className: "flex items-center justify-center h-64", children: _jsxs("div", { className: "text-gray-500", children: ["Setting up ", SERVICES_AND_OFFERINGS_FOLDER_NAME, " folders..."] }) }));
|
|
43
53
|
}
|
|
44
54
|
// Handler for creating new documents
|
|
45
55
|
const handleCreateDocument = (documentType, folderId) => {
|
|
@@ -54,10 +64,10 @@ export function ResourcesServices() {
|
|
|
54
64
|
const fileNodes = nodesInCurrentFolder.filter((n) => isFileNodeKind(n));
|
|
55
65
|
// Render the root view with both folder cards
|
|
56
66
|
if (isInRootView) {
|
|
57
|
-
return (_jsxs("div", { children: [_jsx("div", { className: "text-2xl font-bold text-center mb-6", children:
|
|
67
|
+
return (_jsxs("div", { children: [_jsx("div", { className: "text-2xl font-bold text-center mb-6", children: SERVICES_AND_OFFERINGS_FOLDER_NAME }), _jsxs("div", { className: "space-y-6 px-6", children: [_jsx("p", { className: "text-gray-600 text-center mb-8", children: "Manage your products and service offerings. Click on a folder to view or create documents." }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [_jsxs("div", { className: "border border-gray-200 rounded-lg p-6 hover:border-blue-400 hover:shadow-md transition-all cursor-pointer", onClick: () => setSelectedNode(resourceTemplatesFolder.id), children: [_jsxs("div", { className: "flex items-center gap-3 mb-4", children: [_jsx("div", { className: "p-2 bg-blue-50 rounded-lg", children: _jsx(FileText, { className: "w-6 h-6 text-blue-600" }) }), _jsx("h3", { className: "text-lg font-semibold", children: RESOURCE_TEMPLATES_FOLDER_NAME })] }), _jsx("p", { className: "text-gray-600 text-sm mb-4", children: "Define products that can be used across service offerings." }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-sm text-gray-500", children: [resourceTemplateDocuments.length, " product", resourceTemplateDocuments.length !== 1 ? "s" : ""] }), isAllowedToCreateDocuments && (_jsxs("button", { type: "button", className: "flex items-center gap-1 text-sm bg-blue-500 text-white px-3 py-1 rounded-md hover:bg-blue-600 shadow-sm transition-colors", onClick: (e) => {
|
|
58
68
|
e.stopPropagation();
|
|
59
69
|
handleCreateDocument("powerhouse/resource-template", resourceTemplatesFolder.id);
|
|
60
|
-
}, children: [_jsx(Plus, { size: 14 }), "Add new"] }))] })] }), _jsxs("div", { className: "border border-gray-200 rounded-lg p-6 hover:border-indigo-400 hover:shadow-md transition-all cursor-pointer", onClick: () => setSelectedNode(serviceOfferingsFolder.id), children: [_jsxs("div", { className: "flex items-center gap-3 mb-4", children: [_jsx("div", { className: "p-2 bg-indigo-50 rounded-lg", children: _jsx(Package, { className: "w-6 h-6 text-indigo-600" }) }), _jsx("h3", { className: "text-lg font-semibold", children: SERVICE_OFFERINGS_FOLDER_NAME })] }), _jsx("p", { className: "text-gray-600 text-sm mb-4", children: "Create and manage service offerings with pricing tiers and options." }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-sm text-gray-500", children: [serviceOfferingDocuments.length, " offering", serviceOfferingDocuments.length !== 1 ? "s" : ""] }), isAllowedToCreateDocuments && (_jsxs("button", { type: "button", className: "flex items-center gap-1 text-sm
|
|
70
|
+
}, children: [_jsx(Plus, { size: 14 }), "Add new"] }))] })] }), _jsxs("div", { className: "border border-gray-200 rounded-lg p-6 hover:border-indigo-400 hover:shadow-md transition-all cursor-pointer", onClick: () => setSelectedNode(serviceOfferingsFolder.id), children: [_jsxs("div", { className: "flex items-center gap-3 mb-4", children: [_jsx("div", { className: "p-2 bg-indigo-50 rounded-lg", children: _jsx(Package, { className: "w-6 h-6 text-indigo-600" }) }), _jsx("h3", { className: "text-lg font-semibold", children: SERVICE_OFFERINGS_FOLDER_NAME })] }), _jsx("p", { className: "text-gray-600 text-sm mb-4", children: "Create and manage service offerings with pricing tiers and options." }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-sm text-gray-500", children: [serviceOfferingDocuments.length, " offering", serviceOfferingDocuments.length !== 1 ? "s" : ""] }), isAllowedToCreateDocuments && (_jsxs("button", { type: "button", className: "flex items-center gap-1 text-sm bg-indigo-500 text-white px-3 py-1 rounded-md hover:bg-indigo-600 shadow-sm transition-colors", onClick: (e) => {
|
|
61
71
|
e.stopPropagation();
|
|
62
72
|
handleCreateDocument("powerhouse/service-offering", serviceOfferingsFolder.id);
|
|
63
73
|
}, children: [_jsx(Plus, { size: 14 }), "Add new"] }))] })] })] })] })] }));
|
|
@@ -72,7 +82,5 @@ export function ResourcesServices() {
|
|
|
72
82
|
const hasFolders = folderNodes.length > 0;
|
|
73
83
|
const hasFiles = fileNodes.length > 0;
|
|
74
84
|
const isEmpty = !hasFolders && !hasFiles;
|
|
75
|
-
return (_jsxs("div", { children: [_jsx("div", { className: "text-2xl font-bold text-center mb-4", children: currentFolderName }), _jsxs("div", { className: "space-y-6 px-6", children: [_jsxs("div", { className: "flex h-9 flex-row items-center gap-2 text-gray-500 border-b border-gray-200 pb-3", children: [_jsx("div", { className: "transition-colors hover:text-gray-800 cursor-pointer", onClick: () => setSelectedNode(""), role: "button", children:
|
|
76
|
-
? "resource template"
|
|
77
|
-
: "service offering", "."] }))] })] }))] })] }));
|
|
85
|
+
return (_jsxs("div", { children: [_jsx("div", { className: "text-2xl font-bold text-center mb-4", children: currentFolderName }), _jsxs("div", { className: "space-y-6 px-6", children: [_jsxs("div", { className: "flex h-9 flex-row items-center gap-2 text-gray-500 border-b border-gray-200 pb-3", children: [_jsx("div", { className: "transition-colors hover:text-gray-800 cursor-pointer", onClick: () => setSelectedNode(""), role: "button", children: SERVICES_AND_OFFERINGS_FOLDER_NAME }), _jsx("span", { children: "/" }), _jsx("div", { className: "text-gray-800", children: currentFolderName }), _jsx("span", { children: "/" }), isAllowedToCreateDocuments && (_jsxs("button", { type: "button", className: "ml-1 flex items-center justify-center gap-2 rounded-md bg-gray-50 px-2 py-1.5 transition-colors hover:bg-gray-200 hover:text-gray-800", onClick: () => showCreateDocumentModal(documentType), children: [_jsx(Plus, { size: 14 }), "Add new"] }))] }), hasFolders && (_jsxs("div", { children: [_jsx("h3", { className: "mb-2 text-sm font-bold text-gray-600", children: "Folders" }), _jsx("div", { className: "flex flex-wrap gap-4", children: folderNodes.map((folderNode) => (_jsx(FolderItem, { folderNode: folderNode }, folderNode.id))) })] })), hasFiles && (_jsxs("div", { children: [_jsx("h3", { className: "mb-2 text-sm font-semibold text-gray-600", children: "Documents" }), _jsx("div", { className: "flex flex-wrap gap-4", children: fileNodes.map((fileNode) => (_jsx(FileItem, { fileNode: fileNode }, fileNode.id))) })] })), isEmpty && (_jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [_jsx("div", { className: "text-gray-400 mb-2", children: isInResourceTemplates ? (_jsx(FileText, { className: "w-16 h-16 mx-auto" })) : (_jsx(Package, { className: "w-16 h-16 mx-auto" })) }), _jsxs("p", { className: "text-gray-500 text-sm", children: ["No ", currentFolderName.toLowerCase(), " yet.", isAllowedToCreateDocuments && (_jsxs(_Fragment, { children: [" ", "Click \"Add new\" to create your first", " ", isInResourceTemplates ? "product" : "service offering", "."] }))] })] }))] })] }));
|
|
78
86
|
}
|
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
import type { FolderNode, FileNode } from "document-drive";
|
|
2
2
|
interface UseResourcesServicesAutoPlacementResult {
|
|
3
|
-
/** The
|
|
3
|
+
/** The parent "Services And Offerings" folder node, or null if it doesn't exist yet */
|
|
4
|
+
servicesAndOfferingsFolder: FolderNode | null;
|
|
5
|
+
/** The Products folder node (inside Services And Offerings), or null if it doesn't exist yet */
|
|
4
6
|
resourceTemplatesFolder: FolderNode | null;
|
|
5
|
-
/** The Service Offerings folder node, or null if it doesn't exist yet */
|
|
7
|
+
/** The Service Offerings folder node (inside Services And Offerings), or null if it doesn't exist yet */
|
|
6
8
|
serviceOfferingsFolder: FolderNode | null;
|
|
7
|
-
/** Set of all node IDs within the
|
|
9
|
+
/** Set of all node IDs within the Products folder tree */
|
|
8
10
|
resourceTemplatesNodeIds: Set<string>;
|
|
9
11
|
/** Set of all node IDs within the Service Offerings folder tree */
|
|
10
12
|
serviceOfferingsNodeIds: Set<string>;
|
|
11
|
-
/** All resource template documents within the
|
|
13
|
+
/** All resource template documents within the Products folder */
|
|
12
14
|
resourceTemplateDocuments: FileNode[];
|
|
13
15
|
/** All service offering documents within the Service Offerings folder */
|
|
14
16
|
serviceOfferingDocuments: FileNode[];
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
|
-
* Hook that handles automatic creation of "
|
|
18
|
-
*
|
|
19
|
+
* Hook that handles automatic creation of "Services And Offerings" parent folder
|
|
20
|
+
* with "Products" and "Service Offerings" subfolders, and migrates existing documents
|
|
21
|
+
* from old folder structure.
|
|
22
|
+
*
|
|
23
|
+
* Folder structure:
|
|
24
|
+
* - Services And Offerings (parent folder)
|
|
25
|
+
* - Products (for powerhouse/resource-template docs)
|
|
26
|
+
* - Service Offerings (for powerhouse/service-offering docs)
|
|
19
27
|
*
|
|
20
28
|
* This hook:
|
|
21
|
-
* 1. Creates the
|
|
22
|
-
* 2.
|
|
23
|
-
* 3.
|
|
29
|
+
* 1. Creates the folder structure if it doesn't exist
|
|
30
|
+
* 2. Migrates existing documents from old folders to new structure
|
|
31
|
+
* 3. Deletes old folders after migration
|
|
32
|
+
* 4. Provides access to documents within each folder
|
|
24
33
|
*/
|
|
25
34
|
export declare function useResourcesServicesAutoPlacement(): UseResourcesServicesAutoPlacementResult;
|
|
26
35
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useResourcesServicesAutoPlacement.d.ts","sourceRoot":"","sources":["../../../../editors/builder-team-admin/hooks/useResourcesServicesAutoPlacement.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useResourcesServicesAutoPlacement.d.ts","sourceRoot":"","sources":["../../../../editors/builder-team-admin/hooks/useResourcesServicesAutoPlacement.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAQ,MAAM,gBAAgB,CAAC;AAkBjE,UAAU,uCAAuC;IAC/C,uFAAuF;IACvF,0BAA0B,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9C,gGAAgG;IAChG,uBAAuB,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3C,yGAAyG;IACzG,sBAAsB,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1C,0DAA0D;IAC1D,wBAAwB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtC,mEAAmE;IACnE,uBAAuB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,iEAAiE;IACjE,yBAAyB,EAAE,QAAQ,EAAE,CAAC;IACtC,yEAAyE;IACzE,wBAAwB,EAAE,QAAQ,EAAE,CAAC;CACtC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iCAAiC,IAAI,uCAAuC,CAuU3F"}
|
|
@@ -1,38 +1,89 @@
|
|
|
1
|
-
import { useEffect, useMemo
|
|
2
|
-
import { isFolderNodeKind, isFileNodeKind, addFolder, useSelectedDrive,
|
|
3
|
-
|
|
1
|
+
import { useEffect, useMemo } from "react";
|
|
2
|
+
import { isFolderNodeKind, isFileNodeKind, addFolder, useSelectedDrive, useNodeActions, dispatchActions, } from "@powerhousedao/reactor-browser";
|
|
3
|
+
import { deleteNode } from "document-drive";
|
|
4
|
+
const SERVICES_AND_OFFERINGS_FOLDER_NAME = "Services And Offerings";
|
|
5
|
+
const PRODUCTS_FOLDER_NAME = "Products";
|
|
4
6
|
const SERVICE_OFFERINGS_FOLDER_NAME = "Service Offerings";
|
|
7
|
+
// Old folder names that might exist from previous structure (for migration)
|
|
8
|
+
const OLD_RESOURCE_TEMPLATES_FOLDER_NAME = "Resource Templates";
|
|
9
|
+
// Module-level tracking to prevent duplicate folder creation across all hook instances
|
|
10
|
+
const globalCreationState = {
|
|
11
|
+
createdServicesAndOfferingsFolderForDrives: new Set(),
|
|
12
|
+
createdProductsFolderForDrives: new Set(),
|
|
13
|
+
createdServiceOfferingsFolderForDrives: new Set(),
|
|
14
|
+
processedDocs: new Map(), // driveId -> Set of doc IDs processed
|
|
15
|
+
migratedOldFolders: new Set(), // driveId where migration has been completed
|
|
16
|
+
};
|
|
5
17
|
/**
|
|
6
|
-
* Hook that handles automatic creation of "
|
|
7
|
-
*
|
|
18
|
+
* Hook that handles automatic creation of "Services And Offerings" parent folder
|
|
19
|
+
* with "Products" and "Service Offerings" subfolders, and migrates existing documents
|
|
20
|
+
* from old folder structure.
|
|
21
|
+
*
|
|
22
|
+
* Folder structure:
|
|
23
|
+
* - Services And Offerings (parent folder)
|
|
24
|
+
* - Products (for powerhouse/resource-template docs)
|
|
25
|
+
* - Service Offerings (for powerhouse/service-offering docs)
|
|
8
26
|
*
|
|
9
27
|
* This hook:
|
|
10
|
-
* 1. Creates the
|
|
11
|
-
* 2.
|
|
12
|
-
* 3.
|
|
28
|
+
* 1. Creates the folder structure if it doesn't exist
|
|
29
|
+
* 2. Migrates existing documents from old folders to new structure
|
|
30
|
+
* 3. Deletes old folders after migration
|
|
31
|
+
* 4. Provides access to documents within each folder
|
|
13
32
|
*/
|
|
14
33
|
export function useResourcesServicesAutoPlacement() {
|
|
15
34
|
const [driveDocument] = useSelectedDrive();
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
const { onMoveNode } = useNodeActions();
|
|
36
|
+
const driveId = driveDocument?.header.id;
|
|
37
|
+
// Initialize module-level tracking sets for this drive if needed
|
|
38
|
+
if (driveId && !globalCreationState.processedDocs.has(driveId)) {
|
|
39
|
+
globalCreationState.processedDocs.set(driveId, new Set());
|
|
40
|
+
}
|
|
41
|
+
// Find the "Services And Offerings" parent folder in the drive (at root level)
|
|
42
|
+
const servicesAndOfferingsFolder = useMemo(() => {
|
|
22
43
|
if (!driveDocument)
|
|
23
44
|
return null;
|
|
24
45
|
const nodes = driveDocument.state.global.nodes;
|
|
25
46
|
return (nodes.find((node) => isFolderNodeKind(node) &&
|
|
26
|
-
node.name ===
|
|
47
|
+
node.name === SERVICES_AND_OFFERINGS_FOLDER_NAME &&
|
|
48
|
+
!node.parentFolder) ?? null);
|
|
27
49
|
}, [driveDocument]);
|
|
28
|
-
// Find the "
|
|
50
|
+
// Find the "Products" folder (must be inside Services And Offerings folder)
|
|
51
|
+
const resourceTemplatesFolder = useMemo(() => {
|
|
52
|
+
if (!driveDocument || !servicesAndOfferingsFolder)
|
|
53
|
+
return null;
|
|
54
|
+
const nodes = driveDocument.state.global.nodes;
|
|
55
|
+
return (nodes.find((node) => isFolderNodeKind(node) &&
|
|
56
|
+
node.name === PRODUCTS_FOLDER_NAME &&
|
|
57
|
+
node.parentFolder === servicesAndOfferingsFolder.id) ?? null);
|
|
58
|
+
}, [driveDocument, servicesAndOfferingsFolder]);
|
|
59
|
+
// Find the "Service Offerings" folder (must be inside Services And Offerings folder)
|
|
29
60
|
const serviceOfferingsFolder = useMemo(() => {
|
|
61
|
+
if (!driveDocument || !servicesAndOfferingsFolder)
|
|
62
|
+
return null;
|
|
63
|
+
const nodes = driveDocument.state.global.nodes;
|
|
64
|
+
return (nodes.find((node) => isFolderNodeKind(node) &&
|
|
65
|
+
node.name === SERVICE_OFFERINGS_FOLDER_NAME &&
|
|
66
|
+
node.parentFolder === servicesAndOfferingsFolder.id) ?? null);
|
|
67
|
+
}, [driveDocument, servicesAndOfferingsFolder]);
|
|
68
|
+
// Find old folders that might exist from previous structure (at root level)
|
|
69
|
+
const oldResourceTemplatesFolder = useMemo(() => {
|
|
30
70
|
if (!driveDocument)
|
|
31
71
|
return null;
|
|
32
72
|
const nodes = driveDocument.state.global.nodes;
|
|
33
|
-
return (nodes.find((node) => isFolderNodeKind(node) &&
|
|
73
|
+
return (nodes.find((node) => isFolderNodeKind(node) &&
|
|
74
|
+
(node.name === OLD_RESOURCE_TEMPLATES_FOLDER_NAME ||
|
|
75
|
+
node.name === PRODUCTS_FOLDER_NAME) &&
|
|
76
|
+
!node.parentFolder) ?? null);
|
|
34
77
|
}, [driveDocument]);
|
|
35
|
-
|
|
78
|
+
const oldServiceOfferingsFolder = useMemo(() => {
|
|
79
|
+
if (!driveDocument)
|
|
80
|
+
return null;
|
|
81
|
+
const nodes = driveDocument.state.global.nodes;
|
|
82
|
+
return (nodes.find((node) => isFolderNodeKind(node) &&
|
|
83
|
+
node.name === SERVICE_OFFERINGS_FOLDER_NAME &&
|
|
84
|
+
!node.parentFolder) ?? null);
|
|
85
|
+
}, [driveDocument]);
|
|
86
|
+
// Build a set of all node IDs within the Products folder tree
|
|
36
87
|
const resourceTemplatesNodeIds = useMemo(() => {
|
|
37
88
|
const nodeIds = new Set();
|
|
38
89
|
if (!resourceTemplatesFolder || !driveDocument)
|
|
@@ -76,7 +127,7 @@ export function useResourcesServicesAutoPlacement() {
|
|
|
76
127
|
collectNodeIds(serviceOfferingsFolder.id);
|
|
77
128
|
return nodeIds;
|
|
78
129
|
}, [serviceOfferingsFolder, driveDocument]);
|
|
79
|
-
// Get resource template documents within the
|
|
130
|
+
// Get resource template documents within the Products folder
|
|
80
131
|
const resourceTemplateDocuments = useMemo(() => {
|
|
81
132
|
if (!driveDocument)
|
|
82
133
|
return [];
|
|
@@ -92,27 +143,117 @@ export function useResourcesServicesAutoPlacement() {
|
|
|
92
143
|
node.documentType === "powerhouse/service-offering" &&
|
|
93
144
|
serviceOfferingsNodeIds.has(node.id));
|
|
94
145
|
}, [driveDocument, serviceOfferingsNodeIds]);
|
|
95
|
-
// Create
|
|
146
|
+
// Step 1: Create "Services And Offerings" parent folder if it doesn't exist
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (!driveId || servicesAndOfferingsFolder)
|
|
149
|
+
return;
|
|
150
|
+
if (globalCreationState.createdServicesAndOfferingsFolderForDrives.has(driveId))
|
|
151
|
+
return;
|
|
152
|
+
globalCreationState.createdServicesAndOfferingsFolderForDrives.add(driveId);
|
|
153
|
+
void addFolder(driveId, SERVICES_AND_OFFERINGS_FOLDER_NAME);
|
|
154
|
+
}, [driveId, servicesAndOfferingsFolder]);
|
|
155
|
+
// Step 2: Create "Products" subfolder if it doesn't exist (after parent exists)
|
|
96
156
|
useEffect(() => {
|
|
97
|
-
if (!
|
|
98
|
-
resourceTemplatesFolder ||
|
|
99
|
-
hasCreatedResourceTemplatesFolder.current)
|
|
157
|
+
if (!driveId || !servicesAndOfferingsFolder || resourceTemplatesFolder)
|
|
100
158
|
return;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
159
|
+
if (globalCreationState.createdProductsFolderForDrives.has(driveId))
|
|
160
|
+
return;
|
|
161
|
+
globalCreationState.createdProductsFolderForDrives.add(driveId);
|
|
162
|
+
void addFolder(driveId, PRODUCTS_FOLDER_NAME, servicesAndOfferingsFolder.id);
|
|
163
|
+
}, [driveId, servicesAndOfferingsFolder, resourceTemplatesFolder]);
|
|
164
|
+
// Step 3: Create "Service Offerings" subfolder if it doesn't exist (after parent exists)
|
|
106
165
|
useEffect(() => {
|
|
107
|
-
if (!
|
|
108
|
-
|
|
109
|
-
|
|
166
|
+
if (!driveId || !servicesAndOfferingsFolder || serviceOfferingsFolder)
|
|
167
|
+
return;
|
|
168
|
+
if (globalCreationState.createdServiceOfferingsFolderForDrives.has(driveId))
|
|
169
|
+
return;
|
|
170
|
+
globalCreationState.createdServiceOfferingsFolderForDrives.add(driveId);
|
|
171
|
+
void addFolder(driveId, SERVICE_OFFERINGS_FOLDER_NAME, servicesAndOfferingsFolder.id);
|
|
172
|
+
}, [driveId, servicesAndOfferingsFolder, serviceOfferingsFolder]);
|
|
173
|
+
// Step 4: Migrate documents from old folders to new structure and delete old folders
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (!driveId ||
|
|
176
|
+
!driveDocument ||
|
|
177
|
+
!resourceTemplatesFolder ||
|
|
178
|
+
!serviceOfferingsFolder)
|
|
179
|
+
return;
|
|
180
|
+
if (globalCreationState.migratedOldFolders.has(driveId))
|
|
110
181
|
return;
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
182
|
+
const allNodes = driveDocument.state.global.nodes;
|
|
183
|
+
const processedDocs = globalCreationState.processedDocs.get(driveId);
|
|
184
|
+
if (!processedDocs)
|
|
185
|
+
return;
|
|
186
|
+
// Find all resource template documents that are NOT in the correct folder
|
|
187
|
+
const resourceTemplatesToMigrate = allNodes.filter((node) => isFileNodeKind(node) &&
|
|
188
|
+
node.documentType === "powerhouse/resource-template" &&
|
|
189
|
+
!resourceTemplatesNodeIds.has(node.id) &&
|
|
190
|
+
!processedDocs.has(node.id));
|
|
191
|
+
// Find all service offering documents that are NOT in the correct folder
|
|
192
|
+
const serviceOfferingsToMigrate = allNodes.filter((node) => isFileNodeKind(node) &&
|
|
193
|
+
node.documentType === "powerhouse/service-offering" &&
|
|
194
|
+
!serviceOfferingsNodeIds.has(node.id) &&
|
|
195
|
+
!processedDocs.has(node.id));
|
|
196
|
+
// Move resource templates to Products folder
|
|
197
|
+
for (const fileNode of resourceTemplatesToMigrate) {
|
|
198
|
+
processedDocs.add(fileNode.id);
|
|
199
|
+
onMoveNode(fileNode, resourceTemplatesFolder).catch((error) => {
|
|
200
|
+
console.error(`Failed to migrate resource template:`, error);
|
|
201
|
+
processedDocs.delete(fileNode.id);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
// Move service offerings to Service Offerings folder
|
|
205
|
+
for (const fileNode of serviceOfferingsToMigrate) {
|
|
206
|
+
processedDocs.add(fileNode.id);
|
|
207
|
+
onMoveNode(fileNode, serviceOfferingsFolder).catch((error) => {
|
|
208
|
+
console.error(`Failed to migrate service offering:`, error);
|
|
209
|
+
processedDocs.delete(fileNode.id);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
// Delete old folders if they exist and are empty (after migration)
|
|
213
|
+
const checkAndDeleteOldFolders = () => {
|
|
214
|
+
// Check if old resource templates folder is empty and delete it
|
|
215
|
+
if (oldResourceTemplatesFolder) {
|
|
216
|
+
const childrenInOldResourceTemplates = allNodes.filter((node) => (isFolderNodeKind(node) || isFileNodeKind(node)) &&
|
|
217
|
+
node.parentFolder === oldResourceTemplatesFolder.id);
|
|
218
|
+
if (childrenInOldResourceTemplates.length === 0) {
|
|
219
|
+
dispatchActions(deleteNode({ id: oldResourceTemplatesFolder.id }), driveId).catch((error) => {
|
|
220
|
+
console.error(`Failed to delete old Resource Templates folder:`, error);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Check if old service offerings folder is empty and delete it
|
|
225
|
+
if (oldServiceOfferingsFolder) {
|
|
226
|
+
const childrenInOldServiceOfferings = allNodes.filter((node) => (isFolderNodeKind(node) || isFileNodeKind(node)) &&
|
|
227
|
+
node.parentFolder === oldServiceOfferingsFolder.id);
|
|
228
|
+
if (childrenInOldServiceOfferings.length === 0) {
|
|
229
|
+
dispatchActions(deleteNode({ id: oldServiceOfferingsFolder.id }), driveId).catch((error) => {
|
|
230
|
+
console.error(`Failed to delete old Service Offerings folder:`, error);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
globalCreationState.migratedOldFolders.add(driveId);
|
|
235
|
+
};
|
|
236
|
+
// Delay deletion to allow migrations to complete
|
|
237
|
+
if (resourceTemplatesToMigrate.length === 0 &&
|
|
238
|
+
serviceOfferingsToMigrate.length === 0) {
|
|
239
|
+
checkAndDeleteOldFolders();
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
setTimeout(checkAndDeleteOldFolders, 1000);
|
|
243
|
+
}
|
|
244
|
+
}, [
|
|
245
|
+
driveId,
|
|
246
|
+
driveDocument,
|
|
247
|
+
resourceTemplatesFolder,
|
|
248
|
+
serviceOfferingsFolder,
|
|
249
|
+
resourceTemplatesNodeIds,
|
|
250
|
+
serviceOfferingsNodeIds,
|
|
251
|
+
oldResourceTemplatesFolder,
|
|
252
|
+
oldServiceOfferingsFolder,
|
|
253
|
+
onMoveNode,
|
|
254
|
+
]);
|
|
115
255
|
return {
|
|
256
|
+
servicesAndOfferingsFolder,
|
|
116
257
|
resourceTemplatesFolder,
|
|
117
258
|
serviceOfferingsFolder,
|
|
118
259
|
resourceTemplatesNodeIds,
|
package/dist/style.css
CHANGED
|
@@ -80,7 +80,6 @@
|
|
|
80
80
|
--color-indigo-500: oklch(58.5% 0.233 277.117);
|
|
81
81
|
--color-indigo-600: oklch(51.1% 0.262 276.966);
|
|
82
82
|
--color-indigo-700: oklch(45.7% 0.24 277.023);
|
|
83
|
-
--color-indigo-800: oklch(39.8% 0.195 277.366);
|
|
84
83
|
--color-violet-50: oklch(96.9% 0.016 293.756);
|
|
85
84
|
--color-violet-500: oklch(60.6% 0.25 292.717);
|
|
86
85
|
--color-violet-600: oklch(54.1% 0.281 293.009);
|
|
@@ -2672,13 +2671,6 @@
|
|
|
2672
2671
|
}
|
|
2673
2672
|
}
|
|
2674
2673
|
}
|
|
2675
|
-
.hover\:text-indigo-800 {
|
|
2676
|
-
&:hover {
|
|
2677
|
-
@media (hover: hover) {
|
|
2678
|
-
color: var(--color-indigo-800);
|
|
2679
|
-
}
|
|
2680
|
-
}
|
|
2681
|
-
}
|
|
2682
2674
|
.hover\:text-red-500 {
|
|
2683
2675
|
&:hover {
|
|
2684
2676
|
@media (hover: hover) {
|
|
@@ -22,7 +22,7 @@ export const getResolvers = (subgraph) => {
|
|
|
22
22
|
if (operatorId && state.operatorId !== operatorId) {
|
|
23
23
|
return [];
|
|
24
24
|
}
|
|
25
|
-
return [mapResourceTemplateState(state)];
|
|
25
|
+
return [mapResourceTemplateState(state, doc)];
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
catch {
|
|
@@ -59,7 +59,7 @@ export const getResolvers = (subgraph) => {
|
|
|
59
59
|
if (operatorId && state.operatorId !== operatorId) {
|
|
60
60
|
continue;
|
|
61
61
|
}
|
|
62
|
-
resourceTemplates.push(mapResourceTemplateState(state));
|
|
62
|
+
resourceTemplates.push(mapResourceTemplateState(state, doc));
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -93,7 +93,7 @@ export const getResolvers = (subgraph) => {
|
|
|
93
93
|
state.resourceTemplateId !== resourceTemplateId) {
|
|
94
94
|
return [];
|
|
95
95
|
}
|
|
96
|
-
return [mapServiceOfferingState(state)];
|
|
96
|
+
return [mapServiceOfferingState(state, doc)];
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
catch {
|
|
@@ -135,7 +135,7 @@ export const getResolvers = (subgraph) => {
|
|
|
135
135
|
state.resourceTemplateId !== resourceTemplateId) {
|
|
136
136
|
continue;
|
|
137
137
|
}
|
|
138
|
-
serviceOfferings.push(mapServiceOfferingState(state));
|
|
138
|
+
serviceOfferings.push(mapServiceOfferingState(state, doc));
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -151,9 +151,9 @@ export const getResolvers = (subgraph) => {
|
|
|
151
151
|
/**
|
|
152
152
|
* Map ResourceTemplateState from document model to GraphQL response
|
|
153
153
|
*/
|
|
154
|
-
function mapResourceTemplateState(state) {
|
|
154
|
+
function mapResourceTemplateState(state, doc) {
|
|
155
155
|
return {
|
|
156
|
-
id:
|
|
156
|
+
id: doc.header.id,
|
|
157
157
|
operatorId: state.operatorId,
|
|
158
158
|
title: state.title,
|
|
159
159
|
summary: state.summary,
|
|
@@ -214,9 +214,9 @@ function mapResourceTemplateState(state) {
|
|
|
214
214
|
/**
|
|
215
215
|
* Map ServiceOfferingState from document model to GraphQL response
|
|
216
216
|
*/
|
|
217
|
-
function mapServiceOfferingState(state) {
|
|
217
|
+
function mapServiceOfferingState(state, doc) {
|
|
218
218
|
return {
|
|
219
|
-
id:
|
|
219
|
+
id: doc.header.id,
|
|
220
220
|
operatorId: state.operatorId,
|
|
221
221
|
resourceTemplateId: state.resourceTemplateId || null,
|
|
222
222
|
title: state.title,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/contributor-billing",
|
|
3
3
|
"description": "Document models that help contributors of open organisations get paid anonymously for their work on a monthly basis.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.47",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|