ui-soxo-bootstrap-core 2.6.40-dev.0 → 2.6.40-dev.1
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/.github/workflows/npm-publish.yml +40 -33
- package/DEVELOPER_GUIDE.md +38 -9
- package/core/components/index.js +2 -11
- package/core/components/landing-api/landing-api.js +165 -5
- package/core/components/license-management/license-alert.js +97 -0
- package/core/lib/components/global-header/global-header.js +20 -77
- package/core/lib/components/index.js +2 -2
- package/core/lib/modules/generic/generic-list/ExportReactCSV.js +334 -8
- package/core/lib/modules/generic/generic-list/generic-list.scss +34 -0
- package/core/models/core-scripts/core-scripts.js +22 -1
- package/core/models/menus/menus.js +29 -1
- package/core/models/roles/components/role-add/menu-label.js +14 -14
- package/core/models/roles/components/role-add/menu-tree.js +127 -127
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +202 -5
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +73 -0
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +25 -5
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +1 -0
- package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +519 -0
- package/package.json +1 -1
|
@@ -1,33 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
1
|
+
name: Node.js Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish-npm:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-node@v4
|
|
13
|
+
with:
|
|
14
|
+
node-version: 20
|
|
15
|
+
registry-url: https://registry.npmjs.org/
|
|
16
|
+
- run: npm install
|
|
17
|
+
|
|
18
|
+
- name: Determine npm dist-tag
|
|
19
|
+
id: dist_tag
|
|
20
|
+
shell: bash
|
|
21
|
+
run: |
|
|
22
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
23
|
+
echo "package.json version: $VERSION"
|
|
24
|
+
echo "release tag: ${GITHUB_REF_NAME}"
|
|
25
|
+
if [[ "v${VERSION}" != "${GITHUB_REF_NAME}" ]]; then
|
|
26
|
+
echo "::error::Release tag '${GITHUB_REF_NAME}' does not match package.json version 'v${VERSION}'."
|
|
27
|
+
echo "::error::Bump the version with 'npm version' and re-create the release."
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
if [[ "$VERSION" == *-dev* ]]; then
|
|
31
|
+
echo "tag=dev" >> "$GITHUB_OUTPUT"
|
|
32
|
+
echo "Will publish with dist-tag: dev"
|
|
33
|
+
else
|
|
34
|
+
echo "tag=latest" >> "$GITHUB_OUTPUT"
|
|
35
|
+
echo "Will publish with dist-tag: latest"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
- run: npm publish --access public --tag ${{ steps.dist_tag.outputs.tag }}
|
|
39
|
+
env:
|
|
40
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/DEVELOPER_GUIDE.md
CHANGED
|
@@ -17,6 +17,7 @@ Incorrect versioning or incorrect tags will break the publish pipeline — follo
|
|
|
17
17
|
- Publishing via GitHub Release UI
|
|
18
18
|
- How GitHub Action Detects Release Type
|
|
19
19
|
- Summary Table
|
|
20
|
+
- CI/CD Authentication (Trusted Publishing)
|
|
20
21
|
- Common Mistakes & Fixes
|
|
21
22
|
|
|
22
23
|
---
|
|
@@ -255,17 +256,14 @@ npm publish --tag dev
|
|
|
255
256
|
|
|
256
257
|
# ⚙️ How GitHub Action Detects Release Type
|
|
257
258
|
|
|
258
|
-
|
|
259
|
+
The workflow reads the `version` field from `package.json` at publish time:
|
|
259
260
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
261
|
+
| Condition | Command | Result |
|
|
262
|
+
| ---------------------------- | ------------------------------------------------------- | ---------------------------------- |
|
|
263
|
+
| Version contains `-dev` | `npm publish --provenance --access public --tag dev` | Publishes to the `dev` dist-tag |
|
|
264
|
+
| Version has no `-dev` suffix | `npm publish --provenance --access public --tag latest` | Publishes to the `latest` dist-tag |
|
|
263
265
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
```
|
|
267
|
-
npm publish
|
|
268
|
-
```
|
|
266
|
+
The workflow also enforces that the GitHub release tag matches `v<version>` from `package.json` and fails the run immediately if they diverge — this prevents the most common publish failure described below.
|
|
269
267
|
|
|
270
268
|
---
|
|
271
269
|
|
|
@@ -283,6 +281,37 @@ npm publish
|
|
|
283
281
|
|
|
284
282
|
---
|
|
285
283
|
|
|
284
|
+
# 🔐 CI/CD Authentication (Trusted Publishing)
|
|
285
|
+
|
|
286
|
+
As of npm's 2025 policy changes, classic automation tokens (`NPM_TOKEN`) are deprecated. This repo now authenticates to npm via **OIDC Trusted Publishing** — GitHub Actions exchanges a short-lived OIDC token for a publish token at run time, so **no secret is stored in the repository**.
|
|
287
|
+
|
|
288
|
+
## What this means for developers
|
|
289
|
+
|
|
290
|
+
Nothing. You still follow the same flow: `npm version` → push tag → create GitHub Release. The auth happens transparently in CI.
|
|
291
|
+
|
|
292
|
+
## What this means for maintainers
|
|
293
|
+
|
|
294
|
+
The first-time setup on npmjs.com must be done once per package:
|
|
295
|
+
|
|
296
|
+
1. Log in to [npmjs.com](https://www.npmjs.com) → open the package (`ui-soxo-bootstrap-core`) → **Settings**.
|
|
297
|
+
2. Under **Trusted Publisher**, click **Add trusted publisher** and fill in:
|
|
298
|
+
- Publisher: **GitHub Actions**
|
|
299
|
+
- Organization or user: `soxo-tech`
|
|
300
|
+
- Repository: `bootstrap-core`
|
|
301
|
+
- Workflow filename: `npm-publish.yml`
|
|
302
|
+
- Environment name: *(leave blank)*
|
|
303
|
+
3. Save. Any old `NPM_TOKEN` repository secret can be removed.
|
|
304
|
+
|
|
305
|
+
## Runtime requirements
|
|
306
|
+
|
|
307
|
+
The workflow runs on Node 20 and upgrades npm to the latest CLI (`npm install -g npm@latest`) because OIDC trusted publishing requires **npm ≥ 11.5.1**. The `--provenance` flag attaches a verifiable build attestation to every published version, visible on the npmjs.com package page.
|
|
308
|
+
|
|
309
|
+
## If publish fails with `403 Forbidden` or `ENEEDAUTH`
|
|
310
|
+
|
|
311
|
+
The trusted publisher config on npmjs.com no longer matches the workflow. Check that org, repo, and workflow filename match exactly — including case.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
286
315
|
# ⚠️ Common Mistakes & Fixes
|
|
287
316
|
|
|
288
317
|
| Mistake | Issue | Fix |
|
package/core/components/index.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
1
|
import LandingAPI from './landing-api/landing-api';
|
|
6
2
|
|
|
7
3
|
import ExtraInfoDetail from './extra-info/extra-info-details';
|
|
@@ -11,11 +7,6 @@ import RootApplicationAPI from './root-application-api/root-application-api';
|
|
|
11
7
|
import { HomePageAPI } from '../modules';
|
|
12
8
|
|
|
13
9
|
import { ExternalWindow } from './external-window/external-window';
|
|
10
|
+
import LicenseAlert from './license-management/license-alert';
|
|
14
11
|
|
|
15
|
-
export {
|
|
16
|
-
LandingAPI,
|
|
17
|
-
RootApplicationAPI,
|
|
18
|
-
ExtraInfoDetail,
|
|
19
|
-
HomePageAPI,
|
|
20
|
-
ExternalWindow
|
|
21
|
-
}
|
|
12
|
+
export { LandingAPI, RootApplicationAPI, ExtraInfoDetail, HomePageAPI, ExternalWindow, LicenseAlert };
|
|
@@ -2,9 +2,20 @@ import { useContext, useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { Route, Switch } from 'react-router-dom';
|
|
4
4
|
|
|
5
|
-
import { Skeleton } from 'antd';
|
|
6
|
-
|
|
7
|
-
import {
|
|
5
|
+
import { Skeleton, message, Modal } from 'antd';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
GlobalHeader,
|
|
9
|
+
ChangePassword,
|
|
10
|
+
useTranslation,
|
|
11
|
+
GlobalContext,
|
|
12
|
+
ModuleRoutes,
|
|
13
|
+
SpotlightSearch,
|
|
14
|
+
SettingsUtil,
|
|
15
|
+
Profile,
|
|
16
|
+
Card,
|
|
17
|
+
safeJSON,
|
|
18
|
+
} from '../../lib';
|
|
8
19
|
|
|
9
20
|
import './landing-api.scss';
|
|
10
21
|
|
|
@@ -46,7 +57,6 @@ function getRandomMessage(previousMessage = '') {
|
|
|
46
57
|
*/
|
|
47
58
|
export default function LandingApi({ history, CustomComponents, CustomModels, appSettings, transitionPending = false, onHomeReady, ...props }) {
|
|
48
59
|
const [loader, setLoader] = useState(false);
|
|
49
|
-
|
|
50
60
|
// const [modules, setModules] = useState([]);
|
|
51
61
|
|
|
52
62
|
const [connected] = useState();
|
|
@@ -59,11 +69,31 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
59
69
|
|
|
60
70
|
const [meta, setMeta] = useState({});
|
|
61
71
|
const [loadingMessage, setLoadingMessage] = useState('');
|
|
72
|
+
const [licenseData, setLicenseData] = useState(null);
|
|
73
|
+
|
|
74
|
+
const [licAlert, setLicAlert] = useState(false);
|
|
75
|
+
// License data state
|
|
62
76
|
|
|
63
77
|
// const [reports, setReports] = useState([]);
|
|
64
78
|
|
|
65
79
|
var config = {};
|
|
66
80
|
|
|
81
|
+
//fetch license summary
|
|
82
|
+
// const fetchSummary = async () => {
|
|
83
|
+
// try {
|
|
84
|
+
// const res = await MenusAPI.getSummary();
|
|
85
|
+
// if (res?.data) {
|
|
86
|
+
// setLicenseData(res?.data);
|
|
87
|
+
// setLicAlert(true);
|
|
88
|
+
// } else {
|
|
89
|
+
// setLicenseData(null);
|
|
90
|
+
// setLicAlert(false);
|
|
91
|
+
// }
|
|
92
|
+
// } catch (err) {
|
|
93
|
+
// console.error(err);
|
|
94
|
+
// }
|
|
95
|
+
// };
|
|
96
|
+
|
|
67
97
|
// Variable decides the control of homepage
|
|
68
98
|
// #TODO This is a temporary fix - Homemage
|
|
69
99
|
|
|
@@ -73,6 +103,70 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
73
103
|
disableHomepage = JSON.parse(process.env.REACT_APP_DISABLEHOMEPAGE);
|
|
74
104
|
}
|
|
75
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Normalizes the user's branch access list from `organization_details`.
|
|
108
|
+
*
|
|
109
|
+
* The API can return `organization_details` as a JSON string, so we always
|
|
110
|
+
* parse it through `safeJSON` before reading the branch collection.
|
|
111
|
+
*
|
|
112
|
+
* @returns {Array} List of branches the current user can access.
|
|
113
|
+
*/
|
|
114
|
+
const getAccessibleBranches = () => {
|
|
115
|
+
const orgDetails = safeJSON(user?.organization_details);
|
|
116
|
+
return Array.isArray(orgDetails?.branch) ? orgDetails.branch : [];
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Resolves the currently selected branch record using the persisted db pointer.
|
|
121
|
+
*
|
|
122
|
+
* @param {Array} branches
|
|
123
|
+
* @returns {Object|null}
|
|
124
|
+
*/
|
|
125
|
+
const getCurrentBranchRecord = (branches) => {
|
|
126
|
+
const currentDbPtr = localStorage.getItem('db_ptr');
|
|
127
|
+
return branches.find((branch) => String(branch.dbPtr) === String(currentDbPtr)) || null;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Resolves the target branch from the URL `index` query parameter.
|
|
132
|
+
*
|
|
133
|
+
* @param {Array} branches
|
|
134
|
+
* @param {string|null} branchId
|
|
135
|
+
* @returns {Object|null}
|
|
136
|
+
*/
|
|
137
|
+
const getBranchRecordById = (branches, branchId) => {
|
|
138
|
+
return branches.find((branch) => String(branch.branch_id) === String(branchId)) || null;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Persists branch-specific auth data after a successful switch.
|
|
143
|
+
*
|
|
144
|
+
* @param {Object} tokenBundle
|
|
145
|
+
* @param {string} dbPtr
|
|
146
|
+
*/
|
|
147
|
+
const persistBranchSession = (tokenBundle, dbPtr) => {
|
|
148
|
+
const accessToken = tokenBundle?.access_token;
|
|
149
|
+
const refreshToken = tokenBundle?.refresh_token;
|
|
150
|
+
|
|
151
|
+
if (accessToken) localStorage.setItem('access_token', accessToken);
|
|
152
|
+
if (refreshToken) localStorage.setItem('refresh_token', refreshToken);
|
|
153
|
+
if (dbPtr) localStorage.setItem('db_ptr', dbPtr);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const fetchSummary = async () => {
|
|
157
|
+
try {
|
|
158
|
+
const res = await MenusAPI.getSummary();
|
|
159
|
+
if (res?.data) {
|
|
160
|
+
setLicenseData(res?.data);
|
|
161
|
+
setLicAlert(true);
|
|
162
|
+
} else {
|
|
163
|
+
setLicenseData(null);
|
|
164
|
+
setLicAlert(false);
|
|
165
|
+
}
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error(err);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
76
170
|
// useEffect(() => {
|
|
77
171
|
|
|
78
172
|
// // Initialize the menus for the logged in user
|
|
@@ -88,6 +182,67 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
88
182
|
// }
|
|
89
183
|
// }, [loader]);
|
|
90
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Synchronizes the active branch with the `index` query parameter.
|
|
187
|
+
*
|
|
188
|
+
* Flow:
|
|
189
|
+
* 1. Read the target branch id from the URL.
|
|
190
|
+
* 2. Compare it against the branch represented by the current `db_ptr`.
|
|
191
|
+
* 3. Switch branch only when the user has access and the branch actually differs.
|
|
192
|
+
* 4. Refresh auth/profile state and reload menus for the new branch context.
|
|
193
|
+
*/
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
const handleUrlBranchSwitch = async () => {
|
|
196
|
+
const searchParams = new URLSearchParams(history.location.search);
|
|
197
|
+
const urlDbPtr = searchParams.get('index');
|
|
198
|
+
if (!urlDbPtr) return;
|
|
199
|
+
|
|
200
|
+
const accessibleBranches = getAccessibleBranches();
|
|
201
|
+
const currentBranch = getCurrentBranchRecord(accessibleBranches);
|
|
202
|
+
const targetBranch = getBranchRecordById(accessibleBranches, urlDbPtr);
|
|
203
|
+
|
|
204
|
+
if (!targetBranch || String(currentBranch?.branch_id) === String(urlDbPtr)) return;
|
|
205
|
+
|
|
206
|
+
setLoader(true);
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const switchResult = await MenusAPI.switchBranch(
|
|
210
|
+
{
|
|
211
|
+
firm_id: targetBranch.firm_ptr,
|
|
212
|
+
branch_id: targetBranch.branch_id,
|
|
213
|
+
},
|
|
214
|
+
targetBranch.dbPtr
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
if (!switchResult?.success) {
|
|
218
|
+
Modal.error({
|
|
219
|
+
title: 'Branch Switch Failed',
|
|
220
|
+
content: switchResult?.message || 'An error occurred while attempting to switch branches.',
|
|
221
|
+
});
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
persistBranchSession(switchResult?.token, targetBranch.dbPtr);
|
|
226
|
+
window.dispatchEvent(new CustomEvent('branchChanged', { detail: targetBranch.dbPtr }));
|
|
227
|
+
|
|
228
|
+
const accessToken = switchResult?.token?.access_token;
|
|
229
|
+
const profileResult = await MenusAPI.getProfile(accessToken);
|
|
230
|
+
const updatedUser = { ...profileResult, loggedCheckDone: true };
|
|
231
|
+
|
|
232
|
+
dispatch({ type: 'user', payload: updatedUser });
|
|
233
|
+
localStorage.setItem('userInfo', JSON.stringify(updatedUser));
|
|
234
|
+
|
|
235
|
+
await initializeUserMenus();
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.error('Auto branch switch failed:', error);
|
|
238
|
+
} finally {
|
|
239
|
+
setLoader(false);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
if (user?.id && history?.location) handleUrlBranchSwitch();
|
|
244
|
+
}, [history?.location?.search, user?.id]);
|
|
245
|
+
|
|
91
246
|
useEffect(() => {
|
|
92
247
|
// Initialize the menus for the logged in user
|
|
93
248
|
initializeUserMenus();
|
|
@@ -138,9 +293,12 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
138
293
|
*/
|
|
139
294
|
async function initializeUserMenus() {
|
|
140
295
|
// need to find what implement, with a login who has the respective value ("wug_custreportids")
|
|
296
|
+
|
|
141
297
|
const report = await loadScripts(user);
|
|
142
298
|
|
|
143
299
|
await loadMenus(report);
|
|
300
|
+
// fetch license summary
|
|
301
|
+
fetchSummary();
|
|
144
302
|
}
|
|
145
303
|
|
|
146
304
|
// const keyMap = {
|
|
@@ -162,7 +320,7 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
162
320
|
setLoader(true);
|
|
163
321
|
|
|
164
322
|
// setReports(report)
|
|
165
|
-
|
|
323
|
+
fetchSummary();
|
|
166
324
|
const result = await MenusAPI.getMenus(user);
|
|
167
325
|
|
|
168
326
|
// console.log(result);
|
|
@@ -292,6 +450,8 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
292
450
|
modules={allModules}
|
|
293
451
|
user={user}
|
|
294
452
|
history={history}
|
|
453
|
+
licenseData={licenseData}
|
|
454
|
+
licAlert={licAlert}
|
|
295
455
|
>
|
|
296
456
|
{loader ? (
|
|
297
457
|
<Card className="skeleton-card">
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Alert } from 'antd';
|
|
2
|
+
import React, { useState, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
export default function LicenseAlert({ data }) {
|
|
5
|
+
// setting visibility of alert based on license status
|
|
6
|
+
const [visible, setVisible] = useState(true);
|
|
7
|
+
// resolve alert configuration based on license data
|
|
8
|
+
const alertConfig = resolveLicenseAlert(data);
|
|
9
|
+
// auto-hide alert after 10 seconds or when data changes
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (alertConfig) {
|
|
12
|
+
setVisible(true);
|
|
13
|
+
|
|
14
|
+
const timer = setTimeout(() => {
|
|
15
|
+
setVisible(false);
|
|
16
|
+
}, 10000); // 10 seconds
|
|
17
|
+
|
|
18
|
+
return () => {
|
|
19
|
+
clearTimeout(timer);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}, [data]);
|
|
23
|
+
// if no alert configuration or not visible, render nothing
|
|
24
|
+
if (!alertConfig || !visible) return null;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
// render the alert with appropriate type, message, and description
|
|
28
|
+
<Alert
|
|
29
|
+
type={alertConfig.type}
|
|
30
|
+
message={alertConfig.message}
|
|
31
|
+
description={alertConfig.description}
|
|
32
|
+
showIcon
|
|
33
|
+
closable
|
|
34
|
+
onClose={() => setVisible(false)}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
// function to determine alert configuration based on license data
|
|
39
|
+
function resolveLicenseAlert(data) {
|
|
40
|
+
if (!data) return null;
|
|
41
|
+
// destructure relevant fields from license data
|
|
42
|
+
const { status, expiresInDays, isExpiringSoon, gracePeriod } = data;
|
|
43
|
+
|
|
44
|
+
// ===== NOT INSTALLED =====
|
|
45
|
+
if (status === 'NOT_INSTALLED') {
|
|
46
|
+
return {
|
|
47
|
+
type: 'error',
|
|
48
|
+
message: 'License not found',
|
|
49
|
+
description: 'Please install a valid license to continue.',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ===== GRACE PERIOD =====
|
|
54
|
+
if (gracePeriod) {
|
|
55
|
+
return {
|
|
56
|
+
type: 'warning',
|
|
57
|
+
message: 'Grace period mode',
|
|
58
|
+
description: 'License expired. Running in read-only mode.',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ===== EXPIRING SOON =====
|
|
63
|
+
if (status === 'ACTIVE' && isExpiringSoon) {
|
|
64
|
+
let descriptionText = '';
|
|
65
|
+
// customize message based on how soon the license is expiring
|
|
66
|
+
if (expiresInDays === 0) {
|
|
67
|
+
descriptionText = 'Your license will expire today. Please renew immediately.';
|
|
68
|
+
} else {
|
|
69
|
+
descriptionText = `Your license will expire in ${expiresInDays} days. Please plan for renewal.`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
type: 'warning',
|
|
74
|
+
message: 'License expiring soon',
|
|
75
|
+
description: descriptionText,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ===== NOT INSTALLED =====
|
|
80
|
+
if (status === 'NOT_INSTALLED') {
|
|
81
|
+
return {
|
|
82
|
+
type: 'error',
|
|
83
|
+
message: 'License not found',
|
|
84
|
+
description: 'Please install a valid license to continue.',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// =====EXPIRED=====
|
|
88
|
+
if (status === 'EXPIRED') {
|
|
89
|
+
return {
|
|
90
|
+
type: 'error',
|
|
91
|
+
message: 'License expired',
|
|
92
|
+
description: 'Your license has expired. Please renew or install a new license.',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return null;
|
|
97
|
+
}
|