@apvee/spfx-react-toolkit 1.0.0
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/LICENSE +21 -0
- package/README.md +2012 -0
- package/lib/core/atoms.internal.d.ts +53 -0
- package/lib/core/atoms.internal.d.ts.map +1 -0
- package/lib/core/atoms.internal.js +35 -0
- package/lib/core/atoms.internal.js.map +1 -0
- package/lib/core/context.internal.d.ts +23 -0
- package/lib/core/context.internal.d.ts.map +1 -0
- package/lib/core/context.internal.js +34 -0
- package/lib/core/context.internal.js.map +1 -0
- package/lib/core/index.d.ts +6 -0
- package/lib/core/index.d.ts.map +1 -0
- package/lib/core/index.js +6 -0
- package/lib/core/index.js.map +1 -0
- package/lib/core/provider-application-customizer.d.ts +57 -0
- package/lib/core/provider-application-customizer.d.ts.map +1 -0
- package/lib/core/provider-application-customizer.js +45 -0
- package/lib/core/provider-application-customizer.js.map +1 -0
- package/lib/core/provider-base.internal.d.ts +20 -0
- package/lib/core/provider-base.internal.d.ts.map +1 -0
- package/lib/core/provider-base.internal.js +126 -0
- package/lib/core/provider-base.internal.js.map +1 -0
- package/lib/core/provider-field-customizer.d.ts +58 -0
- package/lib/core/provider-field-customizer.d.ts.map +1 -0
- package/lib/core/provider-field-customizer.js +46 -0
- package/lib/core/provider-field-customizer.js.map +1 -0
- package/lib/core/provider-listview-commandset.d.ts +60 -0
- package/lib/core/provider-listview-commandset.d.ts.map +1 -0
- package/lib/core/provider-listview-commandset.js +48 -0
- package/lib/core/provider-listview-commandset.js.map +1 -0
- package/lib/core/provider-webpart.d.ts +48 -0
- package/lib/core/provider-webpart.d.ts.map +1 -0
- package/lib/core/provider-webpart.js +36 -0
- package/lib/core/provider-webpart.js.map +1 -0
- package/lib/core/types.d.ts +84 -0
- package/lib/core/types.d.ts.map +1 -0
- package/lib/core/types.js +4 -0
- package/lib/core/types.js.map +1 -0
- package/lib/hooks/index.d.ts +34 -0
- package/lib/hooks/index.d.ts.map +1 -0
- package/lib/hooks/index.js +34 -0
- package/lib/hooks/index.js.map +1 -0
- package/lib/hooks/useSPFxAadHttpClient.d.ts +231 -0
- package/lib/hooks/useSPFxAadHttpClient.d.ts.map +1 -0
- package/lib/hooks/useSPFxAadHttpClient.js +299 -0
- package/lib/hooks/useSPFxAadHttpClient.js.map +1 -0
- package/lib/hooks/useSPFxContainerInfo.d.ts +41 -0
- package/lib/hooks/useSPFxContainerInfo.d.ts.map +1 -0
- package/lib/hooks/useSPFxContainerInfo.js +47 -0
- package/lib/hooks/useSPFxContainerInfo.js.map +1 -0
- package/lib/hooks/useSPFxContainerSize.d.ts +119 -0
- package/lib/hooks/useSPFxContainerSize.d.ts.map +1 -0
- package/lib/hooks/useSPFxContainerSize.js +150 -0
- package/lib/hooks/useSPFxContainerSize.js.map +1 -0
- package/lib/hooks/useSPFxContext.d.ts +14 -0
- package/lib/hooks/useSPFxContext.d.ts.map +1 -0
- package/lib/hooks/useSPFxContext.js +16 -0
- package/lib/hooks/useSPFxContext.js.map +1 -0
- package/lib/hooks/useSPFxCorrelationInfo.d.ts +51 -0
- package/lib/hooks/useSPFxCorrelationInfo.d.ts.map +1 -0
- package/lib/hooks/useSPFxCorrelationInfo.js +58 -0
- package/lib/hooks/useSPFxCorrelationInfo.js.map +1 -0
- package/lib/hooks/useSPFxCrossSitePermissions.d.ts +81 -0
- package/lib/hooks/useSPFxCrossSitePermissions.d.ts.map +1 -0
- package/lib/hooks/useSPFxCrossSitePermissions.js +132 -0
- package/lib/hooks/useSPFxCrossSitePermissions.js.map +1 -0
- package/lib/hooks/useSPFxDisplayMode.d.ts +61 -0
- package/lib/hooks/useSPFxDisplayMode.d.ts.map +1 -0
- package/lib/hooks/useSPFxDisplayMode.js +69 -0
- package/lib/hooks/useSPFxDisplayMode.js.map +1 -0
- package/lib/hooks/useSPFxEnvironmentInfo.d.ts +63 -0
- package/lib/hooks/useSPFxEnvironmentInfo.d.ts.map +1 -0
- package/lib/hooks/useSPFxEnvironmentInfo.js +91 -0
- package/lib/hooks/useSPFxEnvironmentInfo.js.map +1 -0
- package/lib/hooks/useSPFxFluent9ThemeInfo.d.ts +105 -0
- package/lib/hooks/useSPFxFluent9ThemeInfo.d.ts.map +1 -0
- package/lib/hooks/useSPFxFluent9ThemeInfo.js +136 -0
- package/lib/hooks/useSPFxFluent9ThemeInfo.js.map +1 -0
- package/lib/hooks/useSPFxHubSiteInfo.d.ts +80 -0
- package/lib/hooks/useSPFxHubSiteInfo.d.ts.map +1 -0
- package/lib/hooks/useSPFxHubSiteInfo.js +127 -0
- package/lib/hooks/useSPFxHubSiteInfo.js.map +1 -0
- package/lib/hooks/useSPFxInstanceInfo.d.ts +41 -0
- package/lib/hooks/useSPFxInstanceInfo.d.ts.map +1 -0
- package/lib/hooks/useSPFxInstanceInfo.js +40 -0
- package/lib/hooks/useSPFxInstanceInfo.js.map +1 -0
- package/lib/hooks/useSPFxListInfo.d.ts +64 -0
- package/lib/hooks/useSPFxListInfo.d.ts.map +1 -0
- package/lib/hooks/useSPFxListInfo.js +70 -0
- package/lib/hooks/useSPFxListInfo.js.map +1 -0
- package/lib/hooks/useSPFxLocaleInfo.d.ts +123 -0
- package/lib/hooks/useSPFxLocaleInfo.d.ts.map +1 -0
- package/lib/hooks/useSPFxLocaleInfo.js +109 -0
- package/lib/hooks/useSPFxLocaleInfo.js.map +1 -0
- package/lib/hooks/useSPFxLogger.d.ts +108 -0
- package/lib/hooks/useSPFxLogger.d.ts.map +1 -0
- package/lib/hooks/useSPFxLogger.js +117 -0
- package/lib/hooks/useSPFxLogger.js.map +1 -0
- package/lib/hooks/useSPFxMSGraphClient.d.ts +200 -0
- package/lib/hooks/useSPFxMSGraphClient.d.ts.map +1 -0
- package/lib/hooks/useSPFxMSGraphClient.js +264 -0
- package/lib/hooks/useSPFxMSGraphClient.js.map +1 -0
- package/lib/hooks/useSPFxOneDriveAppData.d.ts +264 -0
- package/lib/hooks/useSPFxOneDriveAppData.d.ts.map +1 -0
- package/lib/hooks/useSPFxOneDriveAppData.js +395 -0
- package/lib/hooks/useSPFxOneDriveAppData.js.map +1 -0
- package/lib/hooks/useSPFxPageContext.d.ts +37 -0
- package/lib/hooks/useSPFxPageContext.d.ts.map +1 -0
- package/lib/hooks/useSPFxPageContext.js +49 -0
- package/lib/hooks/useSPFxPageContext.js.map +1 -0
- package/lib/hooks/useSPFxPageType.d.ts +82 -0
- package/lib/hooks/useSPFxPageType.d.ts.map +1 -0
- package/lib/hooks/useSPFxPageType.js +137 -0
- package/lib/hooks/useSPFxPageType.js.map +1 -0
- package/lib/hooks/useSPFxPerformance.d.ts +72 -0
- package/lib/hooks/useSPFxPerformance.d.ts.map +1 -0
- package/lib/hooks/useSPFxPerformance.js +167 -0
- package/lib/hooks/useSPFxPerformance.js.map +1 -0
- package/lib/hooks/useSPFxPermissions.d.ts +61 -0
- package/lib/hooks/useSPFxPermissions.d.ts.map +1 -0
- package/lib/hooks/useSPFxPermissions.js +73 -0
- package/lib/hooks/useSPFxPermissions.js.map +1 -0
- package/lib/hooks/useSPFxPnP.d.ts +539 -0
- package/lib/hooks/useSPFxPnP.d.ts.map +1 -0
- package/lib/hooks/useSPFxPnP.js +533 -0
- package/lib/hooks/useSPFxPnP.js.map +1 -0
- package/lib/hooks/useSPFxPnPContext.d.ts +290 -0
- package/lib/hooks/useSPFxPnPContext.d.ts.map +1 -0
- package/lib/hooks/useSPFxPnPContext.js +340 -0
- package/lib/hooks/useSPFxPnPContext.js.map +1 -0
- package/lib/hooks/useSPFxPnPList.d.ts +545 -0
- package/lib/hooks/useSPFxPnPList.d.ts.map +1 -0
- package/lib/hooks/useSPFxPnPList.js +906 -0
- package/lib/hooks/useSPFxPnPList.js.map +1 -0
- package/lib/hooks/useSPFxPnPSearch.d.ts +540 -0
- package/lib/hooks/useSPFxPnPSearch.d.ts.map +1 -0
- package/lib/hooks/useSPFxPnPSearch.js +672 -0
- package/lib/hooks/useSPFxPnPSearch.js.map +1 -0
- package/lib/hooks/useSPFxProperties.d.ts +80 -0
- package/lib/hooks/useSPFxProperties.d.ts.map +1 -0
- package/lib/hooks/useSPFxProperties.js +95 -0
- package/lib/hooks/useSPFxProperties.js.map +1 -0
- package/lib/hooks/useSPFxSPHttpClient.d.ts +218 -0
- package/lib/hooks/useSPFxSPHttpClient.d.ts.map +1 -0
- package/lib/hooks/useSPFxSPHttpClient.js +287 -0
- package/lib/hooks/useSPFxSPHttpClient.js.map +1 -0
- package/lib/hooks/useSPFxServiceScope.d.ts +107 -0
- package/lib/hooks/useSPFxServiceScope.d.ts.map +1 -0
- package/lib/hooks/useSPFxServiceScope.js +105 -0
- package/lib/hooks/useSPFxServiceScope.js.map +1 -0
- package/lib/hooks/useSPFxSiteInfo.d.ts +116 -0
- package/lib/hooks/useSPFxSiteInfo.d.ts.map +1 -0
- package/lib/hooks/useSPFxSiteInfo.js +109 -0
- package/lib/hooks/useSPFxSiteInfo.js.map +1 -0
- package/lib/hooks/useSPFxStorage.d.ts +81 -0
- package/lib/hooks/useSPFxStorage.d.ts.map +1 -0
- package/lib/hooks/useSPFxStorage.js +140 -0
- package/lib/hooks/useSPFxStorage.js.map +1 -0
- package/lib/hooks/useSPFxTeams.d.ts +63 -0
- package/lib/hooks/useSPFxTeams.d.ts.map +1 -0
- package/lib/hooks/useSPFxTeams.js +198 -0
- package/lib/hooks/useSPFxTeams.js.map +1 -0
- package/lib/hooks/useSPFxTenantProperty.d.ts +389 -0
- package/lib/hooks/useSPFxTenantProperty.d.ts.map +1 -0
- package/lib/hooks/useSPFxTenantProperty.js +683 -0
- package/lib/hooks/useSPFxTenantProperty.js.map +1 -0
- package/lib/hooks/useSPFxThemeInfo.d.ts +27 -0
- package/lib/hooks/useSPFxThemeInfo.d.ts.map +1 -0
- package/lib/hooks/useSPFxThemeInfo.js +33 -0
- package/lib/hooks/useSPFxThemeInfo.js.map +1 -0
- package/lib/hooks/useSPFxUserInfo.d.ts +47 -0
- package/lib/hooks/useSPFxUserInfo.d.ts.map +1 -0
- package/lib/hooks/useSPFxUserInfo.js +47 -0
- package/lib/hooks/useSPFxUserInfo.js.map +1 -0
- package/lib/hooks/useSPFxUserPhoto.d.ts +270 -0
- package/lib/hooks/useSPFxUserPhoto.d.ts.map +1 -0
- package/lib/hooks/useSPFxUserPhoto.js +346 -0
- package/lib/hooks/useSPFxUserPhoto.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -0
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/index.js +3 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/resize-observer.internal.d.ts +10 -0
- package/lib/utils/resize-observer.internal.d.ts.map +1 -0
- package/lib/utils/resize-observer.internal.js +34 -0
- package/lib/utils/resize-observer.internal.js.map +1 -0
- package/lib/utils/theme-subscription.internal.d.ts +11 -0
- package/lib/utils/theme-subscription.internal.d.ts.map +1 -0
- package/lib/utils/theme-subscription.internal.js +58 -0
- package/lib/utils/theme-subscription.internal.js.map +1 -0
- package/lib/utils/type-guards.internal.d.ts +35 -0
- package/lib/utils/type-guards.internal.d.ts.map +1 -0
- package/lib/utils/type-guards.internal.js +88 -0
- package/lib/utils/type-guards.internal.js.map +1 -0
- package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.d.ts +13 -0
- package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.d.ts.map +1 -0
- package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.js +67 -0
- package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.js.map +1 -0
- package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.manifest.json +21 -0
- package/lib/webparts/spFxReactToolkitTest/assets/welcome-dark.png +0 -0
- package/lib/webparts/spFxReactToolkitTest/assets/welcome-light.png +0 -0
- package/lib/webparts/spFxReactToolkitTest/components/ISpFxReactToolkitTestProps.d.ts +8 -0
- package/lib/webparts/spFxReactToolkitTest/components/ISpFxReactToolkitTestProps.d.ts.map +1 -0
- package/lib/webparts/spFxReactToolkitTest/components/ISpFxReactToolkitTestProps.js +2 -0
- package/lib/webparts/spFxReactToolkitTest/components/ISpFxReactToolkitTestProps.js.map +1 -0
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.d.ts +8 -0
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.d.ts.map +1 -0
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.js +1351 -0
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.js.map +1 -0
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.css +2 -0
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.d.ts +18 -0
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.d.ts.map +1 -0
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.js +19 -0
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.js.map +1 -0
- package/lib/webparts/spFxReactToolkitTest/loc/en-us.js +16 -0
- package/package.json +95 -0
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
// useSPFxTenantProperty.ts
|
|
2
|
+
// Hook to manage tenant-wide properties using SharePoint StorageEntity API
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
13
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
14
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
15
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
16
|
+
function step(op) {
|
|
17
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
18
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
19
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
20
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
21
|
+
switch (op[0]) {
|
|
22
|
+
case 0: case 1: t = op; break;
|
|
23
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
24
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
25
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
26
|
+
default:
|
|
27
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
28
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
29
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
30
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
31
|
+
if (t[2]) _.ops.pop();
|
|
32
|
+
_.trys.pop(); continue;
|
|
33
|
+
}
|
|
34
|
+
op = body.call(thisArg, _);
|
|
35
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
36
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
40
|
+
import { useSPFxSPHttpClient } from './useSPFxSPHttpClient';
|
|
41
|
+
import { useSPFxPageContext } from './useSPFxPageContext';
|
|
42
|
+
import { SPHttpClient } from '@microsoft/sp-http';
|
|
43
|
+
/**
|
|
44
|
+
* Hook to manage tenant-wide properties using SharePoint StorageEntity API
|
|
45
|
+
*
|
|
46
|
+
* Provides read/write operations for tenant-scoped properties stored in the
|
|
47
|
+
* SharePoint tenant app catalog. Properties are accessible across all sites
|
|
48
|
+
* in the tenant and support metadata (description only).
|
|
49
|
+
*
|
|
50
|
+
* Features:
|
|
51
|
+
* - Tenant-wide centralized storage (not site-specific)
|
|
52
|
+
* - Smart serialization for primitives, Date, and complex objects
|
|
53
|
+
* - Permission checking (canWrite flag)
|
|
54
|
+
* - Optional metadata (description only - SharePoint limitation)
|
|
55
|
+
* - Type-safe with TypeScript generics
|
|
56
|
+
* - Memory leak safe with mounted state tracking
|
|
57
|
+
* - Automatic app catalog URL discovery
|
|
58
|
+
* - Remove operation for cleanup
|
|
59
|
+
*
|
|
60
|
+
* Requirements:
|
|
61
|
+
* - Tenant app catalog must be provisioned
|
|
62
|
+
* - Read: Any authenticated user
|
|
63
|
+
* - Write/Remove: Site Collection Administrator role on tenant app catalog site
|
|
64
|
+
*
|
|
65
|
+
* Permission Notes:
|
|
66
|
+
* - Being a Tenant Admin is NOT sufficient for write operations
|
|
67
|
+
* - You must be explicitly added as Site Collection Administrator to the tenant app catalog
|
|
68
|
+
* - Navigate to the app catalog site → Site Settings → Site Collection Administrators
|
|
69
|
+
* - Add your user account if write operations fail with permission errors
|
|
70
|
+
*
|
|
71
|
+
* Storage Format:
|
|
72
|
+
* - Primitives (string, number, boolean, null) → stored as string
|
|
73
|
+
* - Date → stored as ISO 8601 string
|
|
74
|
+
* - Objects/arrays → stored as JSON string
|
|
75
|
+
*
|
|
76
|
+
* @param key - Unique property key (e.g., 'appVersion', 'apiEndpoint', 'featureFlags')
|
|
77
|
+
* @param autoFetch - Whether to automatically load property on mount. Default: true
|
|
78
|
+
*
|
|
79
|
+
* @returns Object with data, metadata, loading states, error states, and CRUD functions
|
|
80
|
+
*
|
|
81
|
+
* @example Basic usage - string property
|
|
82
|
+
* ```tsx
|
|
83
|
+
* function VersionDisplay() {
|
|
84
|
+
* const { data, isLoading, error, write, canWrite } =
|
|
85
|
+
* useSPFxTenantProperty<string>('appVersion');
|
|
86
|
+
*
|
|
87
|
+
* if (isLoading) return <Spinner label="Loading version..." />;
|
|
88
|
+
* if (error) return <MessageBar messageBarType={MessageBarType.error}>
|
|
89
|
+
* Failed to load: {error.message}
|
|
90
|
+
* </MessageBar>;
|
|
91
|
+
*
|
|
92
|
+
* const handleUpdate = async () => {
|
|
93
|
+
* if (!canWrite) {
|
|
94
|
+
* alert('Insufficient permissions');
|
|
95
|
+
* return;
|
|
96
|
+
* }
|
|
97
|
+
*
|
|
98
|
+
* try {
|
|
99
|
+
* await write('2.0.1', 'Current application version');
|
|
100
|
+
* console.log('Version updated!');
|
|
101
|
+
* } catch (err) {
|
|
102
|
+
* console.error('Update failed:', err);
|
|
103
|
+
* }
|
|
104
|
+
* };
|
|
105
|
+
*
|
|
106
|
+
* return (
|
|
107
|
+
* <div>
|
|
108
|
+
* <Text>Current Version: {data ?? 'Not Set'}</Text>
|
|
109
|
+
* {canWrite && <PrimaryButton onClick={handleUpdate}>Update Version</PrimaryButton>}
|
|
110
|
+
* </div>
|
|
111
|
+
* );
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* @example Number property
|
|
116
|
+
* ```tsx
|
|
117
|
+
* const { data, write } = useSPFxTenantProperty<number>('maxUploadSize');
|
|
118
|
+
*
|
|
119
|
+
* // Stored as "10485760" (10MB in bytes)
|
|
120
|
+
* await write(10485760, 'Maximum file upload size in bytes');
|
|
121
|
+
*
|
|
122
|
+
* // Read returns: 10485760 (number)
|
|
123
|
+
* console.log(typeof data); // "number"
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* @example Boolean flag
|
|
127
|
+
* ```tsx
|
|
128
|
+
* const { data: maintenanceMode, write } = useSPFxTenantProperty<boolean>('maintenanceMode');
|
|
129
|
+
*
|
|
130
|
+
* // Stored as "true" or "false"
|
|
131
|
+
* await write(true, 'Maintenance mode enabled');
|
|
132
|
+
*
|
|
133
|
+
* if (maintenanceMode) {
|
|
134
|
+
* return <MessageBar>System is under maintenance</MessageBar>;
|
|
135
|
+
* }
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* @example Date property
|
|
139
|
+
* ```tsx
|
|
140
|
+
* const { data, write } = useSPFxTenantProperty<string>('lastDeployment');
|
|
141
|
+
*
|
|
142
|
+
* // Write date as ISO string
|
|
143
|
+
* await write(new Date().toISOString(), 'Last deployment timestamp');
|
|
144
|
+
*
|
|
145
|
+
* // Read and convert back to Date
|
|
146
|
+
* const lastDeploy = data ? new Date(data) : undefined;
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* @example Complex object with JSON
|
|
150
|
+
* ```tsx
|
|
151
|
+
* interface FeatureFlags {
|
|
152
|
+
* enableChat: boolean;
|
|
153
|
+
* enableAnalytics: boolean;
|
|
154
|
+
* maxUsers: number;
|
|
155
|
+
* }
|
|
156
|
+
*
|
|
157
|
+
* const { data, write, isLoading } = useSPFxTenantProperty<FeatureFlags>('featureFlags');
|
|
158
|
+
*
|
|
159
|
+
* // Stored as JSON string
|
|
160
|
+
* await write({
|
|
161
|
+
* enableChat: true,
|
|
162
|
+
* enableAnalytics: false,
|
|
163
|
+
* maxUsers: 1000
|
|
164
|
+
* }, 'Global feature flags configuration');
|
|
165
|
+
*
|
|
166
|
+
* // Read returns parsed object
|
|
167
|
+
* if (data?.enableChat) {
|
|
168
|
+
* return <ChatPanel />;
|
|
169
|
+
* }
|
|
170
|
+
* ```
|
|
171
|
+
*
|
|
172
|
+
* @example Permission-aware UI
|
|
173
|
+
* ```tsx
|
|
174
|
+
* function TenantConfigPanel() {
|
|
175
|
+
* const { data, canWrite, write, isWriting, error, writeError } =
|
|
176
|
+
* useSPFxTenantProperty<string>('apiEndpoint');
|
|
177
|
+
*
|
|
178
|
+
* const [editValue, setEditValue] = React.useState(data ?? '');
|
|
179
|
+
*
|
|
180
|
+
* React.useEffect(() => {
|
|
181
|
+
* setEditValue(data ?? '');
|
|
182
|
+
* }, [data]);
|
|
183
|
+
*
|
|
184
|
+
* if (!canWrite) {
|
|
185
|
+
* return (
|
|
186
|
+
* <MessageBar messageBarType={MessageBarType.info}>
|
|
187
|
+
* You don't have permission to edit tenant properties.
|
|
188
|
+
* Contact your SharePoint administrator.
|
|
189
|
+
* </MessageBar>
|
|
190
|
+
* );
|
|
191
|
+
* }
|
|
192
|
+
*
|
|
193
|
+
* const handleSave = async () => {
|
|
194
|
+
* try {
|
|
195
|
+
* await write(editValue, 'Production API endpoint URL');
|
|
196
|
+
* } catch (err) {
|
|
197
|
+
* console.error('Save failed:', err);
|
|
198
|
+
* }
|
|
199
|
+
* };
|
|
200
|
+
*
|
|
201
|
+
* return (
|
|
202
|
+
* <Stack tokens={{ childrenGap: 10 }}>
|
|
203
|
+
* <TextField
|
|
204
|
+
* label="API Endpoint"
|
|
205
|
+
* value={editValue}
|
|
206
|
+
* onChange={(_, val) => setEditValue(val ?? '')}
|
|
207
|
+
* disabled={isWriting}
|
|
208
|
+
* />
|
|
209
|
+
* <PrimaryButton
|
|
210
|
+
* onClick={handleSave}
|
|
211
|
+
* disabled={isWriting || editValue === data}
|
|
212
|
+
* >
|
|
213
|
+
* {isWriting ? 'Saving...' : 'Save'}
|
|
214
|
+
* </PrimaryButton>
|
|
215
|
+
* {writeError && (
|
|
216
|
+
* <MessageBar messageBarType={MessageBarType.error}>
|
|
217
|
+
* {writeError.message}
|
|
218
|
+
* </MessageBar>
|
|
219
|
+
* )}
|
|
220
|
+
* </Stack>
|
|
221
|
+
* );
|
|
222
|
+
* }
|
|
223
|
+
* ```
|
|
224
|
+
*
|
|
225
|
+
* @example With metadata viewing
|
|
226
|
+
* ```tsx
|
|
227
|
+
* function PropertyViewer() {
|
|
228
|
+
* const { data, description, isLoading } =
|
|
229
|
+
* useSPFxTenantProperty<string>('appConfig');
|
|
230
|
+
*
|
|
231
|
+
* if (isLoading) return <Spinner />;
|
|
232
|
+
*
|
|
233
|
+
* return (
|
|
234
|
+
* <Stack tokens={{ childrenGap: 5 }}>
|
|
235
|
+
* <Text variant="large">Value: {data}</Text>
|
|
236
|
+
* {description && <Text variant="small">Description: {description}</Text>}
|
|
237
|
+
* </Stack>
|
|
238
|
+
* );
|
|
239
|
+
* }
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
242
|
+
* @example Lazy loading with manual trigger
|
|
243
|
+
* ```tsx
|
|
244
|
+
* const { data, load, isLoading, write } = useSPFxTenantProperty<Config>(
|
|
245
|
+
* 'appConfig',
|
|
246
|
+
* false // Don't auto-fetch
|
|
247
|
+
* );
|
|
248
|
+
*
|
|
249
|
+
* return (
|
|
250
|
+
* <div>
|
|
251
|
+
* <button onClick={load} disabled={isLoading}>
|
|
252
|
+
* {isLoading ? 'Loading...' : 'Load Config'}
|
|
253
|
+
* </button>
|
|
254
|
+
* {data && <ConfigDisplay config={data} />}
|
|
255
|
+
* </div>
|
|
256
|
+
* );
|
|
257
|
+
* ```
|
|
258
|
+
*
|
|
259
|
+
* @example Property removal
|
|
260
|
+
* ```tsx
|
|
261
|
+
* function PropertyManager() {
|
|
262
|
+
* const { data, remove, canWrite } = useSPFxTenantProperty<string>('deprecatedSetting');
|
|
263
|
+
*
|
|
264
|
+
* const handleDelete = async () => {
|
|
265
|
+
* if (!confirm('Delete this property? This cannot be undone.')) return;
|
|
266
|
+
*
|
|
267
|
+
* try {
|
|
268
|
+
* await remove();
|
|
269
|
+
* console.log('Property deleted');
|
|
270
|
+
* } catch (err) {
|
|
271
|
+
* console.error('Delete failed:', err);
|
|
272
|
+
* }
|
|
273
|
+
* };
|
|
274
|
+
*
|
|
275
|
+
* if (!data) return <Text>Property not found</Text>;
|
|
276
|
+
*
|
|
277
|
+
* return (
|
|
278
|
+
* <Stack tokens={{ childrenGap: 10 }}>
|
|
279
|
+
* <Text>Value: {data}</Text>
|
|
280
|
+
* {canWrite && (
|
|
281
|
+
* <DefaultButton onClick={handleDelete} text="Delete Property" />
|
|
282
|
+
* )}
|
|
283
|
+
* </Stack>
|
|
284
|
+
* );
|
|
285
|
+
* }
|
|
286
|
+
* ```
|
|
287
|
+
*
|
|
288
|
+
* @example Multi-property dashboard
|
|
289
|
+
* ```tsx
|
|
290
|
+
* function TenantDashboard() {
|
|
291
|
+
* const version = useSPFxTenantProperty<string>('appVersion');
|
|
292
|
+
* const maintenance = useSPFxTenantProperty<boolean>('maintenanceMode');
|
|
293
|
+
* const lastUpdate = useSPFxTenantProperty<string>('lastUpdate');
|
|
294
|
+
* const config = useSPFxTenantProperty<AppConfig>('appConfig');
|
|
295
|
+
*
|
|
296
|
+
* const isLoading = version.isLoading || maintenance.isLoading ||
|
|
297
|
+
* lastUpdate.isLoading || config.isLoading;
|
|
298
|
+
*
|
|
299
|
+
* if (isLoading) return <Spinner label="Loading dashboard..." />;
|
|
300
|
+
*
|
|
301
|
+
* return (
|
|
302
|
+
* <Stack tokens={{ childrenGap: 20 }}>
|
|
303
|
+
* <Text variant="xxLarge">Tenant Configuration</Text>
|
|
304
|
+
*
|
|
305
|
+
* <Stack tokens={{ childrenGap: 10 }}>
|
|
306
|
+
* <Label>App Version: {version.data ?? 'Not Set'}</Label>
|
|
307
|
+
* <Label>Maintenance Mode: {maintenance.data ? 'ON' : 'OFF'}</Label>
|
|
308
|
+
* <Label>Last Update: {lastUpdate.data ? new Date(lastUpdate.data).toLocaleString() : 'Never'}</Label>
|
|
309
|
+
* </Stack>
|
|
310
|
+
*
|
|
311
|
+
* {config.data && (
|
|
312
|
+
* <Stack tokens={{ childrenGap: 5 }}>
|
|
313
|
+
* <Text variant="large">Configuration</Text>
|
|
314
|
+
* <pre>{JSON.stringify(config.data, null, 2)}</pre>
|
|
315
|
+
* </Stack>
|
|
316
|
+
* )}
|
|
317
|
+
* </Stack>
|
|
318
|
+
* );
|
|
319
|
+
* }
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
export function useSPFxTenantProperty(key, autoFetch) {
|
|
323
|
+
var _this = this;
|
|
324
|
+
if (autoFetch === void 0) { autoFetch = true; }
|
|
325
|
+
var spHttpClient = useSPFxSPHttpClient().client;
|
|
326
|
+
var pageContext = useSPFxPageContext();
|
|
327
|
+
// State management
|
|
328
|
+
var _a = useState(undefined), data = _a[0], setData = _a[1];
|
|
329
|
+
var _b = useState(undefined), description = _b[0], setDescription = _b[1];
|
|
330
|
+
var _c = useState(false), isLoading = _c[0], setIsLoading = _c[1];
|
|
331
|
+
var _d = useState(undefined), error = _d[0], setError = _d[1];
|
|
332
|
+
var _e = useState(false), isWriting = _e[0], setIsWriting = _e[1];
|
|
333
|
+
var _f = useState(undefined), writeError = _f[0], setWriteError = _f[1];
|
|
334
|
+
var _g = useState(false), canWrite = _g[0], setCanWrite = _g[1];
|
|
335
|
+
var _h = useState(undefined), appCatalogUrl = _h[0], setAppCatalogUrl = _h[1];
|
|
336
|
+
// Track component mounted state to prevent memory leaks
|
|
337
|
+
var isMounted = useRef(true);
|
|
338
|
+
useEffect(function () {
|
|
339
|
+
isMounted.current = true;
|
|
340
|
+
return function () {
|
|
341
|
+
isMounted.current = false;
|
|
342
|
+
};
|
|
343
|
+
}, []);
|
|
344
|
+
/**
|
|
345
|
+
* Check if value is a primitive type (including null, Date)
|
|
346
|
+
*/
|
|
347
|
+
var isPrimitive = useCallback(function (val) {
|
|
348
|
+
if (val === null)
|
|
349
|
+
return true;
|
|
350
|
+
var type = typeof val;
|
|
351
|
+
// ES5-compatible array check
|
|
352
|
+
var primitiveTypes = ['string', 'number', 'boolean', 'bigint'];
|
|
353
|
+
if (primitiveTypes.indexOf(type) !== -1)
|
|
354
|
+
return true;
|
|
355
|
+
// Date is special: treat as primitive and convert to ISO string
|
|
356
|
+
if (val instanceof Date)
|
|
357
|
+
return true;
|
|
358
|
+
return false;
|
|
359
|
+
}, []);
|
|
360
|
+
/**
|
|
361
|
+
* Serialize content for storage
|
|
362
|
+
* - Primitives → String(content)
|
|
363
|
+
* - Date → ISO string
|
|
364
|
+
* - Objects/arrays → JSON.stringify()
|
|
365
|
+
*/
|
|
366
|
+
var serializeValue = useCallback(function (content) {
|
|
367
|
+
if (isPrimitive(content)) {
|
|
368
|
+
return content instanceof Date ? content.toISOString() : String(content);
|
|
369
|
+
}
|
|
370
|
+
return JSON.stringify(content);
|
|
371
|
+
}, [isPrimitive]);
|
|
372
|
+
/**
|
|
373
|
+
* Deserialize value from storage
|
|
374
|
+
* - Try JSON.parse first
|
|
375
|
+
* - If fails, return raw string (will be cast to T by TypeScript)
|
|
376
|
+
*/
|
|
377
|
+
var deserializeValue = useCallback(function (rawValue) {
|
|
378
|
+
try {
|
|
379
|
+
return JSON.parse(rawValue);
|
|
380
|
+
}
|
|
381
|
+
catch (_a) {
|
|
382
|
+
// Not valid JSON, assume it's a primitive value
|
|
383
|
+
return rawValue;
|
|
384
|
+
}
|
|
385
|
+
}, []);
|
|
386
|
+
/**
|
|
387
|
+
* Discover tenant app catalog URL
|
|
388
|
+
*/
|
|
389
|
+
var discoverAppCatalogUrl = useCallback(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
390
|
+
var response, data_1, err_1;
|
|
391
|
+
return __generator(this, function (_a) {
|
|
392
|
+
switch (_a.label) {
|
|
393
|
+
case 0:
|
|
394
|
+
if (!spHttpClient || !pageContext) {
|
|
395
|
+
throw new Error('SPHttpClient or PageContext not available');
|
|
396
|
+
}
|
|
397
|
+
// Check cache first
|
|
398
|
+
if (appCatalogUrl) {
|
|
399
|
+
return [2 /*return*/, appCatalogUrl];
|
|
400
|
+
}
|
|
401
|
+
_a.label = 1;
|
|
402
|
+
case 1:
|
|
403
|
+
_a.trys.push([1, 4, , 5]);
|
|
404
|
+
return [4 /*yield*/, spHttpClient.get("".concat(pageContext.web.absoluteUrl, "/_api/SP_TenantSettings_Current"), SPHttpClient.configurations.v1)];
|
|
405
|
+
case 2:
|
|
406
|
+
response = _a.sent();
|
|
407
|
+
if (!response.ok) {
|
|
408
|
+
throw new Error("Failed to discover app catalog: ".concat(response.statusText));
|
|
409
|
+
}
|
|
410
|
+
return [4 /*yield*/, response.json()];
|
|
411
|
+
case 3:
|
|
412
|
+
data_1 = _a.sent();
|
|
413
|
+
if (!data_1.CorporateCatalogUrl) {
|
|
414
|
+
throw new Error('Tenant app catalog is not provisioned. Please provision the app catalog first.');
|
|
415
|
+
}
|
|
416
|
+
if (isMounted.current) {
|
|
417
|
+
setAppCatalogUrl(data_1.CorporateCatalogUrl);
|
|
418
|
+
}
|
|
419
|
+
return [2 /*return*/, data_1.CorporateCatalogUrl];
|
|
420
|
+
case 4:
|
|
421
|
+
err_1 = _a.sent();
|
|
422
|
+
throw new Error("App catalog discovery failed: ".concat(err_1 instanceof Error ? err_1.message : String(err_1)));
|
|
423
|
+
case 5: return [2 /*return*/];
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}); }, [spHttpClient, pageContext, appCatalogUrl]);
|
|
427
|
+
/**
|
|
428
|
+
* Check write permissions on tenant app catalog
|
|
429
|
+
* User must be Site Collection Administrator of the tenant app catalog
|
|
430
|
+
*/
|
|
431
|
+
var checkWritePermission = useCallback(function (catalogUrl) { return __awaiter(_this, void 0, void 0, function () {
|
|
432
|
+
var response, user, _a;
|
|
433
|
+
return __generator(this, function (_b) {
|
|
434
|
+
switch (_b.label) {
|
|
435
|
+
case 0:
|
|
436
|
+
if (!spHttpClient)
|
|
437
|
+
return [2 /*return*/, false];
|
|
438
|
+
_b.label = 1;
|
|
439
|
+
case 1:
|
|
440
|
+
_b.trys.push([1, 4, , 5]);
|
|
441
|
+
return [4 /*yield*/, spHttpClient.get("".concat(catalogUrl, "/_api/web/currentuser?$select=IsSiteAdmin"), SPHttpClient.configurations.v1)];
|
|
442
|
+
case 2:
|
|
443
|
+
response = _b.sent();
|
|
444
|
+
if (!response.ok)
|
|
445
|
+
return [2 /*return*/, false];
|
|
446
|
+
return [4 /*yield*/, response.json()];
|
|
447
|
+
case 3:
|
|
448
|
+
user = _b.sent();
|
|
449
|
+
return [2 /*return*/, user.IsSiteAdmin === true];
|
|
450
|
+
case 4:
|
|
451
|
+
_a = _b.sent();
|
|
452
|
+
return [2 /*return*/, false];
|
|
453
|
+
case 5: return [2 /*return*/];
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}); }, [spHttpClient]);
|
|
457
|
+
/**
|
|
458
|
+
* Load property from tenant app catalog
|
|
459
|
+
*/
|
|
460
|
+
var load = useCallback(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
461
|
+
var catalogUrl, response, entity, err_2, error_1;
|
|
462
|
+
return __generator(this, function (_a) {
|
|
463
|
+
switch (_a.label) {
|
|
464
|
+
case 0:
|
|
465
|
+
if (!spHttpClient || !pageContext) {
|
|
466
|
+
console.warn('SPHttpClient or PageContext not available yet. Skipping load.');
|
|
467
|
+
return [2 /*return*/];
|
|
468
|
+
}
|
|
469
|
+
if (!key) {
|
|
470
|
+
console.warn('key is required. Skipping load.');
|
|
471
|
+
return [2 /*return*/];
|
|
472
|
+
}
|
|
473
|
+
setIsLoading(true);
|
|
474
|
+
setError(undefined);
|
|
475
|
+
_a.label = 1;
|
|
476
|
+
case 1:
|
|
477
|
+
_a.trys.push([1, 5, 6, 7]);
|
|
478
|
+
return [4 /*yield*/, discoverAppCatalogUrl()];
|
|
479
|
+
case 2:
|
|
480
|
+
catalogUrl = _a.sent();
|
|
481
|
+
// Check write permissions (async, non-blocking)
|
|
482
|
+
checkWritePermission(catalogUrl).then(function (hasPermission) {
|
|
483
|
+
if (isMounted.current) {
|
|
484
|
+
setCanWrite(hasPermission);
|
|
485
|
+
}
|
|
486
|
+
}).catch(function () {
|
|
487
|
+
if (isMounted.current) {
|
|
488
|
+
setCanWrite(false);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
return [4 /*yield*/, spHttpClient.get("".concat(catalogUrl, "/_api/web/GetStorageEntity('").concat(encodeURIComponent(key), "')"), SPHttpClient.configurations.v1)];
|
|
492
|
+
case 3:
|
|
493
|
+
response = _a.sent();
|
|
494
|
+
if (!response.ok) {
|
|
495
|
+
throw new Error("Failed to read property: ".concat(response.statusText));
|
|
496
|
+
}
|
|
497
|
+
return [4 /*yield*/, response.json()];
|
|
498
|
+
case 4:
|
|
499
|
+
entity = _a.sent();
|
|
500
|
+
if (isMounted.current) {
|
|
501
|
+
if (entity.Value) {
|
|
502
|
+
setData(deserializeValue(entity.Value));
|
|
503
|
+
setDescription(entity.Description);
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
// Property doesn't exist or has no value
|
|
507
|
+
setData(undefined);
|
|
508
|
+
setDescription(undefined);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return [3 /*break*/, 7];
|
|
512
|
+
case 5:
|
|
513
|
+
err_2 = _a.sent();
|
|
514
|
+
if (isMounted.current) {
|
|
515
|
+
error_1 = err_2 instanceof Error ? err_2 : new Error(String(err_2));
|
|
516
|
+
setError(error_1);
|
|
517
|
+
console.error('Failed to load tenant property:', error_1);
|
|
518
|
+
}
|
|
519
|
+
return [3 /*break*/, 7];
|
|
520
|
+
case 6:
|
|
521
|
+
if (isMounted.current) {
|
|
522
|
+
setIsLoading(false);
|
|
523
|
+
}
|
|
524
|
+
return [7 /*endfinally*/];
|
|
525
|
+
case 7: return [2 /*return*/];
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
}); }, [spHttpClient, pageContext, key, discoverAppCatalogUrl, checkWritePermission, deserializeValue]);
|
|
529
|
+
/**
|
|
530
|
+
* Write property to tenant app catalog
|
|
531
|
+
*/
|
|
532
|
+
var write = useCallback(function (content, desc) { return __awaiter(_this, void 0, void 0, function () {
|
|
533
|
+
var catalogUrl, serializedValue, body, response, errorText, err_3, error_2;
|
|
534
|
+
return __generator(this, function (_a) {
|
|
535
|
+
switch (_a.label) {
|
|
536
|
+
case 0:
|
|
537
|
+
if (!spHttpClient || !pageContext) {
|
|
538
|
+
throw new Error('SPHttpClient or PageContext not available. Cannot write property.');
|
|
539
|
+
}
|
|
540
|
+
if (!key) {
|
|
541
|
+
throw new Error('key is required. Cannot write property.');
|
|
542
|
+
}
|
|
543
|
+
setIsWriting(true);
|
|
544
|
+
setWriteError(undefined);
|
|
545
|
+
_a.label = 1;
|
|
546
|
+
case 1:
|
|
547
|
+
_a.trys.push([1, 6, 7, 8]);
|
|
548
|
+
return [4 /*yield*/, discoverAppCatalogUrl()];
|
|
549
|
+
case 2:
|
|
550
|
+
catalogUrl = _a.sent();
|
|
551
|
+
serializedValue = serializeValue(content);
|
|
552
|
+
body = {
|
|
553
|
+
key: key,
|
|
554
|
+
value: serializedValue,
|
|
555
|
+
description: desc !== null && desc !== void 0 ? desc : ''
|
|
556
|
+
// comment is NOT supported by SharePoint SetStorageEntity API
|
|
557
|
+
};
|
|
558
|
+
return [4 /*yield*/, spHttpClient.post("".concat(catalogUrl, "/_api/web/SetStorageEntity"), SPHttpClient.configurations.v1, {
|
|
559
|
+
headers: {
|
|
560
|
+
'Content-Type': 'application/json;odata=verbose',
|
|
561
|
+
'Accept': 'application/json;odata=verbose'
|
|
562
|
+
},
|
|
563
|
+
body: JSON.stringify(body)
|
|
564
|
+
})];
|
|
565
|
+
case 3:
|
|
566
|
+
response = _a.sent();
|
|
567
|
+
if (!!response.ok) return [3 /*break*/, 5];
|
|
568
|
+
return [4 /*yield*/, response.text()];
|
|
569
|
+
case 4:
|
|
570
|
+
errorText = _a.sent();
|
|
571
|
+
throw new Error("Failed to write property: ".concat(response.statusText, ". ").concat(errorText));
|
|
572
|
+
case 5:
|
|
573
|
+
if (isMounted.current) {
|
|
574
|
+
// Update local state to reflect successful write
|
|
575
|
+
setData(content);
|
|
576
|
+
setDescription(desc);
|
|
577
|
+
// Clear read error if write succeeds
|
|
578
|
+
setError(undefined);
|
|
579
|
+
}
|
|
580
|
+
return [3 /*break*/, 8];
|
|
581
|
+
case 6:
|
|
582
|
+
err_3 = _a.sent();
|
|
583
|
+
if (isMounted.current) {
|
|
584
|
+
error_2 = err_3 instanceof Error ? err_3 : new Error(String(err_3));
|
|
585
|
+
setWriteError(error_2);
|
|
586
|
+
console.error('Failed to write tenant property:', error_2);
|
|
587
|
+
}
|
|
588
|
+
// Re-throw to allow caller to handle
|
|
589
|
+
throw err_3;
|
|
590
|
+
case 7:
|
|
591
|
+
if (isMounted.current) {
|
|
592
|
+
setIsWriting(false);
|
|
593
|
+
}
|
|
594
|
+
return [7 /*endfinally*/];
|
|
595
|
+
case 8: return [2 /*return*/];
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
}); }, [spHttpClient, pageContext, key, discoverAppCatalogUrl, serializeValue]);
|
|
599
|
+
/**
|
|
600
|
+
* Remove property from tenant app catalog
|
|
601
|
+
*/
|
|
602
|
+
var remove = useCallback(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
603
|
+
var catalogUrl, response, errorText, err_4, error_3;
|
|
604
|
+
return __generator(this, function (_a) {
|
|
605
|
+
switch (_a.label) {
|
|
606
|
+
case 0:
|
|
607
|
+
if (!spHttpClient || !pageContext) {
|
|
608
|
+
throw new Error('SPHttpClient or PageContext not available. Cannot remove property.');
|
|
609
|
+
}
|
|
610
|
+
if (!key) {
|
|
611
|
+
throw new Error('key is required. Cannot remove property.');
|
|
612
|
+
}
|
|
613
|
+
setIsWriting(true);
|
|
614
|
+
setWriteError(undefined);
|
|
615
|
+
_a.label = 1;
|
|
616
|
+
case 1:
|
|
617
|
+
_a.trys.push([1, 6, 7, 8]);
|
|
618
|
+
return [4 /*yield*/, discoverAppCatalogUrl()];
|
|
619
|
+
case 2:
|
|
620
|
+
catalogUrl = _a.sent();
|
|
621
|
+
return [4 /*yield*/, spHttpClient.post("".concat(catalogUrl, "/_api/web/RemoveStorageEntity('").concat(encodeURIComponent(key), "')"), SPHttpClient.configurations.v1, {
|
|
622
|
+
headers: {
|
|
623
|
+
'Accept': 'application/json;odata=verbose'
|
|
624
|
+
}
|
|
625
|
+
})];
|
|
626
|
+
case 3:
|
|
627
|
+
response = _a.sent();
|
|
628
|
+
if (!!response.ok) return [3 /*break*/, 5];
|
|
629
|
+
return [4 /*yield*/, response.text()];
|
|
630
|
+
case 4:
|
|
631
|
+
errorText = _a.sent();
|
|
632
|
+
throw new Error("Failed to remove property: ".concat(response.statusText, ". ").concat(errorText));
|
|
633
|
+
case 5:
|
|
634
|
+
if (isMounted.current) {
|
|
635
|
+
// Clear local state
|
|
636
|
+
setData(undefined);
|
|
637
|
+
setDescription(undefined);
|
|
638
|
+
setError(undefined);
|
|
639
|
+
}
|
|
640
|
+
return [3 /*break*/, 8];
|
|
641
|
+
case 6:
|
|
642
|
+
err_4 = _a.sent();
|
|
643
|
+
if (isMounted.current) {
|
|
644
|
+
error_3 = err_4 instanceof Error ? err_4 : new Error(String(err_4));
|
|
645
|
+
setWriteError(error_3);
|
|
646
|
+
console.error('Failed to remove tenant property:', error_3);
|
|
647
|
+
}
|
|
648
|
+
// Re-throw to allow caller to handle
|
|
649
|
+
throw err_4;
|
|
650
|
+
case 7:
|
|
651
|
+
if (isMounted.current) {
|
|
652
|
+
setIsWriting(false);
|
|
653
|
+
}
|
|
654
|
+
return [7 /*endfinally*/];
|
|
655
|
+
case 8: return [2 /*return*/];
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
}); }, [spHttpClient, pageContext, key, discoverAppCatalogUrl]);
|
|
659
|
+
// Auto-fetch on mount if enabled
|
|
660
|
+
useEffect(function () {
|
|
661
|
+
if (autoFetch && spHttpClient && pageContext && key) {
|
|
662
|
+
load().catch(function () {
|
|
663
|
+
// Error already handled in load() function
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}, [autoFetch, spHttpClient, pageContext, key, load]);
|
|
667
|
+
// Computed state: ready when data loaded successfully
|
|
668
|
+
var isReady = !isLoading && !error && data !== undefined;
|
|
669
|
+
return {
|
|
670
|
+
data: data,
|
|
671
|
+
description: description,
|
|
672
|
+
isLoading: isLoading,
|
|
673
|
+
error: error,
|
|
674
|
+
isWriting: isWriting,
|
|
675
|
+
writeError: writeError,
|
|
676
|
+
canWrite: canWrite,
|
|
677
|
+
load: load,
|
|
678
|
+
write: write,
|
|
679
|
+
remove: remove,
|
|
680
|
+
isReady: isReady,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
//# sourceMappingURL=useSPFxTenantProperty.js.map
|