@juv/codego-react-ui 3.5.21 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,
@@ -1559,66 +1563,6 @@ var React8 = __toESM(require("react"), 1);
1559
1563
  var import_react_dom = require("react-dom");
1560
1564
  var import_axios2 = __toESM(require("axios"), 1);
1561
1565
  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
1566
  var import_jsx_runtime10 = require("react/jsx-runtime");
1623
1567
  var PRIORITY_CONFIG = {
1624
1568
  urgent: { label: "Urgent", icon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.AlertCircle, { className: "h-3 w-3" }), badge: "error" },
@@ -1648,82 +1592,13 @@ function AuthorAvatar({ item }) {
1648
1592
  }
1649
1593
  return null;
1650
1594
  }
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 }) {
1595
+ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions, headerAction, footerAction }) {
1721
1596
  const priority = item.priority ? PRIORITY_CONFIG[item.priority] : null;
1722
1597
  return (0, import_react_dom.createPortal)(
1723
1598
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1724
1599
  "div",
1725
1600
  {
1726
- className: "fixed inset-0 z-50 flex items-center justify-center p-4",
1601
+ className: "fixed inset-0 z-50 flex glass items-center justify-center p-4",
1727
1602
  style: { background: "rgba(0,0,0,0.55)" },
1728
1603
  onMouseDown: (e) => {
1729
1604
  if (e.target === e.currentTarget) onClose();
@@ -1736,7 +1611,8 @@ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customAction
1736
1611
  " Pinned"
1737
1612
  ] }),
1738
1613
  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 })
1614
+ item.category && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Badge, { variant: "outline", size: "sm", children: item.category }),
1615
+ headerAction
1740
1616
  ] }),
1741
1617
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-1 shrink-0", children: [
1742
1618
  onEdit && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -1835,6 +1711,7 @@ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customAction
1835
1711
  ] })
1836
1712
  ] }) }),
1837
1713
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
1714
+ footerAction,
1838
1715
  onEdit && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1839
1716
  "button",
1840
1717
  {
@@ -2229,6 +2106,9 @@ function BulletinBoard({
2229
2106
  deleteBaseUrl,
2230
2107
  deleteIdKey = "id",
2231
2108
  serverPagination,
2109
+ footerAction,
2110
+ headerPreviewAction,
2111
+ footerPreviewAction,
2232
2112
  className
2233
2113
  }) {
2234
2114
  const [previewItem, setPreviewItem] = React8.useState(null);
@@ -2313,6 +2193,7 @@ function BulletinBoard({
2313
2193
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Pin, { className: "h-8 w-8 opacity-20" }),
2314
2194
  emptyMessage
2315
2195
  ] }) : 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)) }),
2196
+ footerAction && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { children: footerAction }),
2316
2197
  serverPagination && (() => {
2317
2198
  const { pagination, currentPage: cp, goToPage } = serverPagination;
2318
2199
  const totalPages = pagination.last_page ?? Math.ceil(pagination.total / pagination.per_page);
@@ -2389,7 +2270,9 @@ function BulletinBoard({
2389
2270
  setDeleteItem(item);
2390
2271
  } : onDelete ? (item) => {
2391
2272
  onDelete(item);
2392
- } : void 0
2273
+ } : void 0,
2274
+ footerAction: footerPreviewAction ? footerPreviewAction(previewItem) : void 0,
2275
+ headerAction: headerPreviewAction
2393
2276
  }
2394
2277
  ),
2395
2278
  editItem && editBaseUrl && editFields && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -3863,6 +3746,66 @@ function MetricRow({ items, divided = true, className }) {
3863
3746
  // src/components/ui/data-grid.tsx
3864
3747
  var React29 = __toESM(require("react"), 1);
3865
3748
  var import_react_dom3 = require("react-dom");
3749
+
3750
+ // src/components/tools/decryptPayload.ts
3751
+ var import_crypto_js = __toESM(require("crypto-js"), 1);
3752
+ function getLaravelSecretKey() {
3753
+ const viteKey = (() => {
3754
+ try {
3755
+ return new Function("return import.meta.env")();
3756
+ } catch {
3757
+ return void 0;
3758
+ }
3759
+ })()?.VITE_LARAVEL_KEY;
3760
+ const legacyKey = globalThis?.process?.env?.REACT_APP_LARAVEL_KEY;
3761
+ const windowKey = globalThis?.__LARAVEL_KEY__;
3762
+ const key = viteKey || legacyKey || windowKey;
3763
+ if (!key) {
3764
+ throw new Error("Missing Laravel decryption key. Set VITE_LARAVEL_KEY in your .env or inject window.__LARAVEL_KEY__ via Blade.");
3765
+ }
3766
+ return key;
3767
+ }
3768
+ function parseLaravelKey(secretKey) {
3769
+ if (secretKey.startsWith("base64:")) {
3770
+ return import_crypto_js.default.enc.Base64.parse(secretKey.slice("base64:".length));
3771
+ }
3772
+ return import_crypto_js.default.enc.Utf8.parse(secretKey);
3773
+ }
3774
+ function parseLaravelEncryptedPayload(payload) {
3775
+ let jsonStr = payload;
3776
+ try {
3777
+ jsonStr = atob(payload);
3778
+ } catch {
3779
+ }
3780
+ return JSON.parse(jsonStr);
3781
+ }
3782
+ function decryptLaravelPayload(payload, secretKey) {
3783
+ const resolvedKey = secretKey ?? getLaravelSecretKey();
3784
+ const parsed = parseLaravelEncryptedPayload(payload);
3785
+ if (parsed.tag) {
3786
+ throw new Error("Unsupported Laravel cipher (AEAD tag present). Expected AES-*-CBC payload.");
3787
+ }
3788
+ const key = parseLaravelKey(resolvedKey);
3789
+ const expectedMac = import_crypto_js.default.HmacSHA256(parsed.iv + parsed.value, key).toString(import_crypto_js.default.enc.Hex);
3790
+ if (expectedMac !== parsed.mac) {
3791
+ throw new Error("Invalid payload MAC (wrong key or tampered payload).");
3792
+ }
3793
+ const iv = import_crypto_js.default.enc.Base64.parse(parsed.iv);
3794
+ const ciphertext = import_crypto_js.default.enc.Base64.parse(parsed.value);
3795
+ const cipherParams = import_crypto_js.default.lib.CipherParams.create({ ciphertext });
3796
+ const decrypted = import_crypto_js.default.AES.decrypt(cipherParams, key, {
3797
+ iv,
3798
+ mode: import_crypto_js.default.mode.CBC,
3799
+ padding: import_crypto_js.default.pad.Pkcs7
3800
+ });
3801
+ const plaintext = decrypted.toString(import_crypto_js.default.enc.Utf8);
3802
+ if (!plaintext) {
3803
+ throw new Error("Decryption produced empty plaintext (wrong key/cipher).");
3804
+ }
3805
+ return JSON.parse(plaintext);
3806
+ }
3807
+
3808
+ // src/components/ui/data-grid.tsx
3866
3809
  var import_axios4 = __toESM(require("axios"), 1);
3867
3810
  var import_lucide_react18 = require("lucide-react");
3868
3811
 
@@ -6806,7 +6749,7 @@ function ModalShell({ title, onClose, children, footer, width = "lg" }) {
6806
6749
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6807
6750
  "div",
6808
6751
  {
6809
- className: "fixed inset-0 z-50 flex items-center justify-center p-4",
6752
+ className: "fixed inset-0 z-50 flex glass items-center justify-center p-4",
6810
6753
  style: { background: "rgba(0,0,0,0.5)" },
6811
6754
  onMouseDown: (e) => {
6812
6755
  if (e.target === e.currentTarget) onClose();
@@ -13817,6 +13760,12 @@ function createStore(initialValue, sessionName, expireInterval) {
13817
13760
  };
13818
13761
  return { store, getStore };
13819
13762
  }
13763
+
13764
+ // src/core/decryption/decode.ts
13765
+ function decryptResponse(response, key) {
13766
+ const payload = typeof response === "string" ? response : response.data;
13767
+ return decryptLaravelPayload(payload, key);
13768
+ }
13820
13769
  // Annotate the CommonJS export names for ESM import in node:
13821
13770
  0 && (module.exports = {
13822
13771
  Accordion,
@@ -13917,7 +13866,11 @@ function createStore(initialValue, sessionName, expireInterval) {
13917
13866
  Wizard,
13918
13867
  api,
13919
13868
  createStore,
13920
- useServerBulletin,
13869
+ decryptLaravelPayload,
13870
+ decryptResponse,
13871
+ getLaravelSecretKey,
13872
+ parseLaravelEncryptedPayload,
13873
+ parseLaravelKey,
13921
13874
  useServerDataGrid,
13922
13875
  useServerTable,
13923
13876
  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";
@@ -2561,4 +2552,29 @@ type StoreWrapper<T extends object> = {
2561
2552
  */
2562
2553
  declare function createStore<T extends object>(initialValue: T, sessionName: string, expireInterval?: number): StoreWrapper<T>;
2563
2554
 
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 };
2555
+ type LaravelEncryptedPayload = {
2556
+ iv: string;
2557
+ value: string;
2558
+ mac: string;
2559
+ tag?: string;
2560
+ };
2561
+ declare function getLaravelSecretKey(): string;
2562
+ declare function parseLaravelKey(secretKey: string): CryptoJS.lib.WordArray;
2563
+ declare function parseLaravelEncryptedPayload(payload: string): LaravelEncryptedPayload;
2564
+ declare function decryptLaravelPayload<T>(payload: string, secretKey?: string): T;
2565
+
2566
+ /**
2567
+ * Decrypt a Laravel-encrypted API response.
2568
+ *
2569
+ * Accepts either:
2570
+ * - a raw encrypted string (the full response body)
2571
+ * - an object with an `data` key holding the encrypted string
2572
+ *
2573
+ * @param response Raw encrypted string or `{ data: string }` object
2574
+ * @param key Optional Laravel APP_KEY (base64:… or raw). Falls back to VITE_LARAVEL_KEY.
2575
+ */
2576
+ declare function decryptResponse<T = unknown>(response: string | {
2577
+ data: string;
2578
+ }, key?: string): T;
2579
+
2580
+ 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 };
package/dist/index.d.ts 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";
@@ -2561,4 +2552,29 @@ type StoreWrapper<T extends object> = {
2561
2552
  */
2562
2553
  declare function createStore<T extends object>(initialValue: T, sessionName: string, expireInterval?: number): StoreWrapper<T>;
2563
2554
 
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 };
2555
+ type LaravelEncryptedPayload = {
2556
+ iv: string;
2557
+ value: string;
2558
+ mac: string;
2559
+ tag?: string;
2560
+ };
2561
+ declare function getLaravelSecretKey(): string;
2562
+ declare function parseLaravelKey(secretKey: string): CryptoJS.lib.WordArray;
2563
+ declare function parseLaravelEncryptedPayload(payload: string): LaravelEncryptedPayload;
2564
+ declare function decryptLaravelPayload<T>(payload: string, secretKey?: string): T;
2565
+
2566
+ /**
2567
+ * Decrypt a Laravel-encrypted API response.
2568
+ *
2569
+ * Accepts either:
2570
+ * - a raw encrypted string (the full response body)
2571
+ * - an object with an `data` key holding the encrypted string
2572
+ *
2573
+ * @param response Raw encrypted string or `{ data: string }` object
2574
+ * @param key Optional Laravel APP_KEY (base64:… or raw). Falls back to VITE_LARAVEL_KEY.
2575
+ */
2576
+ declare function decryptResponse<T = unknown>(response: string | {
2577
+ data: string;
2578
+ }, key?: string): T;
2579
+
2580
+ 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 };
@@ -53730,7 +53730,11 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
53730
53730
  Wizard: () => Wizard,
53731
53731
  api: () => api,
53732
53732
  createStore: () => createStore2,
53733
- useServerBulletin: () => useServerBulletin,
53733
+ decryptLaravelPayload: () => decryptLaravelPayload,
53734
+ decryptResponse: () => decryptResponse,
53735
+ getLaravelSecretKey: () => getLaravelSecretKey,
53736
+ parseLaravelEncryptedPayload: () => parseLaravelEncryptedPayload,
53737
+ parseLaravelKey: () => parseLaravelKey,
53734
53738
  useServerDataGrid: () => useServerDataGrid,
53735
53739
  useServerTable: () => useServerTable,
53736
53740
  useTheme: () => useTheme,
@@ -64733,66 +64737,6 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
64733
64737
  // src/components/ui/bulletin-board.tsx
64734
64738
  var React8 = __toESM(require_react(), 1);
64735
64739
  var import_react_dom = __toESM(require_react_dom(), 1);
64736
-
64737
- // src/components/tools/decryptPayload.ts
64738
- var import_crypto_js = __toESM(require_crypto_js(), 1);
64739
- function getLaravelSecretKey() {
64740
- const viteKey = (() => {
64741
- try {
64742
- return new Function("return import.meta.env")();
64743
- } catch {
64744
- return void 0;
64745
- }
64746
- })()?.VITE_LARAVEL_KEY;
64747
- const legacyKey = globalThis?.process?.env?.REACT_APP_LARAVEL_KEY;
64748
- const windowKey = globalThis?.__LARAVEL_KEY__;
64749
- const key = viteKey || legacyKey || windowKey;
64750
- if (!key) {
64751
- throw new Error("Missing Laravel decryption key. Set VITE_LARAVEL_KEY in your .env or inject window.__LARAVEL_KEY__ via Blade.");
64752
- }
64753
- return key;
64754
- }
64755
- function parseLaravelKey(secretKey) {
64756
- if (secretKey.startsWith("base64:")) {
64757
- return import_crypto_js.default.enc.Base64.parse(secretKey.slice("base64:".length));
64758
- }
64759
- return import_crypto_js.default.enc.Utf8.parse(secretKey);
64760
- }
64761
- function parseLaravelEncryptedPayload(payload) {
64762
- let jsonStr = payload;
64763
- try {
64764
- jsonStr = atob(payload);
64765
- } catch {
64766
- }
64767
- return JSON.parse(jsonStr);
64768
- }
64769
- function decryptLaravelPayload(payload, secretKey) {
64770
- const resolvedKey = secretKey ?? getLaravelSecretKey();
64771
- const parsed = parseLaravelEncryptedPayload(payload);
64772
- if (parsed.tag) {
64773
- throw new Error("Unsupported Laravel cipher (AEAD tag present). Expected AES-*-CBC payload.");
64774
- }
64775
- const key = parseLaravelKey(resolvedKey);
64776
- const expectedMac = import_crypto_js.default.HmacSHA256(parsed.iv + parsed.value, key).toString(import_crypto_js.default.enc.Hex);
64777
- if (expectedMac !== parsed.mac) {
64778
- throw new Error("Invalid payload MAC (wrong key or tampered payload).");
64779
- }
64780
- const iv = import_crypto_js.default.enc.Base64.parse(parsed.iv);
64781
- const ciphertext = import_crypto_js.default.enc.Base64.parse(parsed.value);
64782
- const cipherParams = import_crypto_js.default.lib.CipherParams.create({ ciphertext });
64783
- const decrypted = import_crypto_js.default.AES.decrypt(cipherParams, key, {
64784
- iv,
64785
- mode: import_crypto_js.default.mode.CBC,
64786
- padding: import_crypto_js.default.pad.Pkcs7
64787
- });
64788
- const plaintext = decrypted.toString(import_crypto_js.default.enc.Utf8);
64789
- if (!plaintext) {
64790
- throw new Error("Decryption produced empty plaintext (wrong key/cipher).");
64791
- }
64792
- return JSON.parse(plaintext);
64793
- }
64794
-
64795
- // src/components/ui/bulletin-board.tsx
64796
64740
  var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
64797
64741
  var PRIORITY_CONFIG = {
64798
64742
  urgent: { label: "Urgent", icon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CircleAlert, { className: "h-3 w-3" }), badge: "error" },
@@ -64822,82 +64766,13 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
64822
64766
  }
64823
64767
  return null;
64824
64768
  }
64825
- function useServerBulletin({
64826
- url: url2,
64827
- params,
64828
- encrypt,
64829
- key,
64830
- decryptPayloadLog,
64831
- transform
64832
- }) {
64833
- const [items, setItems] = React8.useState([]);
64834
- const [currentPage, setCurrentPage] = React8.useState(1);
64835
- const [pagination, setPagination] = React8.useState(null);
64836
- const [loading, setLoading] = React8.useState(false);
64837
- const [error, setError] = React8.useState(null);
64838
- const [tick, setTick] = React8.useState(0);
64839
- React8.useEffect(() => {
64840
- let cancelled = false;
64841
- setLoading(true);
64842
- setError(null);
64843
- axios_default.get(url2, { params: { ...params, page: currentPage } }).then(({ data: res }) => {
64844
- if (cancelled) return;
64845
- let payload;
64846
- try {
64847
- payload = encrypt ? decryptLaravelPayload(res, key) : res;
64848
- } catch (decryptErr) {
64849
- console.error("[useServerBulletin] decryption failed:", decryptErr?.message ?? decryptErr);
64850
- if (!cancelled) setError(decryptErr?.message ?? "Decryption failed");
64851
- return;
64852
- }
64853
- if (encrypt && decryptPayloadLog) console.log("[useServerBulletin] decrypted:", payload);
64854
- const rows = Array.isArray(payload) ? payload : payload.data ?? [];
64855
- setItems(transform ? rows.map(transform) : rows);
64856
- if (!Array.isArray(payload)) {
64857
- const rawTotal = payload.total;
64858
- const rawPerPage = payload.per_page;
64859
- const rawLastPage = payload.last_page;
64860
- const lastPage = rawLastPage ?? Math.ceil(rawTotal / rawPerPage);
64861
- const pg = payload.pagination ?? {
64862
- first_page_url: payload.first_page_url ?? `${url2}?page=1`,
64863
- last_page_url: payload.last_page_url ?? `${url2}?page=${lastPage}`,
64864
- last_page: lastPage,
64865
- next_page_url: payload.next_page_url !== void 0 ? payload.next_page_url : currentPage < lastPage ? `${url2}?page=${currentPage + 1}` : null,
64866
- prev_page_url: payload.prev_page_url !== void 0 ? payload.prev_page_url : currentPage > 1 ? `${url2}?page=${currentPage - 1}` : null,
64867
- per_page: rawPerPage,
64868
- total: rawTotal,
64869
- links: payload.links ?? []
64870
- };
64871
- setPagination(pg);
64872
- }
64873
- }).catch((err) => {
64874
- if (cancelled) return;
64875
- setError(err?.response?.data?.message ?? err.message ?? "Request failed");
64876
- }).finally(() => {
64877
- if (!cancelled) setLoading(false);
64878
- });
64879
- return () => {
64880
- cancelled = true;
64881
- };
64882
- }, [url2, currentPage, tick, JSON.stringify(params), encrypt, decryptPayloadLog]);
64883
- return {
64884
- items,
64885
- currentPage,
64886
- pagination,
64887
- serverPagination: pagination ? { pagination, currentPage, goToPage: (p) => setCurrentPage(p) } : null,
64888
- loading,
64889
- error,
64890
- goToPage: (p) => setCurrentPage(p),
64891
- reload: () => setTick((t) => t + 1)
64892
- };
64893
- }
64894
- function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions }) {
64769
+ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions, headerAction, footerAction }) {
64895
64770
  const priority = item.priority ? PRIORITY_CONFIG[item.priority] : null;
64896
64771
  return (0, import_react_dom.createPortal)(
64897
64772
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
64898
64773
  "div",
64899
64774
  {
64900
- className: "fixed inset-0 z-50 flex items-center justify-center p-4",
64775
+ className: "fixed inset-0 z-50 flex glass items-center justify-center p-4",
64901
64776
  style: { background: "rgba(0,0,0,0.55)" },
64902
64777
  onMouseDown: (e) => {
64903
64778
  if (e.target === e.currentTarget) onClose();
@@ -64910,7 +64785,8 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
64910
64785
  " Pinned"
64911
64786
  ] }),
64912
64787
  priority && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Badge, { variant: priority.badge, size: "sm", icon: priority.icon ?? void 0, children: priority.label }),
64913
- item.category && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Badge, { variant: "outline", size: "sm", children: item.category })
64788
+ item.category && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Badge, { variant: "outline", size: "sm", children: item.category }),
64789
+ headerAction
64914
64790
  ] }),
64915
64791
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-1 shrink-0", children: [
64916
64792
  onEdit && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -65009,6 +64885,7 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
65009
64885
  ] })
65010
64886
  ] }) }),
65011
64887
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
64888
+ footerAction,
65012
64889
  onEdit && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
65013
64890
  "button",
65014
64891
  {
@@ -65403,6 +65280,9 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
65403
65280
  deleteBaseUrl,
65404
65281
  deleteIdKey = "id",
65405
65282
  serverPagination,
65283
+ footerAction,
65284
+ headerPreviewAction,
65285
+ footerPreviewAction,
65406
65286
  className
65407
65287
  }) {
65408
65288
  const [previewItem, setPreviewItem] = React8.useState(null);
@@ -65487,6 +65367,7 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
65487
65367
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pin, { className: "h-8 w-8 opacity-20" }),
65488
65368
  emptyMessage
65489
65369
  ] }) : 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)) }),
65370
+ footerAction && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { children: footerAction }),
65490
65371
  serverPagination && (() => {
65491
65372
  const { pagination, currentPage: cp, goToPage } = serverPagination;
65492
65373
  const totalPages = pagination.last_page ?? Math.ceil(pagination.total / pagination.per_page);
@@ -65563,7 +65444,9 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
65563
65444
  setDeleteItem(item);
65564
65445
  } : onDelete ? (item) => {
65565
65446
  onDelete(item);
65566
- } : void 0
65447
+ } : void 0,
65448
+ footerAction: footerPreviewAction ? footerPreviewAction(previewItem) : void 0,
65449
+ headerAction: headerPreviewAction
65567
65450
  }
65568
65451
  ),
65569
65452
  editItem && editBaseUrl && editFields && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -67033,6 +66916,64 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
67033
66916
  var React29 = __toESM(require_react(), 1);
67034
66917
  var import_react_dom3 = __toESM(require_react_dom(), 1);
67035
66918
 
66919
+ // src/components/tools/decryptPayload.ts
66920
+ var import_crypto_js = __toESM(require_crypto_js(), 1);
66921
+ function getLaravelSecretKey() {
66922
+ const viteKey = (() => {
66923
+ try {
66924
+ return new Function("return import.meta.env")();
66925
+ } catch {
66926
+ return void 0;
66927
+ }
66928
+ })()?.VITE_LARAVEL_KEY;
66929
+ const legacyKey = globalThis?.process?.env?.REACT_APP_LARAVEL_KEY;
66930
+ const windowKey = globalThis?.__LARAVEL_KEY__;
66931
+ const key = viteKey || legacyKey || windowKey;
66932
+ if (!key) {
66933
+ throw new Error("Missing Laravel decryption key. Set VITE_LARAVEL_KEY in your .env or inject window.__LARAVEL_KEY__ via Blade.");
66934
+ }
66935
+ return key;
66936
+ }
66937
+ function parseLaravelKey(secretKey) {
66938
+ if (secretKey.startsWith("base64:")) {
66939
+ return import_crypto_js.default.enc.Base64.parse(secretKey.slice("base64:".length));
66940
+ }
66941
+ return import_crypto_js.default.enc.Utf8.parse(secretKey);
66942
+ }
66943
+ function parseLaravelEncryptedPayload(payload) {
66944
+ let jsonStr = payload;
66945
+ try {
66946
+ jsonStr = atob(payload);
66947
+ } catch {
66948
+ }
66949
+ return JSON.parse(jsonStr);
66950
+ }
66951
+ function decryptLaravelPayload(payload, secretKey) {
66952
+ const resolvedKey = secretKey ?? getLaravelSecretKey();
66953
+ const parsed = parseLaravelEncryptedPayload(payload);
66954
+ if (parsed.tag) {
66955
+ throw new Error("Unsupported Laravel cipher (AEAD tag present). Expected AES-*-CBC payload.");
66956
+ }
66957
+ const key = parseLaravelKey(resolvedKey);
66958
+ const expectedMac = import_crypto_js.default.HmacSHA256(parsed.iv + parsed.value, key).toString(import_crypto_js.default.enc.Hex);
66959
+ if (expectedMac !== parsed.mac) {
66960
+ throw new Error("Invalid payload MAC (wrong key or tampered payload).");
66961
+ }
66962
+ const iv = import_crypto_js.default.enc.Base64.parse(parsed.iv);
66963
+ const ciphertext = import_crypto_js.default.enc.Base64.parse(parsed.value);
66964
+ const cipherParams = import_crypto_js.default.lib.CipherParams.create({ ciphertext });
66965
+ const decrypted = import_crypto_js.default.AES.decrypt(cipherParams, key, {
66966
+ iv,
66967
+ mode: import_crypto_js.default.mode.CBC,
66968
+ padding: import_crypto_js.default.pad.Pkcs7
66969
+ });
66970
+ const plaintext = decrypted.toString(import_crypto_js.default.enc.Utf8);
66971
+ if (!plaintext) {
66972
+ throw new Error("Decryption produced empty plaintext (wrong key/cipher).");
66973
+ }
66974
+ return JSON.parse(plaintext);
66975
+ }
66976
+
67036
66977
  // src/components/ui/pagination.tsx
67037
66978
  var import_jsx_runtime19 = __toESM(require_jsx_runtime(), 1);
67038
66979
  function range(start, end) {
@@ -69964,7 +69905,7 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
69964
69905
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
69965
69906
  "div",
69966
69907
  {
69967
- className: "fixed inset-0 z-50 flex items-center justify-center p-4",
69908
+ className: "fixed inset-0 z-50 flex glass items-center justify-center p-4",
69968
69909
  style: { background: "rgba(0,0,0,0.5)" },
69969
69910
  onMouseDown: (e) => {
69970
69911
  if (e.target === e.currentTarget) onClose();
@@ -77626,6 +77567,12 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
77626
77567
  };
77627
77568
  return { store, getStore };
77628
77569
  }
77570
+
77571
+ // src/core/decryption/decode.ts
77572
+ function decryptResponse(response, key) {
77573
+ const payload = typeof response === "string" ? response : response.data;
77574
+ return decryptLaravelPayload(payload, key);
77575
+ }
77629
77576
  return __toCommonJS(index_exports);
77630
77577
  })();
77631
77578
  /*! Bundled license information:
package/dist/index.js CHANGED
@@ -1438,66 +1438,6 @@ import * as React8 from "react";
1438
1438
  import { createPortal as createPortal2 } from "react-dom";
1439
1439
  import axios2 from "axios";
1440
1440
  import { Search, Pin, Tag, MoreHorizontal as MoreHorizontal2, AlertCircle, AlertTriangle, Info, X, ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight3, Pencil, Trash, Loader2 } from "lucide-react";
1441
-
1442
- // src/components/tools/decryptPayload.ts
1443
- import CryptoJS from "crypto-js";
1444
- function getLaravelSecretKey() {
1445
- const viteKey = (() => {
1446
- try {
1447
- return new Function("return import.meta.env")();
1448
- } catch {
1449
- return void 0;
1450
- }
1451
- })()?.VITE_LARAVEL_KEY;
1452
- const legacyKey = globalThis?.process?.env?.REACT_APP_LARAVEL_KEY;
1453
- const windowKey = globalThis?.__LARAVEL_KEY__;
1454
- const key = viteKey || legacyKey || windowKey;
1455
- if (!key) {
1456
- throw new Error("Missing Laravel decryption key. Set VITE_LARAVEL_KEY in your .env or inject window.__LARAVEL_KEY__ via Blade.");
1457
- }
1458
- return key;
1459
- }
1460
- function parseLaravelKey(secretKey) {
1461
- if (secretKey.startsWith("base64:")) {
1462
- return CryptoJS.enc.Base64.parse(secretKey.slice("base64:".length));
1463
- }
1464
- return CryptoJS.enc.Utf8.parse(secretKey);
1465
- }
1466
- function parseLaravelEncryptedPayload(payload) {
1467
- let jsonStr = payload;
1468
- try {
1469
- jsonStr = atob(payload);
1470
- } catch {
1471
- }
1472
- return JSON.parse(jsonStr);
1473
- }
1474
- function decryptLaravelPayload(payload, secretKey) {
1475
- const resolvedKey = secretKey ?? getLaravelSecretKey();
1476
- const parsed = parseLaravelEncryptedPayload(payload);
1477
- if (parsed.tag) {
1478
- throw new Error("Unsupported Laravel cipher (AEAD tag present). Expected AES-*-CBC payload.");
1479
- }
1480
- const key = parseLaravelKey(resolvedKey);
1481
- const expectedMac = CryptoJS.HmacSHA256(parsed.iv + parsed.value, key).toString(CryptoJS.enc.Hex);
1482
- if (expectedMac !== parsed.mac) {
1483
- throw new Error("Invalid payload MAC (wrong key or tampered payload).");
1484
- }
1485
- const iv = CryptoJS.enc.Base64.parse(parsed.iv);
1486
- const ciphertext = CryptoJS.enc.Base64.parse(parsed.value);
1487
- const cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext });
1488
- const decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
1489
- iv,
1490
- mode: CryptoJS.mode.CBC,
1491
- padding: CryptoJS.pad.Pkcs7
1492
- });
1493
- const plaintext = decrypted.toString(CryptoJS.enc.Utf8);
1494
- if (!plaintext) {
1495
- throw new Error("Decryption produced empty plaintext (wrong key/cipher).");
1496
- }
1497
- return JSON.parse(plaintext);
1498
- }
1499
-
1500
- // src/components/ui/bulletin-board.tsx
1501
1441
  import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1502
1442
  var PRIORITY_CONFIG = {
1503
1443
  urgent: { label: "Urgent", icon: /* @__PURE__ */ jsx10(AlertCircle, { className: "h-3 w-3" }), badge: "error" },
@@ -1527,82 +1467,13 @@ function AuthorAvatar({ item }) {
1527
1467
  }
1528
1468
  return null;
1529
1469
  }
1530
- function useServerBulletin({
1531
- url,
1532
- params,
1533
- encrypt,
1534
- key,
1535
- decryptPayloadLog,
1536
- transform
1537
- }) {
1538
- const [items, setItems] = React8.useState([]);
1539
- const [currentPage, setCurrentPage] = React8.useState(1);
1540
- const [pagination, setPagination] = React8.useState(null);
1541
- const [loading, setLoading] = React8.useState(false);
1542
- const [error, setError] = React8.useState(null);
1543
- const [tick, setTick] = React8.useState(0);
1544
- React8.useEffect(() => {
1545
- let cancelled = false;
1546
- setLoading(true);
1547
- setError(null);
1548
- axios2.get(url, { params: { ...params, page: currentPage } }).then(({ data: res }) => {
1549
- if (cancelled) return;
1550
- let payload;
1551
- try {
1552
- payload = encrypt ? decryptLaravelPayload(res, key) : res;
1553
- } catch (decryptErr) {
1554
- console.error("[useServerBulletin] decryption failed:", decryptErr?.message ?? decryptErr);
1555
- if (!cancelled) setError(decryptErr?.message ?? "Decryption failed");
1556
- return;
1557
- }
1558
- if (encrypt && decryptPayloadLog) console.log("[useServerBulletin] decrypted:", payload);
1559
- const rows = Array.isArray(payload) ? payload : payload.data ?? [];
1560
- setItems(transform ? rows.map(transform) : rows);
1561
- if (!Array.isArray(payload)) {
1562
- const rawTotal = payload.total;
1563
- const rawPerPage = payload.per_page;
1564
- const rawLastPage = payload.last_page;
1565
- const lastPage = rawLastPage ?? Math.ceil(rawTotal / rawPerPage);
1566
- const pg = payload.pagination ?? {
1567
- first_page_url: payload.first_page_url ?? `${url}?page=1`,
1568
- last_page_url: payload.last_page_url ?? `${url}?page=${lastPage}`,
1569
- last_page: lastPage,
1570
- next_page_url: payload.next_page_url !== void 0 ? payload.next_page_url : currentPage < lastPage ? `${url}?page=${currentPage + 1}` : null,
1571
- prev_page_url: payload.prev_page_url !== void 0 ? payload.prev_page_url : currentPage > 1 ? `${url}?page=${currentPage - 1}` : null,
1572
- per_page: rawPerPage,
1573
- total: rawTotal,
1574
- links: payload.links ?? []
1575
- };
1576
- setPagination(pg);
1577
- }
1578
- }).catch((err) => {
1579
- if (cancelled) return;
1580
- setError(err?.response?.data?.message ?? err.message ?? "Request failed");
1581
- }).finally(() => {
1582
- if (!cancelled) setLoading(false);
1583
- });
1584
- return () => {
1585
- cancelled = true;
1586
- };
1587
- }, [url, currentPage, tick, JSON.stringify(params), encrypt, decryptPayloadLog]);
1588
- return {
1589
- items,
1590
- currentPage,
1591
- pagination,
1592
- serverPagination: pagination ? { pagination, currentPage, goToPage: (p) => setCurrentPage(p) } : null,
1593
- loading,
1594
- error,
1595
- goToPage: (p) => setCurrentPage(p),
1596
- reload: () => setTick((t) => t + 1)
1597
- };
1598
- }
1599
- function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions }) {
1470
+ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions, headerAction, footerAction }) {
1600
1471
  const priority = item.priority ? PRIORITY_CONFIG[item.priority] : null;
1601
1472
  return createPortal2(
1602
1473
  /* @__PURE__ */ jsx10(
1603
1474
  "div",
1604
1475
  {
1605
- className: "fixed inset-0 z-50 flex items-center justify-center p-4",
1476
+ className: "fixed inset-0 z-50 flex glass items-center justify-center p-4",
1606
1477
  style: { background: "rgba(0,0,0,0.55)" },
1607
1478
  onMouseDown: (e) => {
1608
1479
  if (e.target === e.currentTarget) onClose();
@@ -1615,7 +1486,8 @@ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customAction
1615
1486
  " Pinned"
1616
1487
  ] }),
1617
1488
  priority && /* @__PURE__ */ jsx10(Badge, { variant: priority.badge, size: "sm", icon: priority.icon ?? void 0, children: priority.label }),
1618
- item.category && /* @__PURE__ */ jsx10(Badge, { variant: "outline", size: "sm", children: item.category })
1489
+ item.category && /* @__PURE__ */ jsx10(Badge, { variant: "outline", size: "sm", children: item.category }),
1490
+ headerAction
1619
1491
  ] }),
1620
1492
  /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1 shrink-0", children: [
1621
1493
  onEdit && /* @__PURE__ */ jsx10(
@@ -1714,6 +1586,7 @@ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customAction
1714
1586
  ] })
1715
1587
  ] }) }),
1716
1588
  /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
1589
+ footerAction,
1717
1590
  onEdit && /* @__PURE__ */ jsxs9(
1718
1591
  "button",
1719
1592
  {
@@ -2108,6 +1981,9 @@ function BulletinBoard({
2108
1981
  deleteBaseUrl,
2109
1982
  deleteIdKey = "id",
2110
1983
  serverPagination,
1984
+ footerAction,
1985
+ headerPreviewAction,
1986
+ footerPreviewAction,
2111
1987
  className
2112
1988
  }) {
2113
1989
  const [previewItem, setPreviewItem] = React8.useState(null);
@@ -2192,6 +2068,7 @@ function BulletinBoard({
2192
2068
  /* @__PURE__ */ jsx10(Pin, { className: "h-8 w-8 opacity-20" }),
2193
2069
  emptyMessage
2194
2070
  ] }) : layout === "list" ? /* @__PURE__ */ jsx10("div", { className: "flex flex-col gap-3", children: filtered.map((item) => /* @__PURE__ */ jsx10(BulletinCard, { item, variant, layout: "list", onClick: handleCardClick }, item.id)) }) : layout === "masonry" ? /* @__PURE__ */ jsx10("div", { className: cn("gap-4", COLS_CLASS[columns]), style: { display: "grid", gridTemplateRows: "masonry" }, children: filtered.map((item) => /* @__PURE__ */ jsx10(BulletinCard, { item, variant, layout: "masonry", onClick: handleCardClick }, item.id)) }) : /* @__PURE__ */ jsx10("div", { className: cn("grid gap-4", COLS_CLASS[columns]), children: filtered.map((item) => /* @__PURE__ */ jsx10(BulletinCard, { item, variant, layout: "grid", onClick: handleCardClick }, item.id)) }),
2071
+ footerAction && /* @__PURE__ */ jsx10("div", { children: footerAction }),
2195
2072
  serverPagination && (() => {
2196
2073
  const { pagination, currentPage: cp, goToPage } = serverPagination;
2197
2074
  const totalPages = pagination.last_page ?? Math.ceil(pagination.total / pagination.per_page);
@@ -2268,7 +2145,9 @@ function BulletinBoard({
2268
2145
  setDeleteItem(item);
2269
2146
  } : onDelete ? (item) => {
2270
2147
  onDelete(item);
2271
- } : void 0
2148
+ } : void 0,
2149
+ footerAction: footerPreviewAction ? footerPreviewAction(previewItem) : void 0,
2150
+ headerAction: headerPreviewAction
2272
2151
  }
2273
2152
  ),
2274
2153
  editItem && editBaseUrl && editFields && /* @__PURE__ */ jsx10(
@@ -3742,6 +3621,66 @@ function MetricRow({ items, divided = true, className }) {
3742
3621
  // src/components/ui/data-grid.tsx
3743
3622
  import * as React29 from "react";
3744
3623
  import { createPortal as createPortal4 } from "react-dom";
3624
+
3625
+ // src/components/tools/decryptPayload.ts
3626
+ import CryptoJS from "crypto-js";
3627
+ function getLaravelSecretKey() {
3628
+ const viteKey = (() => {
3629
+ try {
3630
+ return new Function("return import.meta.env")();
3631
+ } catch {
3632
+ return void 0;
3633
+ }
3634
+ })()?.VITE_LARAVEL_KEY;
3635
+ const legacyKey = globalThis?.process?.env?.REACT_APP_LARAVEL_KEY;
3636
+ const windowKey = globalThis?.__LARAVEL_KEY__;
3637
+ const key = viteKey || legacyKey || windowKey;
3638
+ if (!key) {
3639
+ throw new Error("Missing Laravel decryption key. Set VITE_LARAVEL_KEY in your .env or inject window.__LARAVEL_KEY__ via Blade.");
3640
+ }
3641
+ return key;
3642
+ }
3643
+ function parseLaravelKey(secretKey) {
3644
+ if (secretKey.startsWith("base64:")) {
3645
+ return CryptoJS.enc.Base64.parse(secretKey.slice("base64:".length));
3646
+ }
3647
+ return CryptoJS.enc.Utf8.parse(secretKey);
3648
+ }
3649
+ function parseLaravelEncryptedPayload(payload) {
3650
+ let jsonStr = payload;
3651
+ try {
3652
+ jsonStr = atob(payload);
3653
+ } catch {
3654
+ }
3655
+ return JSON.parse(jsonStr);
3656
+ }
3657
+ function decryptLaravelPayload(payload, secretKey) {
3658
+ const resolvedKey = secretKey ?? getLaravelSecretKey();
3659
+ const parsed = parseLaravelEncryptedPayload(payload);
3660
+ if (parsed.tag) {
3661
+ throw new Error("Unsupported Laravel cipher (AEAD tag present). Expected AES-*-CBC payload.");
3662
+ }
3663
+ const key = parseLaravelKey(resolvedKey);
3664
+ const expectedMac = CryptoJS.HmacSHA256(parsed.iv + parsed.value, key).toString(CryptoJS.enc.Hex);
3665
+ if (expectedMac !== parsed.mac) {
3666
+ throw new Error("Invalid payload MAC (wrong key or tampered payload).");
3667
+ }
3668
+ const iv = CryptoJS.enc.Base64.parse(parsed.iv);
3669
+ const ciphertext = CryptoJS.enc.Base64.parse(parsed.value);
3670
+ const cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext });
3671
+ const decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
3672
+ iv,
3673
+ mode: CryptoJS.mode.CBC,
3674
+ padding: CryptoJS.pad.Pkcs7
3675
+ });
3676
+ const plaintext = decrypted.toString(CryptoJS.enc.Utf8);
3677
+ if (!plaintext) {
3678
+ throw new Error("Decryption produced empty plaintext (wrong key/cipher).");
3679
+ }
3680
+ return JSON.parse(plaintext);
3681
+ }
3682
+
3683
+ // src/components/ui/data-grid.tsx
3745
3684
  import axios4 from "axios";
3746
3685
  import { ChevronUp as ChevronUp2, ChevronDown as ChevronDown5, ChevronsUpDown as ChevronsUpDown2, ChevronLeft as ChevronLeft7, ChevronRight as ChevronRight9, Search as Search6, Settings2, Check as Check5, Eye as Eye3, Pencil as Pencil3, Trash as Trash4, Loader2 as Loader23, X as X10 } from "lucide-react";
3747
3686
 
@@ -6685,7 +6624,7 @@ function ModalShell({ title, onClose, children, footer, width = "lg" }) {
6685
6624
  /* @__PURE__ */ jsx32(
6686
6625
  "div",
6687
6626
  {
6688
- className: "fixed inset-0 z-50 flex items-center justify-center p-4",
6627
+ className: "fixed inset-0 z-50 flex glass items-center justify-center p-4",
6689
6628
  style: { background: "rgba(0,0,0,0.5)" },
6690
6629
  onMouseDown: (e) => {
6691
6630
  if (e.target === e.currentTarget) onClose();
@@ -13696,6 +13635,12 @@ function createStore(initialValue, sessionName, expireInterval) {
13696
13635
  };
13697
13636
  return { store, getStore };
13698
13637
  }
13638
+
13639
+ // src/core/decryption/decode.ts
13640
+ function decryptResponse(response, key) {
13641
+ const payload = typeof response === "string" ? response : response.data;
13642
+ return decryptLaravelPayload(payload, key);
13643
+ }
13699
13644
  export {
13700
13645
  Accordion,
13701
13646
  Authentication,
@@ -13795,7 +13740,11 @@ export {
13795
13740
  Wizard,
13796
13741
  api,
13797
13742
  createStore,
13798
- useServerBulletin,
13743
+ decryptLaravelPayload,
13744
+ decryptResponse,
13745
+ getLaravelSecretKey,
13746
+ parseLaravelEncryptedPayload,
13747
+ parseLaravelKey,
13799
13748
  useServerDataGrid,
13800
13749
  useServerTable,
13801
13750
  useTheme,
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "registry": "https://registry.npmjs.org/",
5
5
  "access": "public"
6
6
  },
7
- "version": "3.5.21",
7
+ "version": "3.6.0",
8
8
  "description": "Reusable React UI components",
9
9
  "license": "MIT",
10
10
  "main": "dist/index.js",