@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 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
- useServerBulletin: () => useServerBulletin,
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 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
374
- "div",
375
- {
376
- className: "fixed inset-0 z-50 flex items-center justify-center",
377
- onClick: handleConfirmCancel,
378
- children: [
379
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm" }),
380
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
381
- "div",
382
- {
383
- className: "relative z-10 w-full max-w-sm rounded-2xl border border-border bg-card p-6 shadow-2xl",
384
- onClick: (e) => e.stopPropagation(),
385
- children: [
386
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-base font-semibold text-foreground mb-2", children: confirmBeforeClickModalTitle ?? "Are you sure?" }),
387
- confirmBeforeClickModalContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-sm text-muted-foreground mb-5", children: confirmBeforeClickModalContent }),
388
- /* @__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: [
389
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
390
- "button",
391
- {
392
- type: "button",
393
- onClick: handleConfirmCancel,
394
- 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",
395
- children: "Cancel"
396
- }
397
- ),
398
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
399
- "button",
400
- {
401
- type: "button",
402
- onClick: handleConfirmProceed,
403
- 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",
404
- children: "Proceed"
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 useServerBulletin({
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 ReactDOM2 = __toESM(require("react-dom"), 1);
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 && ReactDOM2.createPortal(
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
- useServerBulletin,
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
- 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, 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 UseServerBulletinOptions, type UseServerBulletinReturn, 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, useServerBulletin, useServerDataGrid, useServerTable, useTheme, useToast, useToc };
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 };