@appcorp/fusion-storybook 0.5.1 → 0.5.4
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/base-modules/workspace/cache.d.ts +10 -1
- package/base-modules/workspace/cache.js +37 -2
- package/base-modules/workspace/context.d.ts +36 -2
- package/base-modules/workspace/context.js +62 -17
- package/base-modules/workspace/form.js +14 -11
- package/base-modules/workspace/page.d.ts +5 -0
- package/base-modules/workspace/page.js +1 -1
- package/base-modules/workspace/view.js +11 -11
- package/base-modules/workspace-user/context.d.ts +11 -5
- package/base-modules/workspace-user/context.js +99 -32
- package/base-modules/workspace-user/form.js +59 -1
- package/base-modules/workspace-user/more-actions.js +3 -4
- package/base-modules/workspace-user/page.js +2 -2
- package/package.json +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/user-management.d.ts +2 -1
- package/utils/resolve-rbac-permissions.js +84 -1
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
* Workspace Cache
|
|
3
3
|
*
|
|
4
4
|
* Cache utilities for workspace data using generic cache system.
|
|
5
|
+
*
|
|
6
|
+
* The cache entry shape is `{ item: WorkspaceBE, cachedAt: ISOString }` — that
|
|
7
|
+
* is what `getCachedSingleItem` writes, and what `getCachedSingleItemSync`
|
|
8
|
+
* reads. Keep any writer (including this module) on the same shape.
|
|
5
9
|
*/
|
|
6
10
|
import { WorkspaceBE } from "../../type";
|
|
7
|
-
export declare const getCachedWorkspace: (
|
|
11
|
+
export declare const getCachedWorkspace: (subdomain: string) => Promise<WorkspaceBE | null>;
|
|
8
12
|
export declare const getCachedWorkspaceSync: () => WorkspaceBE | null;
|
|
13
|
+
/**
|
|
14
|
+
* Persist a freshly fetched/updated workspace so every other module reading
|
|
15
|
+
* `getCachedWorkspaceSync()` sees the new values without a reload.
|
|
16
|
+
*/
|
|
17
|
+
export declare const setCachedWorkspace: (workspace: WorkspaceBE) => void;
|
|
9
18
|
export declare const invalidateWorkspaceCache: () => void;
|
|
@@ -2,28 +2,63 @@
|
|
|
2
2
|
* Workspace Cache
|
|
3
3
|
*
|
|
4
4
|
* Cache utilities for workspace data using generic cache system.
|
|
5
|
+
*
|
|
6
|
+
* The cache entry shape is `{ item: WorkspaceBE, cachedAt: ISOString }` — that
|
|
7
|
+
* is what `getCachedSingleItem` writes, and what `getCachedSingleItemSync`
|
|
8
|
+
* reads. Keep any writer (including this module) on the same shape.
|
|
5
9
|
*/
|
|
6
10
|
import { getCachedSingleItem, getCachedSingleItemSync, invalidateCache, } from "@react-pakistan/util-functions/general/generic-cache";
|
|
7
11
|
import { ONE_YEAR_IN_MS } from "@react-pakistan/util-functions/constants/cache-time";
|
|
8
12
|
import { WORKSPACE_API_ROUTES } from "../../constants";
|
|
9
13
|
import { LS_KEYS } from "../../constants";
|
|
14
|
+
import { setStorageValue } from "@react-pakistan/util-functions/local-storage/set-storage-value";
|
|
10
15
|
// ============================================================================
|
|
11
16
|
// CACHE CONFIGURATION
|
|
12
17
|
// ============================================================================
|
|
13
18
|
const CACHE_CONFIG = {
|
|
14
19
|
cacheKey: LS_KEYS.WORKSPACE,
|
|
15
20
|
apiUrl: WORKSPACE_API_ROUTES.UNIT,
|
|
21
|
+
expirationMs: ONE_YEAR_IN_MS,
|
|
16
22
|
responseKey: "item",
|
|
17
23
|
};
|
|
18
24
|
// ============================================================================
|
|
19
25
|
// CACHE WRAPPERS
|
|
20
26
|
// ============================================================================
|
|
21
|
-
export const getCachedWorkspace = (
|
|
22
|
-
|
|
27
|
+
export const getCachedWorkspace = async (subdomain) => {
|
|
28
|
+
const item = await getCachedSingleItem(CACHE_CONFIG, {
|
|
29
|
+
subdomain,
|
|
30
|
+
});
|
|
31
|
+
// The generic cache ignores request params and caches whatever body came
|
|
32
|
+
// back (including a 404 `{ error }`). Drop anything that is not a workspace
|
|
33
|
+
// for the requested tenant so we never render another tenant's data.
|
|
34
|
+
if (!(item === null || item === void 0 ? void 0 : item.id)) {
|
|
35
|
+
invalidateCache(LS_KEYS.WORKSPACE);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
if (subdomain && item.subdomain !== subdomain) {
|
|
39
|
+
invalidateCache(LS_KEYS.WORKSPACE);
|
|
40
|
+
const fresh = await getCachedSingleItem(CACHE_CONFIG, {
|
|
41
|
+
subdomain,
|
|
42
|
+
});
|
|
43
|
+
return (fresh === null || fresh === void 0 ? void 0 : fresh.id) && fresh.subdomain === subdomain ? fresh : null;
|
|
44
|
+
}
|
|
45
|
+
return item;
|
|
23
46
|
};
|
|
24
47
|
export const getCachedWorkspaceSync = () => {
|
|
25
48
|
return getCachedSingleItemSync(LS_KEYS.WORKSPACE, ONE_YEAR_IN_MS);
|
|
26
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* Persist a freshly fetched/updated workspace so every other module reading
|
|
52
|
+
* `getCachedWorkspaceSync()` sees the new values without a reload.
|
|
53
|
+
*/
|
|
54
|
+
export const setCachedWorkspace = (workspace) => {
|
|
55
|
+
if (!(workspace === null || workspace === void 0 ? void 0 : workspace.id))
|
|
56
|
+
return;
|
|
57
|
+
setStorageValue(LS_KEYS.WORKSPACE, {
|
|
58
|
+
item: workspace,
|
|
59
|
+
cachedAt: new Date().toISOString(),
|
|
60
|
+
});
|
|
61
|
+
};
|
|
27
62
|
export const invalidateWorkspaceCache = () => {
|
|
28
63
|
invalidateCache(LS_KEYS.WORKSPACE);
|
|
29
64
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WorkspaceBE } from "../../type";
|
|
1
2
|
export declare const WORKSPACE_DRAWER: {
|
|
2
3
|
readonly FILTER_DRAWER: string;
|
|
3
4
|
readonly FORM_DRAWER: string;
|
|
@@ -30,6 +31,11 @@ export declare const WORKSPACE_ACTION_TYPES: {
|
|
|
30
31
|
id: string;
|
|
31
32
|
logo: string;
|
|
32
33
|
name: string;
|
|
34
|
+
plan: {
|
|
35
|
+
currency?: string;
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
} | null;
|
|
33
39
|
planEndDate: null;
|
|
34
40
|
planId: string;
|
|
35
41
|
planStartDate: null;
|
|
@@ -56,6 +62,11 @@ export declare const WORKSPACE_ACTION_TYPES: {
|
|
|
56
62
|
id: string;
|
|
57
63
|
logo: string;
|
|
58
64
|
name: string;
|
|
65
|
+
plan: {
|
|
66
|
+
currency?: string;
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
} | null;
|
|
59
70
|
planEndDate: null;
|
|
60
71
|
planId: string;
|
|
61
72
|
planStartDate: null;
|
|
@@ -84,6 +95,11 @@ export declare const WORKSPACE_ACTION_TYPES: {
|
|
|
84
95
|
id: string;
|
|
85
96
|
logo: string;
|
|
86
97
|
name: string;
|
|
98
|
+
plan: {
|
|
99
|
+
currency?: string;
|
|
100
|
+
id: string;
|
|
101
|
+
name: string;
|
|
102
|
+
} | null;
|
|
87
103
|
planEndDate: null;
|
|
88
104
|
planId: string;
|
|
89
105
|
planStartDate: null;
|
|
@@ -110,6 +126,11 @@ export declare const WORKSPACE_ACTION_TYPES: {
|
|
|
110
126
|
id: string;
|
|
111
127
|
logo: string;
|
|
112
128
|
name: string;
|
|
129
|
+
plan: {
|
|
130
|
+
currency?: string;
|
|
131
|
+
id: string;
|
|
132
|
+
name: string;
|
|
133
|
+
} | null;
|
|
113
134
|
planEndDate: null;
|
|
114
135
|
planId: string;
|
|
115
136
|
planStartDate: null;
|
|
@@ -136,6 +157,11 @@ export declare const WORKSPACE_ACTION_TYPES: {
|
|
|
136
157
|
id: string;
|
|
137
158
|
logo: string;
|
|
138
159
|
name: string;
|
|
160
|
+
plan: {
|
|
161
|
+
currency?: string;
|
|
162
|
+
id: string;
|
|
163
|
+
name: string;
|
|
164
|
+
} | null;
|
|
139
165
|
planEndDate: null;
|
|
140
166
|
planId: string;
|
|
141
167
|
planStartDate: null;
|
|
@@ -150,11 +176,13 @@ export declare const WORKSPACE_ACTION_TYPES: {
|
|
|
150
176
|
errors: Record<string, string>;
|
|
151
177
|
}>;
|
|
152
178
|
type Plan = {
|
|
153
|
-
|
|
179
|
+
currency?: string;
|
|
154
180
|
id: string;
|
|
181
|
+
name: string;
|
|
155
182
|
};
|
|
156
|
-
export declare const WorkspaceStateContextProvider: ({ children, }: {
|
|
183
|
+
export declare const WorkspaceStateContextProvider: ({ children, subdomain, }: {
|
|
157
184
|
children: React.ReactNode;
|
|
185
|
+
subdomain?: string;
|
|
158
186
|
}) => import("react").JSX.Element;
|
|
159
187
|
export declare const useWorkspaceModule: () => {
|
|
160
188
|
handleChange: (field: string, value: string | number | boolean | undefined | Record<string, string>) => void;
|
|
@@ -164,6 +192,7 @@ export declare const useWorkspaceModule: () => {
|
|
|
164
192
|
handleView: () => void;
|
|
165
193
|
plans: Plan[];
|
|
166
194
|
updateLoading: boolean;
|
|
195
|
+
workspace: WorkspaceBE | null;
|
|
167
196
|
handleSearch?: (query: string) => void;
|
|
168
197
|
handlePageChange?: (page: number) => void;
|
|
169
198
|
handlePageLimitChange?: (limit: number) => void;
|
|
@@ -181,6 +210,11 @@ export declare const useWorkspaceModule: () => {
|
|
|
181
210
|
id: string;
|
|
182
211
|
logo: string;
|
|
183
212
|
name: string;
|
|
213
|
+
plan: {
|
|
214
|
+
currency?: string;
|
|
215
|
+
id: string;
|
|
216
|
+
name: string;
|
|
217
|
+
} | null;
|
|
184
218
|
planEndDate: null;
|
|
185
219
|
planId: string;
|
|
186
220
|
planStartDate: null;
|
|
@@ -10,7 +10,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
10
10
|
* Key responsibilities:
|
|
11
11
|
* - Expose `useWorkspaceModule()` which UI components call for actions
|
|
12
12
|
* - Keep module-specific `apiParams` and callbacks in one place
|
|
13
|
-
* -
|
|
13
|
+
* - Keep the shared workspace cache in sync on mutation
|
|
14
14
|
*
|
|
15
15
|
* Exported utilities:
|
|
16
16
|
* - `WorkspaceStateContextProvider` — provider component used by the page
|
|
@@ -26,10 +26,9 @@ import { useModuleEntityV2, } from "@react-pakistan/util-functions/hooks/use-mod
|
|
|
26
26
|
import { createGenericModule } from "@react-pakistan/util-functions/factory/generic-module-factory";
|
|
27
27
|
import { DRAWER_TYPES } from "@react-pakistan/util-functions/factory/generic-component-factory";
|
|
28
28
|
import { generateThemeToast, TOAST_VARIANT, } from "@appcorp/shadcn/lib/toast-utils";
|
|
29
|
-
import { WORKSPACE_API_ROUTES } from "../../constants";
|
|
30
|
-
import { getCachedWorkspaceSync,
|
|
29
|
+
import { AUTH_API_ROUTES, WORKSPACE_API_ROUTES } from "../../constants";
|
|
30
|
+
import { getCachedWorkspace, getCachedWorkspaceSync, setCachedWorkspace, } from "./cache";
|
|
31
31
|
import { workspaceFormValidation } from "./validate";
|
|
32
|
-
import { AUTH_API_ROUTES } from "../../constants";
|
|
33
32
|
// ============================================================================
|
|
34
33
|
// 1.1 DRAWER TYPES
|
|
35
34
|
// ============================================================================
|
|
@@ -60,6 +59,7 @@ const workspaceConfig = {
|
|
|
60
59
|
id: "",
|
|
61
60
|
logo: "",
|
|
62
61
|
name: "",
|
|
62
|
+
plan: null,
|
|
63
63
|
planEndDate: null,
|
|
64
64
|
planId: "",
|
|
65
65
|
planStartDate: null,
|
|
@@ -81,23 +81,49 @@ const workspaceConfig = {
|
|
|
81
81
|
// ============================================================================
|
|
82
82
|
export const { actionTypes: WORKSPACE_ACTION_TYPES, config: workspaceModuleConfig, initialState: initialWorkspaceState, Provider: WorkspaceProvider, reducer: workspaceReducer, useContext: useWorkspaceContext, } = createGenericModule(workspaceConfig);
|
|
83
83
|
const PlansContext = createContext([]);
|
|
84
|
-
export const WorkspaceStateContextProvider = ({ children, }) => {
|
|
84
|
+
export const WorkspaceStateContextProvider = ({ children, subdomain, }) => {
|
|
85
85
|
const [plans, setPlans] = useState([]);
|
|
86
|
+
// Render only once the cache holds the workspace for this tenant, otherwise
|
|
87
|
+
// the lazy `useState(() => getCachedWorkspaceSync())` below hydrates empty.
|
|
88
|
+
// Start `false` whenever a subdomain is given so SSR and hydration agree.
|
|
89
|
+
const [ready, setReady] = useState(() => !subdomain);
|
|
86
90
|
const t = useTranslations("workspace");
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (!subdomain)
|
|
93
|
+
return;
|
|
94
|
+
let isMounted = true;
|
|
95
|
+
(async () => {
|
|
96
|
+
const cached = getCachedWorkspaceSync();
|
|
97
|
+
if ((cached === null || cached === void 0 ? void 0 : cached.id) && cached.subdomain === subdomain) {
|
|
98
|
+
if (isMounted)
|
|
99
|
+
setReady(true);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
await getCachedWorkspace(subdomain);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
if (isMounted)
|
|
107
|
+
setReady(true);
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
110
|
+
return () => {
|
|
111
|
+
isMounted = false;
|
|
112
|
+
};
|
|
113
|
+
}, [subdomain]);
|
|
87
114
|
useEffect(() => {
|
|
88
115
|
const fetchPlans = async () => {
|
|
89
116
|
try {
|
|
90
|
-
const response = await fetch(AUTH_API_ROUTES.PLAN
|
|
117
|
+
const response = await fetch(`${AUTH_API_ROUTES.PLAN}?currentPage=1&pageLimit=100`, {
|
|
91
118
|
method: "GET",
|
|
92
119
|
headers: {
|
|
93
120
|
"Content-Type": "application/json",
|
|
94
|
-
// "x-api-token": process.env.NEXT_PUBLIC_API_KEY!,
|
|
95
121
|
},
|
|
96
122
|
});
|
|
97
|
-
if (response.ok)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
123
|
+
if (!response.ok)
|
|
124
|
+
throw new Error(`Plans fetch failed: ${response.status}`);
|
|
125
|
+
const data = await response.json();
|
|
126
|
+
setPlans((data.items || []));
|
|
101
127
|
}
|
|
102
128
|
catch (_a) {
|
|
103
129
|
generateThemeToast({
|
|
@@ -108,6 +134,9 @@ export const WorkspaceStateContextProvider = ({ children, }) => {
|
|
|
108
134
|
};
|
|
109
135
|
fetchPlans();
|
|
110
136
|
}, [t]);
|
|
137
|
+
if (!ready) {
|
|
138
|
+
return (_jsx("div", { className: "flex min-h-[50vh] items-center justify-center", children: _jsx("div", { className: "border-primary h-8 w-8 animate-spin rounded-full border-4 border-r-transparent" }) }));
|
|
139
|
+
}
|
|
111
140
|
return (_jsx(PlansContext.Provider, { value: plans, children: _jsx(WorkspaceProvider, { children: children }) }));
|
|
112
141
|
};
|
|
113
142
|
// ============================================================================
|
|
@@ -207,11 +236,18 @@ export const useWorkspaceModule = () => {
|
|
|
207
236
|
return;
|
|
208
237
|
}
|
|
209
238
|
if (data) {
|
|
239
|
+
const updated = data;
|
|
240
|
+
// Keep local state and the shared cache in sync. Invalidating without
|
|
241
|
+
// refetching used to leave every workspace-scoped module with no data.
|
|
242
|
+
dispatch({
|
|
243
|
+
type: WORKSPACE_ACTION_TYPES.SET_FORM_DATA,
|
|
244
|
+
payload: { form: updated },
|
|
245
|
+
});
|
|
246
|
+
setCachedWorkspace(updated);
|
|
210
247
|
showToast(t("messagesWorkspaceUpdated"), TOAST_VARIANT.SUCCESS);
|
|
211
|
-
invalidateWorkspaceCache();
|
|
212
248
|
resetFormAndCloseDrawer();
|
|
213
249
|
}
|
|
214
|
-
}, [resetFormAndCloseDrawer, showToast, t]);
|
|
250
|
+
}, [dispatch, resetFormAndCloseDrawer, showToast, t]);
|
|
215
251
|
const byIdCallback = useCallback(() => { }, []);
|
|
216
252
|
const deleteCallback = useCallback(() => { }, []);
|
|
217
253
|
// ============================================================================
|
|
@@ -224,7 +260,7 @@ export const useWorkspaceModule = () => {
|
|
|
224
260
|
deleteParams: {},
|
|
225
261
|
listCallback,
|
|
226
262
|
listParams: {},
|
|
227
|
-
listUrl: WORKSPACE_API_ROUTES.
|
|
263
|
+
listUrl: WORKSPACE_API_ROUTES.UNIT,
|
|
228
264
|
searchQuery: "",
|
|
229
265
|
unitByIdUrl: WORKSPACE_API_ROUTES.UNIT,
|
|
230
266
|
unitUrl: WORKSPACE_API_ROUTES.UNIT,
|
|
@@ -242,7 +278,16 @@ export const useWorkspaceModule = () => {
|
|
|
242
278
|
type: WORKSPACE_ACTION_TYPES.SET_INPUT_FIELD,
|
|
243
279
|
payload: { key: field, value },
|
|
244
280
|
});
|
|
245
|
-
|
|
281
|
+
// A retried field must not keep blocking the Save button.
|
|
282
|
+
if (state.errors[field]) {
|
|
283
|
+
const nextErrors = Object.assign({}, state.errors);
|
|
284
|
+
delete nextErrors[field];
|
|
285
|
+
dispatch({
|
|
286
|
+
type: WORKSPACE_ACTION_TYPES.SET_ERRORS,
|
|
287
|
+
payload: { errors: nextErrors },
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}, [dispatch, state.errors]);
|
|
246
291
|
const handleCloseDrawer = useCallback(() => {
|
|
247
292
|
resetFormAndCloseDrawer();
|
|
248
293
|
}, [resetFormAndCloseDrawer]);
|
|
@@ -275,7 +320,6 @@ export const useWorkspaceModule = () => {
|
|
|
275
320
|
});
|
|
276
321
|
},
|
|
277
322
|
errorCallback: (errors) => {
|
|
278
|
-
console.log("Validation errors:", errors);
|
|
279
323
|
dispatch({
|
|
280
324
|
type: WORKSPACE_ACTION_TYPES.SET_ERRORS,
|
|
281
325
|
payload: { errors },
|
|
@@ -308,5 +352,6 @@ export const useWorkspaceModule = () => {
|
|
|
308
352
|
handleSubmit,
|
|
309
353
|
handleView,
|
|
310
354
|
plans,
|
|
311
|
-
updateLoading
|
|
355
|
+
updateLoading,
|
|
356
|
+
workspace });
|
|
312
357
|
};
|
|
@@ -27,31 +27,34 @@ const SecretsEditor = ({ secrets, errors, onChange, secretPlaceholder, }) => {
|
|
|
27
27
|
// WORKSPACE FORM
|
|
28
28
|
// ============================================================================
|
|
29
29
|
export const WorkspaceForm = () => {
|
|
30
|
-
const { state, handleChange, plans } = useWorkspaceModule();
|
|
30
|
+
const { state, handleChange, plans, workspace } = useWorkspaceModule();
|
|
31
31
|
const t = useTranslations("workspace");
|
|
32
32
|
const secrets = state.secrets || {};
|
|
33
33
|
const handleSecretChange = (key, value) => {
|
|
34
34
|
handleChange("secrets", Object.assign(Object.assign({}, secrets), { [key]: value }));
|
|
35
35
|
};
|
|
36
|
+
// Plans are priced per currency. Match the workspace's agreed currency and
|
|
37
|
+
// fall back to the full list when the workspace has none (new/unpriced).
|
|
38
|
+
const currency = state.agreedCurrency || (workspace === null || workspace === void 0 ? void 0 : workspace.agreedCurrency);
|
|
39
|
+
const currencyPlans = currency
|
|
40
|
+
? plans.filter((plan) => !plan.currency || plan.currency === currency)
|
|
41
|
+
: plans;
|
|
42
|
+
const planOptions = (currencyPlans.length > 0 ? currencyPlans : plans).map((plan) => ({ value: plan.id, label: plan.name }));
|
|
36
43
|
const { enhancedComboboxElement: planIdCombo } = useEnhancedCombobox({
|
|
37
44
|
id: "planId",
|
|
38
45
|
label: t("formPlanLabel"),
|
|
39
46
|
value: state.planId || "",
|
|
40
|
-
onValueChange: (value) => handleChange("planId", value),
|
|
41
|
-
options:
|
|
42
|
-
.filter(({ name }) => name.match("PKR"))
|
|
43
|
-
.map((plan) => ({
|
|
44
|
-
value: plan.id,
|
|
45
|
-
label: plan.name,
|
|
46
|
-
})),
|
|
47
|
+
onValueChange: (value) => handleChange("planId", value || ""),
|
|
48
|
+
options: planOptions,
|
|
47
49
|
placeholder: t("formPlanPlaceholder"),
|
|
48
50
|
info: t("formPlanInfo"),
|
|
49
51
|
searchEndpoint: PLAN_API_ROUTES.UNIT,
|
|
52
|
+
searchParams: currency ? { currency } : undefined,
|
|
50
53
|
formatSearchResult: (item) => {
|
|
51
|
-
var _a, _b;
|
|
54
|
+
var _a, _b, _c, _d;
|
|
52
55
|
return ({
|
|
53
|
-
label: String((_a = item.
|
|
54
|
-
value: String((
|
|
56
|
+
label: String((_b = (_a = item.name) !== null && _a !== void 0 ? _a : item.label) !== null && _b !== void 0 ? _b : ""),
|
|
57
|
+
value: String((_d = (_c = item.id) !== null && _c !== void 0 ? _c : item.value) !== null && _d !== void 0 ? _d : ""),
|
|
55
58
|
});
|
|
56
59
|
},
|
|
57
60
|
});
|
|
@@ -16,6 +16,11 @@ interface Props {
|
|
|
16
16
|
tableDescription: string;
|
|
17
17
|
tableTitle: string;
|
|
18
18
|
userRole: USER_ROLE;
|
|
19
|
+
/**
|
|
20
|
+
* Tenant subdomain used to hydrate the workspace cache when it is empty.
|
|
21
|
+
* Optional so the module still renders in Storybook / standalone apps.
|
|
22
|
+
*/
|
|
23
|
+
subdomain?: string;
|
|
19
24
|
}
|
|
20
25
|
export declare const WorkspacePage: FC<Props>;
|
|
21
26
|
export {};
|
|
@@ -35,4 +35,4 @@ const WorkspaceInnerPage = ({ actionsButtonEdit, tableDescription, tableTitle, u
|
|
|
35
35
|
// ============================================================================
|
|
36
36
|
// ROOT PAGE COMPONENT (wraps with provider)
|
|
37
37
|
// ============================================================================
|
|
38
|
-
export const WorkspacePage = (props) => (_jsx(WorkspaceStateContextProvider, { children: _jsx(WorkspaceInnerPage, Object.assign({}, props)) }));
|
|
38
|
+
export const WorkspacePage = (props) => (_jsx(WorkspaceStateContextProvider, { subdomain: props.subdomain, children: _jsx(WorkspaceInnerPage, Object.assign({}, props)) }));
|
|
@@ -7,16 +7,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
*/
|
|
8
8
|
import { useWorkspaceModule } from "./context";
|
|
9
9
|
import { useTranslations } from "next-intl";
|
|
10
|
-
import Image from "next/image";
|
|
11
10
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@appcorp/shadcn/components/ui/card";
|
|
12
11
|
import { Badge } from "@appcorp/shadcn/components/ui/badge";
|
|
13
12
|
import { Separator } from "@appcorp/shadcn/components/ui/separator";
|
|
14
13
|
import { Building2, Globe, FileText, Image as LucideImage, CheckCircle2, XCircle, Calendar, CreditCard, Users, BookOpen, GraduationCap, Home, HardDrive, Clock, Shield, } from "lucide-react";
|
|
15
14
|
import { DATE_FORMATS, formatDate, } from "@react-pakistan/util-functions/general/format-date";
|
|
16
15
|
import { formatValue } from "@react-pakistan/util-functions/general/format-value";
|
|
16
|
+
const APP_DOMAIN = process.env.NEXT_PUBLIC_APP_DOMAIN || "edupilotpro.com";
|
|
17
17
|
export const WorkspaceView = () => {
|
|
18
|
-
|
|
18
|
+
var _a, _b;
|
|
19
|
+
const { state, workspace } = useWorkspaceModule();
|
|
19
20
|
const t = useTranslations("workspace");
|
|
21
|
+
const appDomain = APP_DOMAIN;
|
|
22
|
+
const planName = ((_a = state.plan) === null || _a === void 0 ? void 0 : _a.name) || ((_b = workspace === null || workspace === void 0 ? void 0 : workspace.plan) === null || _b === void 0 ? void 0 : _b.name) || formatValue(state.planId);
|
|
20
23
|
const formatCurrency = (amount, currency) => {
|
|
21
24
|
if (!amount)
|
|
22
25
|
return "—";
|
|
@@ -40,17 +43,14 @@ export const WorkspaceView = () => {
|
|
|
40
43
|
return `${mb} MB`;
|
|
41
44
|
return `${(mb / 1024).toFixed(1)} GB`;
|
|
42
45
|
};
|
|
43
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Building2, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("viewSectionBasicInfo") })] }), _jsx(CardDescription, { children: t("viewBasicInfoDescription") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-2 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1 md:col-span-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Building2, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("formNameLabel") })] }), _jsx("p", { className: "text-base", children: formatValue(state.name) })] }), _jsxs("div", { className: "space-y-1 md:col-span-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Globe, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("formSubdomainLabel") })] }), _jsx("p", { className: "text-base", children: formatValue(state.subdomain) }), state.subdomain && (_jsxs("p", { className: "text-muted-foreground text-sm", children: [t("viewFieldUrlPrefix"), state.subdomain, ".
|
|
46
|
+
return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Building2, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("viewSectionBasicInfo") })] }), _jsx(CardDescription, { children: t("viewBasicInfoDescription") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-2 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1 md:col-span-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Building2, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("formNameLabel") })] }), _jsx("p", { className: "text-base", children: formatValue(state.name) })] }), _jsxs("div", { className: "space-y-1 md:col-span-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Globe, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("formSubdomainLabel") })] }), _jsx("p", { className: "text-base", children: formatValue(state.subdomain) }), state.subdomain && (_jsxs("p", { className: "text-muted-foreground text-sm", children: [t("viewFieldUrlPrefix"), state.subdomain, ".", appDomain] }))] }), _jsxs("div", { className: "space-y-1 md:col-span-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(FileText, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("formDescriptionLabel") })] }), _jsx("p", { className: "text-base", children: formatValue(state.description) === "—"
|
|
44
47
|
? t("viewTextDescriptionNotProvided")
|
|
45
|
-
: state.description })] }), state.logo && (_jsxs("div", { className: "space-y-1 md:col-span-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(LucideImage, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("formLogoLabel") })] }), _jsx("div", { className: "mt-2", children: _jsx(
|
|
48
|
+
: state.description })] }), state.logo && (_jsxs("div", { className: "space-y-1 md:col-span-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(LucideImage, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("formLogoLabel") })] }), _jsx("div", { className: "mt-2", children: _jsx("img", { src: state.logo, alt: t("viewFieldLogoAlt"), width: 64, height: 64, className: "h-16 w-16 rounded border object-contain", onError: (e) => {
|
|
46
49
|
e.currentTarget.style.display = "none";
|
|
47
50
|
} }) })] })), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldStatus") }), _jsxs(Badge, { variant: state.enabled ? "default" : "destructive", className: "gap-1", children: [state.enabled ? (_jsx(CheckCircle2, { className: "h-3 w-3" })) : (_jsx(XCircle, { className: "h-3 w-3" })), state.enabled
|
|
48
51
|
? t("viewStatusEnabled")
|
|
49
|
-
: t("viewStatusDisabled")] })] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CreditCard, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("viewSectionPlanBilling") })] }), _jsx(CardDescription, { children: t("viewPlanBillingDescription") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CreditCard, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldPlanIdLabel") })] }), _jsx("p", { className: "
|
|
50
|
-
.length === 0 ? (_jsx("p", { className: "text-muted-foreground italic", children: t("viewFieldNoSecretsConfigured") })) : (Object.entries(state.secrets).map(([k, v]) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
? `${(_a = String(v)) === null || _a === void 0 ? void 0 : _a.slice(0, 3)}${"*".repeat(Math.max(0, String(v).length - 3))}`
|
|
54
|
-
: "—" })] }, k));
|
|
55
|
-
})) })) : (_jsx("p", { className: "text-muted-foreground italic", children: t("viewFieldNoSecretsConfigured") })), _jsx("p", { className: "text-muted-foreground mt-2 text-xs", children: t("viewFieldSecretsHelpText") })] }) }) })] })] }));
|
|
52
|
+
: t("viewStatusDisabled")] })] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CreditCard, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("viewSectionPlanBilling") })] }), _jsx(CardDescription, { children: t("viewPlanBillingDescription") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CreditCard, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldPlanIdLabel") })] }), _jsx("p", { className: "text-base", children: planName })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldPlanStartDateLabel") })] }), _jsx("p", { className: "text-base", children: formatDate(state.planStartDate || "", DATE_FORMATS.LOCALE_DATE) })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldPlanEndDateLabel") })] }), _jsx("p", { className: "text-base", children: formatDate(state.planEndDate || "", DATE_FORMATS.LOCALE_DATE) })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Clock, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldBillingIntervalLabel") })] }), _jsx("p", { className: "text-base", children: formatBillingInterval(state.billingInterval) })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CreditCard, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldAgreedPriceLabel") })] }), _jsx("p", { className: "text-base", children: formatCurrency(state.agreedPrice, state.agreedCurrency) })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Globe, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldCurrencyLabel") })] }), _jsx("p", { className: "text-base", children: formatValue(state.agreedCurrency) })] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Users, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("viewSectionUsageStats") })] }), _jsx(CardDescription, { children: t("viewUsageStatsDescription") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-2 gap-6 md:grid-cols-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(GraduationCap, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldStudentsLabel") })] }), _jsx("p", { className: "text-primary text-2xl font-bold", children: state.studentsCount })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Users, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldTeachersLabel") })] }), _jsx("p", { className: "text-primary text-2xl font-bold", children: state.teachersCount })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(BookOpen, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldClassesLabel") })] }), _jsx("p", { className: "text-primary text-2xl font-bold", children: state.classesCount })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(BookOpen, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldCoursesLabel") })] }), _jsx("p", { className: "text-primary text-2xl font-bold", children: state.coursesCount })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Home, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldFamiliesLabel") })] }), _jsx("p", { className: "text-primary text-2xl font-bold", children: state.familiesCount })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(HardDrive, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldStorageUsedLabel") })] }), _jsx("p", { className: "text-primary text-2xl font-bold", children: formatStorage(state.storageUsedMB) })] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Shield, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("viewSectionSecrets") })] }), _jsx(CardDescription, { children: t("viewSecretsDescription") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsx("div", { className: "space-y-4", children: _jsxs("div", { children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(Shield, { className: "text-muted-foreground h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("viewFieldSecretsJsonLabel") })] }), state.secrets ? (_jsx("div", { className: "space-y-2", children: Object.keys(state.secrets)
|
|
53
|
+
.length === 0 ? (_jsx("p", { className: "text-muted-foreground italic", children: t("viewFieldNoSecretsConfigured") })) : (Object.entries(state.secrets).map(([k, v]) => (_jsxs("div", { className: "bg-muted/10 border-border/60 flex items-center justify-between rounded-md border p-3", children: [_jsx("div", { className: "text-foreground text-sm font-medium", children: k }), _jsx("div", { className: "text-foreground/80 text-sm", children: typeof v === "string" && v.length > 0
|
|
54
|
+
? t("viewFieldSecretConfigured")
|
|
55
|
+
: "—" })] }, k)))) })) : (_jsx("p", { className: "text-muted-foreground italic", children: t("viewFieldNoSecretsConfigured") })), _jsx("p", { className: "text-muted-foreground mt-2 text-xs", children: t("viewFieldSecretsHelpText") })] }) }) })] })] }));
|
|
56
56
|
};
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* - `WORKSPACE_USER_DRAWER` — drawer type constants
|
|
18
18
|
* - `useWorkspaceUserModule()` — hook that returns handlers and state
|
|
19
19
|
*/
|
|
20
|
-
import { RoleBE, WorkspaceBE, WorkspaceUserBE } from "../../type";
|
|
21
|
-
import { type RowAction } from "@appcorp/shadcn/components/enhanced-table";
|
|
20
|
+
import { RoleBE, UserBE, WorkspaceBE, WorkspaceUserBE } from "../../type";
|
|
21
|
+
import { type RowAction, type TableRow } from "@appcorp/shadcn/components/enhanced-table";
|
|
22
22
|
export declare const WORKSPACE_USER_DRAWER: {
|
|
23
23
|
readonly FILTER_DRAWER: string;
|
|
24
24
|
readonly FORM_DRAWER: string;
|
|
@@ -53,6 +53,7 @@ export declare const WORKSPACE_USER_ACTION_TYPES: {
|
|
|
53
53
|
userId: string;
|
|
54
54
|
workspaceId: string;
|
|
55
55
|
role: RoleBE | undefined;
|
|
56
|
+
user: UserBE | undefined;
|
|
56
57
|
workspace: WorkspaceBE | undefined;
|
|
57
58
|
}>, initialWorkspaceUserState: {
|
|
58
59
|
items: WorkspaceUserBE[];
|
|
@@ -70,6 +71,7 @@ export declare const WORKSPACE_USER_ACTION_TYPES: {
|
|
|
70
71
|
userId: string;
|
|
71
72
|
workspaceId: string;
|
|
72
73
|
role: RoleBE | undefined;
|
|
74
|
+
user: UserBE | undefined;
|
|
73
75
|
workspace: WorkspaceBE | undefined;
|
|
74
76
|
}, WorkspaceUserProvider: import("react").FC<{
|
|
75
77
|
children: React.ReactNode;
|
|
@@ -89,6 +91,7 @@ export declare const WORKSPACE_USER_ACTION_TYPES: {
|
|
|
89
91
|
userId: string;
|
|
90
92
|
workspaceId: string;
|
|
91
93
|
role: RoleBE | undefined;
|
|
94
|
+
user: UserBE | undefined;
|
|
92
95
|
workspace: WorkspaceBE | undefined;
|
|
93
96
|
}, action: any) => {
|
|
94
97
|
items: WorkspaceUserBE[];
|
|
@@ -106,6 +109,7 @@ export declare const WORKSPACE_USER_ACTION_TYPES: {
|
|
|
106
109
|
userId: string;
|
|
107
110
|
workspaceId: string;
|
|
108
111
|
role: RoleBE | undefined;
|
|
112
|
+
user: UserBE | undefined;
|
|
109
113
|
workspace: WorkspaceBE | undefined;
|
|
110
114
|
}, useWorkspaceUserContext: () => import("@react-pakistan/util-functions/factory/generic-module-factory").GenericModuleContextWithHandlers<{
|
|
111
115
|
items: WorkspaceUserBE[];
|
|
@@ -123,6 +127,7 @@ export declare const WORKSPACE_USER_ACTION_TYPES: {
|
|
|
123
127
|
userId: string;
|
|
124
128
|
workspaceId: string;
|
|
125
129
|
role: RoleBE | undefined;
|
|
130
|
+
user: UserBE | undefined;
|
|
126
131
|
workspace: WorkspaceBE | undefined;
|
|
127
132
|
}>;
|
|
128
133
|
export declare const useWorkspaceUserModule: () => {
|
|
@@ -133,15 +138,15 @@ export declare const useWorkspaceUserModule: () => {
|
|
|
133
138
|
handleChange: (field: string, value: string | number | boolean | undefined | Record<string, string>) => void;
|
|
134
139
|
handleCloseDrawer: () => void;
|
|
135
140
|
handleCreate: () => void;
|
|
136
|
-
handleDelete: (row?:
|
|
137
|
-
handleEdit: () => void;
|
|
141
|
+
handleDelete: (row?: TableRow) => void;
|
|
142
|
+
handleEdit: (row?: TableRow) => void;
|
|
138
143
|
handleFilters: () => void;
|
|
139
144
|
handleMoreActions: () => void;
|
|
140
145
|
handlePageChange: (page: number | unknown) => void;
|
|
141
146
|
handlePageLimitChange: (limit: number) => void;
|
|
142
147
|
handleSearch: (query: string) => void;
|
|
143
148
|
handleSubmit: () => void;
|
|
144
|
-
handleView: () => void;
|
|
149
|
+
handleView: (row?: TableRow) => void;
|
|
145
150
|
headerActions: {
|
|
146
151
|
enabled: boolean;
|
|
147
152
|
handleOnClick: () => void;
|
|
@@ -169,6 +174,7 @@ export declare const useWorkspaceUserModule: () => {
|
|
|
169
174
|
userId: string;
|
|
170
175
|
workspaceId: string;
|
|
171
176
|
role: RoleBE | undefined;
|
|
177
|
+
user: UserBE | undefined;
|
|
172
178
|
workspace: WorkspaceBE | undefined;
|
|
173
179
|
};
|
|
174
180
|
dispatch: React.Dispatch<any>;
|