@juv/codego-react-ui 3.5.21 → 3.6.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/README.md +55 -0
- package/dist/index.cjs +139 -178
- package/dist/index.d.cts +41 -23
- package/dist/index.d.ts +41 -23
- package/dist/index.global.js +132 -177
- package/dist/index.js +143 -186
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -619,6 +619,14 @@ function AnnouncementsPage() {
|
|
|
619
619
|
}
|
|
620
620
|
```
|
|
621
621
|
|
|
622
|
+
> **Note:** `useServerBulletin` uses the internal `codego` api client which automatically attaches the Bearer token from `localStorage.getItem("token")`. For encrypted Laravel responses, pass `encrypt: true` and set `VITE_LARAVEL_KEY` in your `.env`:
|
|
623
|
+
>
|
|
624
|
+
> ```
|
|
625
|
+
> VITE_LARAVEL_KEY=base64:your_app_key_here
|
|
626
|
+
> ```
|
|
627
|
+
>
|
|
628
|
+
> Or pass the key directly: `key: "base64:..."`. The response can be a plain array `[...]` or a paginated object `{ data: [...], total, per_page, ... }` — both are handled automatically.
|
|
629
|
+
|
|
622
630
|
---
|
|
623
631
|
|
|
624
632
|
## LeafletMap Props
|
|
@@ -731,6 +739,53 @@ function AnnouncementsPage() {
|
|
|
731
739
|
|
|
732
740
|
---
|
|
733
741
|
|
|
742
|
+
## Laravel Response Decryption
|
|
743
|
+
|
|
744
|
+
Use `decryptResponse` to decrypt Laravel-encrypted API responses (AES-256-CBC).
|
|
745
|
+
|
|
746
|
+
### Setup
|
|
747
|
+
|
|
748
|
+
Add your Laravel `APP_KEY` to `.env`:
|
|
749
|
+
|
|
750
|
+
```env
|
|
751
|
+
VITE_LARAVEL_KEY=base64:your_laravel_app_key_here
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Usage
|
|
755
|
+
|
|
756
|
+
```tsx
|
|
757
|
+
import { api, decryptResponse } from "@juv/codego-react-ui"
|
|
758
|
+
|
|
759
|
+
type Certificate = { id: number; name: string }
|
|
760
|
+
|
|
761
|
+
const fetchCertificates = async () => {
|
|
762
|
+
const data = await api.get<string>('/certificate')
|
|
763
|
+
const decoded = decryptResponse<Certificate[]>(data)
|
|
764
|
+
console.log(decoded)
|
|
765
|
+
}
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
> `api.get` should be typed as `string` since the raw response is an encrypted payload. `decryptResponse` reads `VITE_LARAVEL_KEY` automatically.
|
|
769
|
+
|
|
770
|
+
You can also pass the key explicitly:
|
|
771
|
+
|
|
772
|
+
```tsx
|
|
773
|
+
const decoded = decryptResponse<Certificate[]>(data, "base64:your_key_here")
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### API
|
|
777
|
+
|
|
778
|
+
| Export | Description |
|
|
779
|
+
|---|---|
|
|
780
|
+
| `decryptResponse(response, key?)` | Decrypts a Laravel-encrypted string or `{ data: string }` object. |
|
|
781
|
+
| `decryptLaravelPayload(payload, key?)` | Low-level decryption of a raw encrypted payload string. |
|
|
782
|
+
| `getLaravelSecretKey()` | Reads the key from `VITE_LARAVEL_KEY`, `REACT_APP_LARAVEL_KEY`, or `window.__LARAVEL_KEY__`. |
|
|
783
|
+
| `parseLaravelKey(secretKey)` | Parses a `base64:...` or raw key string into a `CryptoJS.WordArray`. |
|
|
784
|
+
| `parseLaravelEncryptedPayload(payload)` | Parses a base64-encoded Laravel encrypted payload into `{ iv, value, mac }`. |
|
|
785
|
+
| `LaravelEncryptedPayload` | Type for the parsed payload object. |
|
|
786
|
+
|
|
787
|
+
---
|
|
788
|
+
|
|
734
789
|
## Run Locally
|
|
735
790
|
|
|
736
791
|
**Prerequisites:** Node.js
|
package/dist/index.cjs
CHANGED
|
@@ -127,7 +127,11 @@ __export(index_exports, {
|
|
|
127
127
|
Wizard: () => Wizard,
|
|
128
128
|
api: () => api,
|
|
129
129
|
createStore: () => createStore,
|
|
130
|
-
|
|
130
|
+
decryptLaravelPayload: () => decryptLaravelPayload,
|
|
131
|
+
decryptResponse: () => decryptResponse,
|
|
132
|
+
getLaravelSecretKey: () => getLaravelSecretKey,
|
|
133
|
+
parseLaravelEncryptedPayload: () => parseLaravelEncryptedPayload,
|
|
134
|
+
parseLaravelKey: () => parseLaravelKey,
|
|
131
135
|
useServerDataGrid: () => useServerDataGrid,
|
|
132
136
|
useServerTable: () => useServerTable,
|
|
133
137
|
useTheme: () => useTheme,
|
|
@@ -165,6 +169,7 @@ function FloatingPortal({ children }) {
|
|
|
165
169
|
|
|
166
170
|
// src/components/ui/button.tsx
|
|
167
171
|
var React = __toESM(require("react"), 1);
|
|
172
|
+
var ReactDOM2 = __toESM(require("react-dom"), 1);
|
|
168
173
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
169
174
|
var Button = React.forwardRef(
|
|
170
175
|
({
|
|
@@ -243,6 +248,8 @@ var Button = React.forwardRef(
|
|
|
243
248
|
confirmBeforeClickModalTitle,
|
|
244
249
|
confirmBeforeClickModalContent,
|
|
245
250
|
confirmBeforeClickFooterAction,
|
|
251
|
+
onConfirm,
|
|
252
|
+
onCancel,
|
|
246
253
|
href,
|
|
247
254
|
target,
|
|
248
255
|
as = "button",
|
|
@@ -338,11 +345,13 @@ var Button = React.forwardRef(
|
|
|
338
345
|
};
|
|
339
346
|
function handleConfirmProceed() {
|
|
340
347
|
setConfirmOpen(false);
|
|
348
|
+
onConfirm?.();
|
|
341
349
|
onClick?.(pendingEvent.current ?? void 0);
|
|
342
350
|
pendingEvent.current = null;
|
|
343
351
|
}
|
|
344
352
|
function handleConfirmCancel() {
|
|
345
353
|
setConfirmOpen(false);
|
|
354
|
+
onCancel?.();
|
|
346
355
|
pendingEvent.current = null;
|
|
347
356
|
}
|
|
348
357
|
const buttonContent = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
@@ -370,46 +379,49 @@ var Button = React.forwardRef(
|
|
|
370
379
|
tabIndex,
|
|
371
380
|
"data-testid": testID
|
|
372
381
|
};
|
|
373
|
-
const confirmModal = confirmBeforeClick && confirmOpen ?
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
382
|
+
const confirmModal = confirmBeforeClick && confirmOpen ? ReactDOM2.createPortal(
|
|
383
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
384
|
+
"div",
|
|
385
|
+
{
|
|
386
|
+
className: "fixed inset-0 z-[9999] flex items-center justify-center",
|
|
387
|
+
onClick: handleConfirmCancel,
|
|
388
|
+
children: [
|
|
389
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm" }),
|
|
390
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
391
|
+
"div",
|
|
392
|
+
{
|
|
393
|
+
className: "relative z-10 w-full max-w-sm rounded-2xl border border-border bg-card p-6 shadow-2xl",
|
|
394
|
+
onClick: (e) => e.stopPropagation(),
|
|
395
|
+
children: [
|
|
396
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-base font-semibold text-foreground mb-2", children: confirmBeforeClickModalTitle ?? "Are you sure?" }),
|
|
397
|
+
confirmBeforeClickModalContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-sm text-muted-foreground mb-5", children: confirmBeforeClickModalContent }),
|
|
398
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex justify-end gap-2 mt-4", children: confirmBeforeClickFooterAction ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
399
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
400
|
+
"button",
|
|
401
|
+
{
|
|
402
|
+
type: "button",
|
|
403
|
+
onClick: handleConfirmCancel,
|
|
404
|
+
className: "inline-flex items-center justify-center rounded-md border border-border bg-background px-4 py-2 text-sm font-medium text-foreground hover:bg-accent transition-colors",
|
|
405
|
+
children: "Cancel"
|
|
406
|
+
}
|
|
407
|
+
),
|
|
408
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
409
|
+
"button",
|
|
410
|
+
{
|
|
411
|
+
type: "button",
|
|
412
|
+
onClick: handleConfirmProceed,
|
|
413
|
+
className: "inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary-hover transition-colors",
|
|
414
|
+
children: "Proceed"
|
|
415
|
+
}
|
|
416
|
+
)
|
|
417
|
+
] }) })
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
]
|
|
422
|
+
}
|
|
423
|
+
),
|
|
424
|
+
document.body
|
|
413
425
|
) : null;
|
|
414
426
|
if (Element === "a") {
|
|
415
427
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
@@ -1559,66 +1571,6 @@ var React8 = __toESM(require("react"), 1);
|
|
|
1559
1571
|
var import_react_dom = require("react-dom");
|
|
1560
1572
|
var import_axios2 = __toESM(require("axios"), 1);
|
|
1561
1573
|
var import_lucide_react4 = require("lucide-react");
|
|
1562
|
-
|
|
1563
|
-
// src/components/tools/decryptPayload.ts
|
|
1564
|
-
var import_crypto_js = __toESM(require("crypto-js"), 1);
|
|
1565
|
-
function getLaravelSecretKey() {
|
|
1566
|
-
const viteKey = (() => {
|
|
1567
|
-
try {
|
|
1568
|
-
return new Function("return import.meta.env")();
|
|
1569
|
-
} catch {
|
|
1570
|
-
return void 0;
|
|
1571
|
-
}
|
|
1572
|
-
})()?.VITE_LARAVEL_KEY;
|
|
1573
|
-
const legacyKey = globalThis?.process?.env?.REACT_APP_LARAVEL_KEY;
|
|
1574
|
-
const windowKey = globalThis?.__LARAVEL_KEY__;
|
|
1575
|
-
const key = viteKey || legacyKey || windowKey;
|
|
1576
|
-
if (!key) {
|
|
1577
|
-
throw new Error("Missing Laravel decryption key. Set VITE_LARAVEL_KEY in your .env or inject window.__LARAVEL_KEY__ via Blade.");
|
|
1578
|
-
}
|
|
1579
|
-
return key;
|
|
1580
|
-
}
|
|
1581
|
-
function parseLaravelKey(secretKey) {
|
|
1582
|
-
if (secretKey.startsWith("base64:")) {
|
|
1583
|
-
return import_crypto_js.default.enc.Base64.parse(secretKey.slice("base64:".length));
|
|
1584
|
-
}
|
|
1585
|
-
return import_crypto_js.default.enc.Utf8.parse(secretKey);
|
|
1586
|
-
}
|
|
1587
|
-
function parseLaravelEncryptedPayload(payload) {
|
|
1588
|
-
let jsonStr = payload;
|
|
1589
|
-
try {
|
|
1590
|
-
jsonStr = atob(payload);
|
|
1591
|
-
} catch {
|
|
1592
|
-
}
|
|
1593
|
-
return JSON.parse(jsonStr);
|
|
1594
|
-
}
|
|
1595
|
-
function decryptLaravelPayload(payload, secretKey) {
|
|
1596
|
-
const resolvedKey = secretKey ?? getLaravelSecretKey();
|
|
1597
|
-
const parsed = parseLaravelEncryptedPayload(payload);
|
|
1598
|
-
if (parsed.tag) {
|
|
1599
|
-
throw new Error("Unsupported Laravel cipher (AEAD tag present). Expected AES-*-CBC payload.");
|
|
1600
|
-
}
|
|
1601
|
-
const key = parseLaravelKey(resolvedKey);
|
|
1602
|
-
const expectedMac = import_crypto_js.default.HmacSHA256(parsed.iv + parsed.value, key).toString(import_crypto_js.default.enc.Hex);
|
|
1603
|
-
if (expectedMac !== parsed.mac) {
|
|
1604
|
-
throw new Error("Invalid payload MAC (wrong key or tampered payload).");
|
|
1605
|
-
}
|
|
1606
|
-
const iv = import_crypto_js.default.enc.Base64.parse(parsed.iv);
|
|
1607
|
-
const ciphertext = import_crypto_js.default.enc.Base64.parse(parsed.value);
|
|
1608
|
-
const cipherParams = import_crypto_js.default.lib.CipherParams.create({ ciphertext });
|
|
1609
|
-
const decrypted = import_crypto_js.default.AES.decrypt(cipherParams, key, {
|
|
1610
|
-
iv,
|
|
1611
|
-
mode: import_crypto_js.default.mode.CBC,
|
|
1612
|
-
padding: import_crypto_js.default.pad.Pkcs7
|
|
1613
|
-
});
|
|
1614
|
-
const plaintext = decrypted.toString(import_crypto_js.default.enc.Utf8);
|
|
1615
|
-
if (!plaintext) {
|
|
1616
|
-
throw new Error("Decryption produced empty plaintext (wrong key/cipher).");
|
|
1617
|
-
}
|
|
1618
|
-
return JSON.parse(plaintext);
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
// src/components/ui/bulletin-board.tsx
|
|
1622
1574
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1623
1575
|
var PRIORITY_CONFIG = {
|
|
1624
1576
|
urgent: { label: "Urgent", icon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.AlertCircle, { className: "h-3 w-3" }), badge: "error" },
|
|
@@ -1648,82 +1600,13 @@ function AuthorAvatar({ item }) {
|
|
|
1648
1600
|
}
|
|
1649
1601
|
return null;
|
|
1650
1602
|
}
|
|
1651
|
-
function
|
|
1652
|
-
url,
|
|
1653
|
-
params,
|
|
1654
|
-
encrypt,
|
|
1655
|
-
key,
|
|
1656
|
-
decryptPayloadLog,
|
|
1657
|
-
transform
|
|
1658
|
-
}) {
|
|
1659
|
-
const [items, setItems] = React8.useState([]);
|
|
1660
|
-
const [currentPage, setCurrentPage] = React8.useState(1);
|
|
1661
|
-
const [pagination, setPagination] = React8.useState(null);
|
|
1662
|
-
const [loading, setLoading] = React8.useState(false);
|
|
1663
|
-
const [error, setError] = React8.useState(null);
|
|
1664
|
-
const [tick, setTick] = React8.useState(0);
|
|
1665
|
-
React8.useEffect(() => {
|
|
1666
|
-
let cancelled = false;
|
|
1667
|
-
setLoading(true);
|
|
1668
|
-
setError(null);
|
|
1669
|
-
import_axios2.default.get(url, { params: { ...params, page: currentPage } }).then(({ data: res }) => {
|
|
1670
|
-
if (cancelled) return;
|
|
1671
|
-
let payload;
|
|
1672
|
-
try {
|
|
1673
|
-
payload = encrypt ? decryptLaravelPayload(res, key) : res;
|
|
1674
|
-
} catch (decryptErr) {
|
|
1675
|
-
console.error("[useServerBulletin] decryption failed:", decryptErr?.message ?? decryptErr);
|
|
1676
|
-
if (!cancelled) setError(decryptErr?.message ?? "Decryption failed");
|
|
1677
|
-
return;
|
|
1678
|
-
}
|
|
1679
|
-
if (encrypt && decryptPayloadLog) console.log("[useServerBulletin] decrypted:", payload);
|
|
1680
|
-
const rows = Array.isArray(payload) ? payload : payload.data ?? [];
|
|
1681
|
-
setItems(transform ? rows.map(transform) : rows);
|
|
1682
|
-
if (!Array.isArray(payload)) {
|
|
1683
|
-
const rawTotal = payload.total;
|
|
1684
|
-
const rawPerPage = payload.per_page;
|
|
1685
|
-
const rawLastPage = payload.last_page;
|
|
1686
|
-
const lastPage = rawLastPage ?? Math.ceil(rawTotal / rawPerPage);
|
|
1687
|
-
const pg = payload.pagination ?? {
|
|
1688
|
-
first_page_url: payload.first_page_url ?? `${url}?page=1`,
|
|
1689
|
-
last_page_url: payload.last_page_url ?? `${url}?page=${lastPage}`,
|
|
1690
|
-
last_page: lastPage,
|
|
1691
|
-
next_page_url: payload.next_page_url !== void 0 ? payload.next_page_url : currentPage < lastPage ? `${url}?page=${currentPage + 1}` : null,
|
|
1692
|
-
prev_page_url: payload.prev_page_url !== void 0 ? payload.prev_page_url : currentPage > 1 ? `${url}?page=${currentPage - 1}` : null,
|
|
1693
|
-
per_page: rawPerPage,
|
|
1694
|
-
total: rawTotal,
|
|
1695
|
-
links: payload.links ?? []
|
|
1696
|
-
};
|
|
1697
|
-
setPagination(pg);
|
|
1698
|
-
}
|
|
1699
|
-
}).catch((err) => {
|
|
1700
|
-
if (cancelled) return;
|
|
1701
|
-
setError(err?.response?.data?.message ?? err.message ?? "Request failed");
|
|
1702
|
-
}).finally(() => {
|
|
1703
|
-
if (!cancelled) setLoading(false);
|
|
1704
|
-
});
|
|
1705
|
-
return () => {
|
|
1706
|
-
cancelled = true;
|
|
1707
|
-
};
|
|
1708
|
-
}, [url, currentPage, tick, JSON.stringify(params), encrypt, decryptPayloadLog]);
|
|
1709
|
-
return {
|
|
1710
|
-
items,
|
|
1711
|
-
currentPage,
|
|
1712
|
-
pagination,
|
|
1713
|
-
serverPagination: pagination ? { pagination, currentPage, goToPage: (p) => setCurrentPage(p) } : null,
|
|
1714
|
-
loading,
|
|
1715
|
-
error,
|
|
1716
|
-
goToPage: (p) => setCurrentPage(p),
|
|
1717
|
-
reload: () => setTick((t) => t + 1)
|
|
1718
|
-
};
|
|
1719
|
-
}
|
|
1720
|
-
function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions }) {
|
|
1603
|
+
function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions, headerAction, footerAction }) {
|
|
1721
1604
|
const priority = item.priority ? PRIORITY_CONFIG[item.priority] : null;
|
|
1722
1605
|
return (0, import_react_dom.createPortal)(
|
|
1723
1606
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1724
1607
|
"div",
|
|
1725
1608
|
{
|
|
1726
|
-
className: "fixed inset-0 z-50 flex items-center justify-center p-4",
|
|
1609
|
+
className: "fixed inset-0 z-50 flex glass items-center justify-center p-4",
|
|
1727
1610
|
style: { background: "rgba(0,0,0,0.55)" },
|
|
1728
1611
|
onMouseDown: (e) => {
|
|
1729
1612
|
if (e.target === e.currentTarget) onClose();
|
|
@@ -1736,7 +1619,8 @@ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customAction
|
|
|
1736
1619
|
" Pinned"
|
|
1737
1620
|
] }),
|
|
1738
1621
|
priority && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Badge, { variant: priority.badge, size: "sm", icon: priority.icon ?? void 0, children: priority.label }),
|
|
1739
|
-
item.category && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Badge, { variant: "outline", size: "sm", children: item.category })
|
|
1622
|
+
item.category && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Badge, { variant: "outline", size: "sm", children: item.category }),
|
|
1623
|
+
headerAction
|
|
1740
1624
|
] }),
|
|
1741
1625
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-1 shrink-0", children: [
|
|
1742
1626
|
onEdit && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
@@ -1835,6 +1719,7 @@ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customAction
|
|
|
1835
1719
|
] })
|
|
1836
1720
|
] }) }),
|
|
1837
1721
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1722
|
+
footerAction,
|
|
1838
1723
|
onEdit && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
1839
1724
|
"button",
|
|
1840
1725
|
{
|
|
@@ -2229,6 +2114,9 @@ function BulletinBoard({
|
|
|
2229
2114
|
deleteBaseUrl,
|
|
2230
2115
|
deleteIdKey = "id",
|
|
2231
2116
|
serverPagination,
|
|
2117
|
+
footerAction,
|
|
2118
|
+
headerPreviewAction,
|
|
2119
|
+
footerPreviewAction,
|
|
2232
2120
|
className
|
|
2233
2121
|
}) {
|
|
2234
2122
|
const [previewItem, setPreviewItem] = React8.useState(null);
|
|
@@ -2313,6 +2201,7 @@ function BulletinBoard({
|
|
|
2313
2201
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Pin, { className: "h-8 w-8 opacity-20" }),
|
|
2314
2202
|
emptyMessage
|
|
2315
2203
|
] }) : layout === "list" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-col gap-3", children: filtered.map((item) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BulletinCard, { item, variant, layout: "list", onClick: handleCardClick }, item.id)) }) : layout === "masonry" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: cn("gap-4", COLS_CLASS[columns]), style: { display: "grid", gridTemplateRows: "masonry" }, children: filtered.map((item) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BulletinCard, { item, variant, layout: "masonry", onClick: handleCardClick }, item.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: cn("grid gap-4", COLS_CLASS[columns]), children: filtered.map((item) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BulletinCard, { item, variant, layout: "grid", onClick: handleCardClick }, item.id)) }),
|
|
2204
|
+
footerAction && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { children: footerAction }),
|
|
2316
2205
|
serverPagination && (() => {
|
|
2317
2206
|
const { pagination, currentPage: cp, goToPage } = serverPagination;
|
|
2318
2207
|
const totalPages = pagination.last_page ?? Math.ceil(pagination.total / pagination.per_page);
|
|
@@ -2389,7 +2278,9 @@ function BulletinBoard({
|
|
|
2389
2278
|
setDeleteItem(item);
|
|
2390
2279
|
} : onDelete ? (item) => {
|
|
2391
2280
|
onDelete(item);
|
|
2392
|
-
} : void 0
|
|
2281
|
+
} : void 0,
|
|
2282
|
+
footerAction: footerPreviewAction ? footerPreviewAction(previewItem) : void 0,
|
|
2283
|
+
headerAction: headerPreviewAction
|
|
2393
2284
|
}
|
|
2394
2285
|
),
|
|
2395
2286
|
editItem && editBaseUrl && editFields && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
@@ -3863,6 +3754,66 @@ function MetricRow({ items, divided = true, className }) {
|
|
|
3863
3754
|
// src/components/ui/data-grid.tsx
|
|
3864
3755
|
var React29 = __toESM(require("react"), 1);
|
|
3865
3756
|
var import_react_dom3 = require("react-dom");
|
|
3757
|
+
|
|
3758
|
+
// src/components/tools/decryptPayload.ts
|
|
3759
|
+
var import_crypto_js = __toESM(require("crypto-js"), 1);
|
|
3760
|
+
function getLaravelSecretKey() {
|
|
3761
|
+
const viteKey = (() => {
|
|
3762
|
+
try {
|
|
3763
|
+
return new Function("return import.meta.env")();
|
|
3764
|
+
} catch {
|
|
3765
|
+
return void 0;
|
|
3766
|
+
}
|
|
3767
|
+
})()?.VITE_LARAVEL_KEY;
|
|
3768
|
+
const legacyKey = globalThis?.process?.env?.REACT_APP_LARAVEL_KEY;
|
|
3769
|
+
const windowKey = globalThis?.__LARAVEL_KEY__;
|
|
3770
|
+
const key = viteKey || legacyKey || windowKey;
|
|
3771
|
+
if (!key) {
|
|
3772
|
+
throw new Error("Missing Laravel decryption key. Set VITE_LARAVEL_KEY in your .env or inject window.__LARAVEL_KEY__ via Blade.");
|
|
3773
|
+
}
|
|
3774
|
+
return key;
|
|
3775
|
+
}
|
|
3776
|
+
function parseLaravelKey(secretKey) {
|
|
3777
|
+
if (secretKey.startsWith("base64:")) {
|
|
3778
|
+
return import_crypto_js.default.enc.Base64.parse(secretKey.slice("base64:".length));
|
|
3779
|
+
}
|
|
3780
|
+
return import_crypto_js.default.enc.Utf8.parse(secretKey);
|
|
3781
|
+
}
|
|
3782
|
+
function parseLaravelEncryptedPayload(payload) {
|
|
3783
|
+
let jsonStr = payload;
|
|
3784
|
+
try {
|
|
3785
|
+
jsonStr = atob(payload);
|
|
3786
|
+
} catch {
|
|
3787
|
+
}
|
|
3788
|
+
return JSON.parse(jsonStr);
|
|
3789
|
+
}
|
|
3790
|
+
function decryptLaravelPayload(payload, secretKey) {
|
|
3791
|
+
const resolvedKey = secretKey ?? getLaravelSecretKey();
|
|
3792
|
+
const parsed = parseLaravelEncryptedPayload(payload);
|
|
3793
|
+
if (parsed.tag) {
|
|
3794
|
+
throw new Error("Unsupported Laravel cipher (AEAD tag present). Expected AES-*-CBC payload.");
|
|
3795
|
+
}
|
|
3796
|
+
const key = parseLaravelKey(resolvedKey);
|
|
3797
|
+
const expectedMac = import_crypto_js.default.HmacSHA256(parsed.iv + parsed.value, key).toString(import_crypto_js.default.enc.Hex);
|
|
3798
|
+
if (expectedMac !== parsed.mac) {
|
|
3799
|
+
throw new Error("Invalid payload MAC (wrong key or tampered payload).");
|
|
3800
|
+
}
|
|
3801
|
+
const iv = import_crypto_js.default.enc.Base64.parse(parsed.iv);
|
|
3802
|
+
const ciphertext = import_crypto_js.default.enc.Base64.parse(parsed.value);
|
|
3803
|
+
const cipherParams = import_crypto_js.default.lib.CipherParams.create({ ciphertext });
|
|
3804
|
+
const decrypted = import_crypto_js.default.AES.decrypt(cipherParams, key, {
|
|
3805
|
+
iv,
|
|
3806
|
+
mode: import_crypto_js.default.mode.CBC,
|
|
3807
|
+
padding: import_crypto_js.default.pad.Pkcs7
|
|
3808
|
+
});
|
|
3809
|
+
const plaintext = decrypted.toString(import_crypto_js.default.enc.Utf8);
|
|
3810
|
+
if (!plaintext) {
|
|
3811
|
+
throw new Error("Decryption produced empty plaintext (wrong key/cipher).");
|
|
3812
|
+
}
|
|
3813
|
+
return JSON.parse(plaintext);
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
// src/components/ui/data-grid.tsx
|
|
3866
3817
|
var import_axios4 = __toESM(require("axios"), 1);
|
|
3867
3818
|
var import_lucide_react18 = require("lucide-react");
|
|
3868
3819
|
|
|
@@ -6806,7 +6757,7 @@ function ModalShell({ title, onClose, children, footer, width = "lg" }) {
|
|
|
6806
6757
|
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
6807
6758
|
"div",
|
|
6808
6759
|
{
|
|
6809
|
-
className: "fixed inset-0 z-50 flex items-center justify-center p-4",
|
|
6760
|
+
className: "fixed inset-0 z-50 flex glass items-center justify-center p-4",
|
|
6810
6761
|
style: { background: "rgba(0,0,0,0.5)" },
|
|
6811
6762
|
onMouseDown: (e) => {
|
|
6812
6763
|
if (e.target === e.currentTarget) onClose();
|
|
@@ -11163,7 +11114,7 @@ var import_lucide_react25 = require("lucide-react");
|
|
|
11163
11114
|
|
|
11164
11115
|
// src/components/ui/tooltip.tsx
|
|
11165
11116
|
var React38 = __toESM(require("react"), 1);
|
|
11166
|
-
var
|
|
11117
|
+
var ReactDOM3 = __toESM(require("react-dom"), 1);
|
|
11167
11118
|
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
11168
11119
|
function Tooltip({
|
|
11169
11120
|
content,
|
|
@@ -11218,7 +11169,7 @@ function Tooltip({
|
|
|
11218
11169
|
onBlur: () => setVisible(false),
|
|
11219
11170
|
children: [
|
|
11220
11171
|
children,
|
|
11221
|
-
visible &&
|
|
11172
|
+
visible && ReactDOM3.createPortal(
|
|
11222
11173
|
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
11223
11174
|
"div",
|
|
11224
11175
|
{
|
|
@@ -13817,6 +13768,12 @@ function createStore(initialValue, sessionName, expireInterval) {
|
|
|
13817
13768
|
};
|
|
13818
13769
|
return { store, getStore };
|
|
13819
13770
|
}
|
|
13771
|
+
|
|
13772
|
+
// src/core/decryption/decode.ts
|
|
13773
|
+
function decryptResponse(response, key) {
|
|
13774
|
+
const payload = typeof response === "string" ? response : response.data;
|
|
13775
|
+
return decryptLaravelPayload(payload, key);
|
|
13776
|
+
}
|
|
13820
13777
|
// Annotate the CommonJS export names for ESM import in node:
|
|
13821
13778
|
0 && (module.exports = {
|
|
13822
13779
|
Accordion,
|
|
@@ -13917,7 +13874,11 @@ function createStore(initialValue, sessionName, expireInterval) {
|
|
|
13917
13874
|
Wizard,
|
|
13918
13875
|
api,
|
|
13919
13876
|
createStore,
|
|
13920
|
-
|
|
13877
|
+
decryptLaravelPayload,
|
|
13878
|
+
decryptResponse,
|
|
13879
|
+
getLaravelSecretKey,
|
|
13880
|
+
parseLaravelEncryptedPayload,
|
|
13881
|
+
parseLaravelKey,
|
|
13921
13882
|
useServerDataGrid,
|
|
13922
13883
|
useServerTable,
|
|
13923
13884
|
useTheme,
|
package/dist/index.d.cts
CHANGED
|
@@ -3,6 +3,7 @@ import * as React from 'react';
|
|
|
3
3
|
import React__default from 'react';
|
|
4
4
|
import { AxiosRequestConfig } from 'axios';
|
|
5
5
|
import { UseBoundStore, StoreApi } from 'zustand';
|
|
6
|
+
import CryptoJS from 'crypto-js';
|
|
6
7
|
|
|
7
8
|
type AuthView = "login" | "register" | "resetPassword";
|
|
8
9
|
interface AuthField {
|
|
@@ -810,26 +811,6 @@ interface BulletinServerPaginationProp {
|
|
|
810
811
|
currentPage: number;
|
|
811
812
|
goToPage: (page: number) => void;
|
|
812
813
|
}
|
|
813
|
-
interface UseServerBulletinOptions {
|
|
814
|
-
url: string;
|
|
815
|
-
params?: Record<string, string | number>;
|
|
816
|
-
encrypt?: boolean;
|
|
817
|
-
key?: string;
|
|
818
|
-
decryptPayloadLog?: boolean;
|
|
819
|
-
/** Map raw API row → BulletinItem */
|
|
820
|
-
transform?: (row: any) => BulletinItem;
|
|
821
|
-
}
|
|
822
|
-
interface UseServerBulletinReturn {
|
|
823
|
-
items: BulletinItem[];
|
|
824
|
-
currentPage: number;
|
|
825
|
-
pagination: ServerPagination | null;
|
|
826
|
-
serverPagination: BulletinServerPaginationProp | null;
|
|
827
|
-
loading: boolean;
|
|
828
|
-
error: string | null;
|
|
829
|
-
goToPage: (page: number) => void;
|
|
830
|
-
reload: () => void;
|
|
831
|
-
}
|
|
832
|
-
declare function useServerBulletin({ url, params, encrypt, key, decryptPayloadLog, transform, }: UseServerBulletinOptions): UseServerBulletinReturn;
|
|
833
814
|
interface BulletinPreviewProps {
|
|
834
815
|
item: BulletinItem;
|
|
835
816
|
onClose: () => void;
|
|
@@ -838,8 +819,12 @@ interface BulletinPreviewProps {
|
|
|
838
819
|
onView?: (item: BulletinItem) => void;
|
|
839
820
|
/** Custom actions to add to the preview header */
|
|
840
821
|
customActions?: BulletinAction[];
|
|
822
|
+
/** Extra React elements rendered in the preview modal header's left area */
|
|
823
|
+
headerAction?: React.ReactNode;
|
|
824
|
+
/** Extra React elements rendered in the preview modal footer's action area */
|
|
825
|
+
footerAction?: React.ReactNode;
|
|
841
826
|
}
|
|
842
|
-
declare function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions }: BulletinPreviewProps): React.ReactPortal;
|
|
827
|
+
declare function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions, headerAction, footerAction }: BulletinPreviewProps): React.ReactPortal;
|
|
843
828
|
interface BulletinEditField {
|
|
844
829
|
key: keyof BulletinItem | string;
|
|
845
830
|
label: string;
|
|
@@ -898,10 +883,16 @@ interface BulletinBoardProps {
|
|
|
898
883
|
serverPagination?: BulletinServerPaginationProp | null;
|
|
899
884
|
/** Fired when a post card is clicked (ignored when preview=true). */
|
|
900
885
|
onItemClick?: (item: BulletinItem) => void;
|
|
886
|
+
/** Extra React elements rendered below the board content (above pagination). */
|
|
887
|
+
footerAction?: React.ReactNode;
|
|
888
|
+
/** Extra React elements rendered in the preview modal header's left area. */
|
|
889
|
+
headerPreviewAction?: React.ReactNode;
|
|
890
|
+
/** Extra React elements rendered in the preview modal footer's action area. */
|
|
891
|
+
footerPreviewAction?: (item: BulletinItem) => React.ReactNode;
|
|
901
892
|
/** Additional CSS classes on the outer wrapper. */
|
|
902
893
|
className?: string;
|
|
903
894
|
}
|
|
904
|
-
declare function BulletinBoard({ items, layout, columns, variant, searchable, filterable, categories: categoriesProp, title, headerAction, showHeader, emptyMessage, loading, loadingCount, onItemClick, onView, onEdit, onDelete, preview, editBaseUrl, editMethod, editIdKey, editFields, deleteBaseUrl, deleteIdKey, serverPagination, className, }: BulletinBoardProps): react_jsx_runtime.JSX.Element;
|
|
895
|
+
declare function BulletinBoard({ items, layout, columns, variant, searchable, filterable, categories: categoriesProp, title, headerAction, showHeader, emptyMessage, loading, loadingCount, onItemClick, onView, onEdit, onDelete, preview, editBaseUrl, editMethod, editIdKey, editFields, deleteBaseUrl, deleteIdKey, serverPagination, footerAction, headerPreviewAction, footerPreviewAction, className, }: BulletinBoardProps): react_jsx_runtime.JSX.Element;
|
|
905
896
|
|
|
906
897
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
907
898
|
variant?: "primary" | "secondary" | "outline" | "ghost" | "link" | "danger" | "success" | "destructive";
|
|
@@ -978,6 +969,8 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
978
969
|
confirmBeforeClickModalTitle?: string;
|
|
979
970
|
confirmBeforeClickModalContent?: React.ReactNode;
|
|
980
971
|
confirmBeforeClickFooterAction?: React.ReactNode;
|
|
972
|
+
onConfirm?: () => void;
|
|
973
|
+
onCancel?: () => void;
|
|
981
974
|
href?: string;
|
|
982
975
|
target?: "_blank" | "_self";
|
|
983
976
|
as?: "button" | "a" | "div";
|
|
@@ -2561,4 +2554,29 @@ type StoreWrapper<T extends object> = {
|
|
|
2561
2554
|
*/
|
|
2562
2555
|
declare function createStore<T extends object>(initialValue: T, sessionName: string, expireInterval?: number): StoreWrapper<T>;
|
|
2563
2556
|
|
|
2564
|
-
|
|
2557
|
+
type LaravelEncryptedPayload = {
|
|
2558
|
+
iv: string;
|
|
2559
|
+
value: string;
|
|
2560
|
+
mac: string;
|
|
2561
|
+
tag?: string;
|
|
2562
|
+
};
|
|
2563
|
+
declare function getLaravelSecretKey(): string;
|
|
2564
|
+
declare function parseLaravelKey(secretKey: string): CryptoJS.lib.WordArray;
|
|
2565
|
+
declare function parseLaravelEncryptedPayload(payload: string): LaravelEncryptedPayload;
|
|
2566
|
+
declare function decryptLaravelPayload<T>(payload: string, secretKey?: string): T;
|
|
2567
|
+
|
|
2568
|
+
/**
|
|
2569
|
+
* Decrypt a Laravel-encrypted API response.
|
|
2570
|
+
*
|
|
2571
|
+
* Accepts either:
|
|
2572
|
+
* - a raw encrypted string (the full response body)
|
|
2573
|
+
* - an object with an `data` key holding the encrypted string
|
|
2574
|
+
*
|
|
2575
|
+
* @param response Raw encrypted string or `{ data: string }` object
|
|
2576
|
+
* @param key Optional Laravel APP_KEY (base64:… or raw). Falls back to VITE_LARAVEL_KEY.
|
|
2577
|
+
*/
|
|
2578
|
+
declare function decryptResponse<T = unknown>(response: string | {
|
|
2579
|
+
data: string;
|
|
2580
|
+
}, key?: string): T;
|
|
2581
|
+
|
|
2582
|
+
export { Accordion, type AccordionItem, type AccordionProps, type AccordionVariant, type ActionField, type ActionFieldType, type AuthField, type AuthVariant, type AuthView, Authentication, type AuthenticationProps, AvatarStack, type AvatarStackProps, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, type BulletinAction, BulletinBoard, type BulletinBoardProps, type BulletinColumns, type BulletinEditField, type BulletinItem, type BulletinLayout, BulletinPreview, type BulletinPreviewProps, type BulletinPriority, type BulletinServerPaginationProp, type BulletinVariant, Button, type ButtonProps, COLOR_PALETTE, Calendar, CalendarDateRangePicker, type CalendarDateRangePickerProps, type CalendarDateRangeVariant, type CalendarEvent, type CalendarProps, type CalendarView, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type ChartDataPoint, ChartWidget, type ChartWidgetProps, Checkbox, type CheckboxProps, CircularProgress, type CircularProgressProps, type ClusterVariant, CodegoApiProvider, ColorPicker, type ColorPickerProps, type Column, Combobox, type ComboboxOption, type ComboboxProps, type CommandItem, CommandPalette, type CommandPaletteProps, ComposableWidget, type ComposableWidgetProps, type ConfirmVariant, ContextMenu, type ContextMenuItem, type ContextMenuProps, DataGrid, type DataGridColumn, type DataGridProps, DatePickerPopup, type DateRange, DateRangePicker, type DateRangePickerProps, type DefaultActionsConfig, DocsLayout, Drawer, type DrawerProps, type DrawerSide, Dropdown, DropdownItem, DropdownLabel, type DropdownProps, DropdownSeparator, EVENT_COLORS, type FileTypeValidation, FileUpload, type FileUploadProps, type FlexAlign, type FlexDirection, type FlexGap, FlexItem, type FlexItemProps, type FlexJustify, FlexLayout, type FlexLayoutProps, type FlexWrap, type FlyToOptions, type FormField, type FormFieldType, type GridAlign, type GridCols, type GridGap, GridItem, type GridItemProps, GridLayout, type GridLayoutProps, GroupNavigation, type GroupNavigationProps, type ImageEditorMode, type ImageEditorOptions, Input, type InputProps, KanbanBoard, type KanbanBoardProps, type KanbanCard, type KanbanColumn, Label, type LaravelEncryptedPayload, LeafletMap, type LeafletMapProps, LeftSidebar, type LeftSidebarProps, type MapLibreClusterVariant, MapLibreMap, type MapLibreMarker, type MapLibreProps, type MapLibreRoute, type MapLibreRouteType, type MapLibreStyle, type MapMarker, type MapRoute, type MarkerColor, type MetricItem, MetricRow, type MetricRowProps, Modal, ModalConfirmation, type ModalConfirmationProps, type ModalProps, ModalUnchange, type ModalUnchangeProps, ModalWithForms, type ModalWithFormsProps, type NavGroup, type NavItem, Navigation, type NavigationProps, NotificationBanner, type NotificationBannerProps, type NotificationItem, NotificationPanel, type NotificationPanelProps, type NotificationVariant, OtpInput, type OtpInputProps, Pagination, type PaginationProps, Panel, type PanelProps, PanelSettings, type PanelSettingsProps, type PanelSettingsTab, PanelSidebarGroup, PanelSidebarItem, Popover, type PopoverPlacement, type PopoverProps, Progress, type ProgressProps, type ProgressSize, type ProgressVariant, type PropRow, PropsTable, RadioGroup, type RadioGroupProps, type RadioOption, type RadioSize, type RadioVariant, RangeSlider, type RangeSliderProps, Repeater, type RepeaterProps, type RequestConfig, ResizablePanels, type ResizablePanelsProps, RichTextEditor, type RichTextEditorProps, RightSidebar, type RightSidebarProps, type RouteType, ScrollArea, type ScrollAreaProps, Section, SectionBlock, type SectionProps, type SectionVariant, Select, type SelectOption, type SelectProps, type SemanticColor, type ServerDataGridProp, type ServerPagination, type ServerPaginationLink, type ServerPaginationProp, type ServerTableResponse, Skeleton, Slider, type SliderProps, type SortDir, StatCard, type StatCardProps, type StatTrend, StatsWidget, type StatsWidgetProps, type Step, type StepStatus, Stepper, type StepperProps, type TabItem, type TabSize, type TabVariant, Table, TableOfContents, type TableProps, TableWidget, type TableWidgetProps, Tabs, type TabsProps, TagInput, type TagInputProps, Textarea, type TextareaProps, type ThemeColors, ThemeProvider, type ThemeSettings, Timeline, type TimelineItem, type TimelineProps, type TimelineVariant, type ToastItem, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, type TocItem, TocProvider, ToggleSwitch, type ToggleSwitchProps, Tooltip, type TooltipProps, Topbar, type TopbarProps, type TreeNode, TreeView, type TreeViewProps, type TrendDir, type UseServerDataGridOptions, type UseServerDataGridReturn, type UseServerTableOptions, type UseServerTableReturn, Widget, type WidgetProps, Wizard, type WizardActionProps, type WizardLayout, type WizardProps, type WizardSize, type WizardStep, type WizardVariant, api, createStore, decryptLaravelPayload, decryptResponse, getLaravelSecretKey, parseLaravelEncryptedPayload, parseLaravelKey, useServerDataGrid, useServerTable, useTheme, useToast, useToc };
|