@apvee/spfx-react-toolkit 1.2.1 → 2.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/lib/core/atoms.internal.js +1 -1
- package/lib/core/atoms.internal.js.map +1 -1
- package/lib/core/context.internal.js +2 -2
- package/lib/core/context.internal.js.map +1 -1
- package/lib/core/provider-base.internal.js +29 -29
- package/lib/core/provider-base.internal.js.map +1 -1
- package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.d.ts +14 -0
- package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.d.ts.map +1 -0
- package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.js +20 -0
- package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.js.map +1 -0
- package/lib/extensions/spFxReactToolkitTest/SpFxReactToolkitTestApplicationCustomizer.manifest.json +17 -0
- package/lib/extensions/spFxReactToolkitTest/loc/en-us.js +5 -0
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.d.ts.map +1 -1
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/index.js.map +1 -1
- package/lib/hooks/useAppCatalogUrl.internal.d.ts +26 -0
- package/lib/hooks/useAppCatalogUrl.internal.d.ts.map +1 -0
- package/lib/hooks/useAppCatalogUrl.internal.js +72 -0
- package/lib/hooks/useAppCatalogUrl.internal.js.map +1 -0
- package/lib/hooks/useAsyncInvoke.internal.js +27 -75
- package/lib/hooks/useAsyncInvoke.internal.js.map +1 -1
- package/lib/hooks/useSPFxAadHttpClient.d.ts +46 -0
- package/lib/hooks/useSPFxAadHttpClient.d.ts.map +1 -1
- package/lib/hooks/useSPFxAadHttpClient.js +65 -20
- package/lib/hooks/useSPFxAadHttpClient.js.map +1 -1
- package/lib/hooks/useSPFxContainerInfo.js +5 -5
- package/lib/hooks/useSPFxContainerInfo.js.map +1 -1
- package/lib/hooks/useSPFxContainerSize.js +9 -10
- package/lib/hooks/useSPFxContainerSize.js.map +1 -1
- package/lib/hooks/useSPFxCorrelationInfo.js +6 -7
- package/lib/hooks/useSPFxCorrelationInfo.js.map +1 -1
- package/lib/hooks/useSPFxCrossSitePermissions.js +48 -58
- package/lib/hooks/useSPFxCrossSitePermissions.js.map +1 -1
- package/lib/hooks/useSPFxDisplayMode.js +8 -8
- package/lib/hooks/useSPFxDisplayMode.js.map +1 -1
- package/lib/hooks/useSPFxEnvironmentInfo.js +17 -18
- package/lib/hooks/useSPFxEnvironmentInfo.js.map +1 -1
- package/lib/hooks/useSPFxFluent9ThemeInfo.js +4 -4
- package/lib/hooks/useSPFxFluent9ThemeInfo.js.map +1 -1
- package/lib/hooks/useSPFxHttpClient.d.ts +18 -2
- package/lib/hooks/useSPFxHttpClient.d.ts.map +1 -1
- package/lib/hooks/useSPFxHttpClient.js +19 -9
- package/lib/hooks/useSPFxHttpClient.js.map +1 -1
- package/lib/hooks/useSPFxHubSiteInfo.js +21 -24
- package/lib/hooks/useSPFxHubSiteInfo.js.map +1 -1
- package/lib/hooks/useSPFxInstanceInfo.js +2 -2
- package/lib/hooks/useSPFxInstanceInfo.js.map +1 -1
- package/lib/hooks/useSPFxListInfo.js +8 -9
- package/lib/hooks/useSPFxListInfo.js.map +1 -1
- package/lib/hooks/useSPFxLocaleInfo.js +10 -10
- package/lib/hooks/useSPFxLocaleInfo.js.map +1 -1
- package/lib/hooks/useSPFxLogger.js +26 -26
- package/lib/hooks/useSPFxLogger.js.map +1 -1
- package/lib/hooks/useSPFxMSGraphClient.d.ts +50 -3
- package/lib/hooks/useSPFxMSGraphClient.d.ts.map +1 -1
- package/lib/hooks/useSPFxMSGraphClient.js +68 -15
- package/lib/hooks/useSPFxMSGraphClient.js.map +1 -1
- package/lib/hooks/useSPFxOneDriveAppData.d.ts +0 -1
- package/lib/hooks/useSPFxOneDriveAppData.d.ts.map +1 -1
- package/lib/hooks/useSPFxOneDriveAppData.js +420 -230
- package/lib/hooks/useSPFxOneDriveAppData.js.map +1 -1
- package/lib/hooks/useSPFxPageContext.js +2 -2
- package/lib/hooks/useSPFxPageContext.js.map +1 -1
- package/lib/hooks/useSPFxPageType.js +19 -20
- package/lib/hooks/useSPFxPageType.js.map +1 -1
- package/lib/hooks/useSPFxPerformance.js +33 -87
- package/lib/hooks/useSPFxPerformance.js.map +1 -1
- package/lib/hooks/useSPFxPermissions.js +14 -15
- package/lib/hooks/useSPFxPermissions.js.map +1 -1
- package/lib/hooks/useSPFxPnP.js +62 -119
- package/lib/hooks/useSPFxPnP.js.map +1 -1
- package/lib/hooks/useSPFxPnPContext.js +22 -25
- package/lib/hooks/useSPFxPnPContext.js.map +1 -1
- package/lib/hooks/useSPFxPnPList.js +307 -451
- package/lib/hooks/useSPFxPnPList.js.map +1 -1
- package/lib/hooks/useSPFxPnPSearch.js +262 -353
- package/lib/hooks/useSPFxPnPSearch.js.map +1 -1
- package/lib/hooks/useSPFxProperties.js +12 -20
- package/lib/hooks/useSPFxProperties.js.map +1 -1
- package/lib/hooks/useSPFxSPHttpClient.d.ts +18 -2
- package/lib/hooks/useSPFxSPHttpClient.d.ts.map +1 -1
- package/lib/hooks/useSPFxSPHttpClient.js +28 -18
- package/lib/hooks/useSPFxSPHttpClient.js.map +1 -1
- package/lib/hooks/useSPFxServiceScope.js +6 -6
- package/lib/hooks/useSPFxServiceScope.js.map +1 -1
- package/lib/hooks/useSPFxSiteInfo.js +7 -8
- package/lib/hooks/useSPFxSiteInfo.js.map +1 -1
- package/lib/hooks/useSPFxStorage.js +22 -22
- package/lib/hooks/useSPFxStorage.js.map +1 -1
- package/lib/hooks/useSPFxTeams.js +37 -92
- package/lib/hooks/useSPFxTeams.js.map +1 -1
- package/lib/hooks/useSPFxTenantKeyValueStore.d.ts +252 -0
- package/lib/hooks/useSPFxTenantKeyValueStore.d.ts.map +1 -0
- package/lib/hooks/useSPFxTenantKeyValueStore.js +572 -0
- package/lib/hooks/useSPFxTenantKeyValueStore.js.map +1 -0
- package/lib/hooks/useSPFxTenantProperty.d.ts +23 -244
- package/lib/hooks/useSPFxTenantProperty.d.ts.map +1 -1
- package/lib/hooks/useSPFxTenantProperty.js +85 -559
- package/lib/hooks/useSPFxTenantProperty.js.map +1 -1
- package/lib/hooks/useSPFxUserInfo.js +3 -4
- package/lib/hooks/useSPFxUserInfo.js.map +1 -1
- package/lib/hooks/useSPFxUserPhoto.js +76 -123
- package/lib/hooks/useSPFxUserPhoto.js.map +1 -1
- package/lib/utils/resize-observer.internal.js +6 -7
- package/lib/utils/resize-observer.internal.js.map +1 -1
- package/lib/utils/theme-subscription.internal.js +8 -8
- package/lib/utils/theme-subscription.internal.js.map +1 -1
- package/lib/utils/type-guards.internal.js +6 -6
- package/lib/utils/type-guards.internal.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.js +12 -37
- package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.d.ts.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.js +279 -342
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.js +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/demos/HttpClientDemo.js +26 -86
- package/lib/webparts/spFxReactToolkitTest/components/demos/HttpClientDemo.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPContextDemo.js +53 -113
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPContextDemo.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPListDemo.js +49 -121
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPListDemo.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPOperationsDemo.js +44 -103
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPOperationsDemo.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchAdvancedDemo.js +15 -15
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchAdvancedDemo.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchBasicDemo.js +18 -66
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchBasicDemo.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchRefinersDemo.js +9 -9
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchRefinersDemo.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchSuggestionsDemo.js +37 -86
- package/lib/webparts/spFxReactToolkitTest/components/demos/PnPSearchSuggestionsDemo.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/shared/InfoRow.js +6 -9
- package/lib/webparts/spFxReactToolkitTest/components/shared/InfoRow.js.map +1 -1
- package/lib/webparts/spFxReactToolkitTest/components/shared/StatusBadge.js +3 -6
- package/lib/webparts/spFxReactToolkitTest/components/shared/StatusBadge.js.map +1 -1
- package/package.json +8 -6
|
@@ -1,151 +1,51 @@
|
|
|
1
1
|
// useSPFxTenantProperty.ts
|
|
2
|
-
// Hook to
|
|
3
|
-
|
|
4
|
-
|
|
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';
|
|
2
|
+
// Hook to read tenant-wide properties using SharePoint StorageEntity API (read-only)
|
|
3
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
4
|
+
import { useAppCatalogUrl } from './useAppCatalogUrl.internal';
|
|
42
5
|
import { SPHttpClient } from '@microsoft/sp-http';
|
|
43
6
|
/**
|
|
44
|
-
* Hook to
|
|
7
|
+
* Hook to read tenant-wide properties using SharePoint StorageEntity API (read-only)
|
|
8
|
+
*
|
|
9
|
+
* Provides read access to tenant-scoped properties stored in the SharePoint tenant
|
|
10
|
+
* app catalog. Properties are accessible across all sites in the tenant.
|
|
45
11
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
12
|
+
* **Note:** Write and remove operations have been removed because Microsoft has
|
|
13
|
+
* blocked the SetStorageEntity and RemoveStorageEntity REST API endpoints.
|
|
14
|
+
* Tenant properties can only be managed via PowerShell (Set-PnPStorageEntity,
|
|
15
|
+
* Remove-PnPStorageEntity) or the SharePoint Management Shell.
|
|
16
|
+
* For a read/write key-value store at tenant level, use `useSPFxTenantKeyValueStore`.
|
|
49
17
|
*
|
|
50
18
|
* Features:
|
|
51
19
|
* - Tenant-wide centralized storage (not site-specific)
|
|
52
|
-
* - Smart
|
|
53
|
-
* - Permission checking (canWrite flag)
|
|
20
|
+
* - Smart deserialization for primitives and complex objects
|
|
54
21
|
* - Optional metadata (description only - SharePoint limitation)
|
|
55
22
|
* - Type-safe with TypeScript generics
|
|
56
23
|
* - Memory leak safe with mounted state tracking
|
|
57
24
|
* - Automatic app catalog URL discovery
|
|
58
|
-
* - Remove operation for cleanup
|
|
59
25
|
*
|
|
60
26
|
* Requirements:
|
|
61
27
|
* - Tenant app catalog must be provisioned
|
|
62
|
-
* -
|
|
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
|
|
28
|
+
* - Any authenticated user can read tenant properties
|
|
75
29
|
*
|
|
76
30
|
* @param key - Unique property key (e.g., 'appVersion', 'apiEndpoint', 'featureFlags')
|
|
77
31
|
* @param autoFetch - Whether to automatically load property on mount. Default: true
|
|
78
32
|
*
|
|
79
|
-
* @returns Object with data, metadata, loading
|
|
33
|
+
* @returns Object with data, metadata, loading state, error state, and load function
|
|
80
34
|
*
|
|
81
35
|
* @example Basic usage - string property
|
|
82
36
|
* ```tsx
|
|
83
37
|
* function VersionDisplay() {
|
|
84
|
-
* const { data, isLoading, error
|
|
85
|
-
* useSPFxTenantProperty<string>('appVersion');
|
|
38
|
+
* const { data, isLoading, error } = useSPFxTenantProperty<string>('appVersion');
|
|
86
39
|
*
|
|
87
40
|
* if (isLoading) return <Spinner label="Loading version..." />;
|
|
88
41
|
* if (error) return <MessageBar messageBarType={MessageBarType.error}>
|
|
89
42
|
* Failed to load: {error.message}
|
|
90
43
|
* </MessageBar>;
|
|
91
44
|
*
|
|
92
|
-
*
|
|
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>;
|
|
45
|
+
* return <Text>Current Version: {data ?? 'Not Set'}</Text>;
|
|
135
46
|
* }
|
|
136
47
|
* ```
|
|
137
48
|
*
|
|
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
49
|
* @example Complex object with JSON
|
|
150
50
|
* ```tsx
|
|
151
51
|
* interface FeatureFlags {
|
|
@@ -154,74 +54,14 @@ import { SPHttpClient } from '@microsoft/sp-http';
|
|
|
154
54
|
* maxUsers: number;
|
|
155
55
|
* }
|
|
156
56
|
*
|
|
157
|
-
* const { data,
|
|
57
|
+
* const { data, isLoading } = useSPFxTenantProperty<FeatureFlags>('featureFlags');
|
|
158
58
|
*
|
|
159
|
-
* //
|
|
160
|
-
* await write({
|
|
161
|
-
* enableChat: true,
|
|
162
|
-
* enableAnalytics: false,
|
|
163
|
-
* maxUsers: 1000
|
|
164
|
-
* }, 'Global feature flags configuration');
|
|
165
|
-
*
|
|
166
|
-
* // Read returns parsed object
|
|
59
|
+
* // Returns parsed object (stored as JSON string via PowerShell)
|
|
167
60
|
* if (data?.enableChat) {
|
|
168
61
|
* return <ChatPanel />;
|
|
169
62
|
* }
|
|
170
63
|
* ```
|
|
171
64
|
*
|
|
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
65
|
* @example With metadata viewing
|
|
226
66
|
* ```tsx
|
|
227
67
|
* function PropertyViewer() {
|
|
@@ -241,7 +81,7 @@ import { SPHttpClient } from '@microsoft/sp-http';
|
|
|
241
81
|
*
|
|
242
82
|
* @example Lazy loading with manual trigger
|
|
243
83
|
* ```tsx
|
|
244
|
-
* const { data, load, isLoading
|
|
84
|
+
* const { data, load, isLoading } = useSPFxTenantProperty<Config>(
|
|
245
85
|
* 'appConfig',
|
|
246
86
|
* false // Don't auto-fetch
|
|
247
87
|
* );
|
|
@@ -256,428 +96,114 @@ import { SPHttpClient } from '@microsoft/sp-http';
|
|
|
256
96
|
* );
|
|
257
97
|
* ```
|
|
258
98
|
*
|
|
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
99
|
* @example Multi-property dashboard
|
|
289
100
|
* ```tsx
|
|
290
101
|
* function TenantDashboard() {
|
|
291
102
|
* const version = useSPFxTenantProperty<string>('appVersion');
|
|
292
103
|
* const maintenance = useSPFxTenantProperty<boolean>('maintenanceMode');
|
|
293
|
-
* const lastUpdate = useSPFxTenantProperty<string>('lastUpdate');
|
|
294
104
|
* const config = useSPFxTenantProperty<AppConfig>('appConfig');
|
|
295
105
|
*
|
|
296
|
-
* const isLoading = version.isLoading || maintenance.isLoading ||
|
|
297
|
-
* lastUpdate.isLoading || config.isLoading;
|
|
106
|
+
* const isLoading = version.isLoading || maintenance.isLoading || config.isLoading;
|
|
298
107
|
*
|
|
299
108
|
* if (isLoading) return <Spinner label="Loading dashboard..." />;
|
|
300
109
|
*
|
|
301
110
|
* return (
|
|
302
111
|
* <Stack tokens={{ childrenGap: 20 }}>
|
|
303
112
|
* <Text variant="xxLarge">Tenant Configuration</Text>
|
|
304
|
-
*
|
|
305
|
-
* <
|
|
306
|
-
*
|
|
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
|
-
* )}
|
|
113
|
+
* <Label>App Version: {version.data ?? 'Not Set'}</Label>
|
|
114
|
+
* <Label>Maintenance Mode: {maintenance.data ? 'ON' : 'OFF'}</Label>
|
|
115
|
+
* {config.data && <pre>{JSON.stringify(config.data, null, 2)}</pre>}
|
|
317
116
|
* </Stack>
|
|
318
117
|
* );
|
|
319
118
|
* }
|
|
320
119
|
* ```
|
|
321
120
|
*/
|
|
322
|
-
export function useSPFxTenantProperty(key, autoFetch) {
|
|
323
|
-
|
|
324
|
-
if (autoFetch === void 0) { autoFetch = true; }
|
|
325
|
-
var spHttpClient = useSPFxSPHttpClient().client;
|
|
326
|
-
var pageContext = useSPFxPageContext();
|
|
121
|
+
export function useSPFxTenantProperty(key, autoFetch = true) {
|
|
122
|
+
const { spHttpClient, discoverAppCatalogUrl, isMountedRef } = useAppCatalogUrl();
|
|
327
123
|
// State management
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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]);
|
|
124
|
+
const [data, setData] = useState(undefined);
|
|
125
|
+
const [description, setDescription] = useState(undefined);
|
|
126
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
127
|
+
const [error, setError] = useState(undefined);
|
|
372
128
|
/**
|
|
373
129
|
* Deserialize value from storage
|
|
374
130
|
* - Try JSON.parse first
|
|
375
131
|
* - If fails, return raw string (will be cast to T by TypeScript)
|
|
376
132
|
*/
|
|
377
|
-
|
|
133
|
+
const deserializeValue = useCallback((rawValue) => {
|
|
378
134
|
try {
|
|
379
135
|
return JSON.parse(rawValue);
|
|
380
136
|
}
|
|
381
|
-
catch
|
|
137
|
+
catch {
|
|
382
138
|
// Not valid JSON, assume it's a primitive value
|
|
383
139
|
return rawValue;
|
|
384
140
|
}
|
|
385
141
|
}, []);
|
|
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
142
|
/**
|
|
458
143
|
* Load property from tenant app catalog
|
|
459
144
|
*/
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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*/];
|
|
145
|
+
const load = useCallback(async () => {
|
|
146
|
+
if (!spHttpClient) {
|
|
147
|
+
console.warn('SPHttpClient not available yet. Skipping load.');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!key) {
|
|
151
|
+
console.warn('key is required. Skipping load.');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
setIsLoading(true);
|
|
155
|
+
setError(undefined);
|
|
156
|
+
try {
|
|
157
|
+
// Discover app catalog URL
|
|
158
|
+
const catalogUrl = await discoverAppCatalogUrl();
|
|
159
|
+
// Read property
|
|
160
|
+
const response = await spHttpClient.get(`${catalogUrl}/_api/web/GetStorageEntity('${encodeURIComponent(key)}')`, SPHttpClient.configurations.v1);
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
throw new Error(`Failed to read property: ${response.statusText}`);
|
|
526
163
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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*/];
|
|
164
|
+
const entity = await response.json();
|
|
165
|
+
if (isMountedRef.current) {
|
|
166
|
+
if (entity.Value) {
|
|
167
|
+
setData(deserializeValue(entity.Value));
|
|
168
|
+
setDescription(entity.Description);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Property doesn't exist or has no value
|
|
172
|
+
setData(undefined);
|
|
173
|
+
setDescription(undefined);
|
|
174
|
+
}
|
|
596
175
|
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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*/];
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
if (isMountedRef.current) {
|
|
179
|
+
const capturedError = err instanceof Error ? err : new Error(String(err));
|
|
180
|
+
setError(capturedError);
|
|
181
|
+
console.error('Failed to load tenant property:', capturedError);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
if (isMountedRef.current) {
|
|
186
|
+
setIsLoading(false);
|
|
656
187
|
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
188
|
+
}
|
|
189
|
+
}, [spHttpClient, key, discoverAppCatalogUrl, deserializeValue, isMountedRef]);
|
|
659
190
|
// Auto-fetch on mount if enabled
|
|
660
|
-
useEffect(
|
|
661
|
-
if (autoFetch && spHttpClient &&
|
|
662
|
-
load().catch(
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (autoFetch && spHttpClient && key) {
|
|
193
|
+
load().catch(() => {
|
|
663
194
|
// Error already handled in load() function
|
|
664
195
|
});
|
|
665
196
|
}
|
|
666
|
-
}, [autoFetch, spHttpClient,
|
|
197
|
+
}, [autoFetch, spHttpClient, key, load]);
|
|
667
198
|
// Computed state: ready when data loaded successfully
|
|
668
|
-
|
|
199
|
+
const isReady = !isLoading && !error && data !== undefined;
|
|
669
200
|
return {
|
|
670
|
-
data
|
|
671
|
-
description
|
|
672
|
-
isLoading
|
|
673
|
-
error
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
canWrite: canWrite,
|
|
677
|
-
load: load,
|
|
678
|
-
write: write,
|
|
679
|
-
remove: remove,
|
|
680
|
-
isReady: isReady,
|
|
201
|
+
data,
|
|
202
|
+
description,
|
|
203
|
+
isLoading,
|
|
204
|
+
error,
|
|
205
|
+
load,
|
|
206
|
+
isReady,
|
|
681
207
|
};
|
|
682
208
|
}
|
|
683
209
|
//# sourceMappingURL=useSPFxTenantProperty.js.map
|