@juv/codego-react-ui 3.5.5 → 3.5.6

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
@@ -739,6 +739,53 @@ function AnnouncementsPage() {
739
739
 
740
740
  ---
741
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
+
742
789
  ## Run Locally
743
790
 
744
791
  **Prerequisites:** Node.js
package/dist/index.cjs CHANGED
@@ -2160,7 +2160,7 @@ function useServerBulletin({
2160
2160
  reload: () => setTick((t) => t + 1)
2161
2161
  };
2162
2162
  }
2163
- function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions }) {
2163
+ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions, headerAction, footerAction }) {
2164
2164
  const priority = item.priority ? PRIORITY_CONFIG[item.priority] : null;
2165
2165
  return (0, import_react_dom.createPortal)(
2166
2166
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -2179,7 +2179,8 @@ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customAction
2179
2179
  " Pinned"
2180
2180
  ] }),
2181
2181
  priority && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Badge, { variant: priority.badge, size: "sm", icon: priority.icon ?? void 0, children: priority.label }),
2182
- item.category && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Badge, { variant: "outline", size: "sm", children: item.category })
2182
+ item.category && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Badge, { variant: "outline", size: "sm", children: item.category }),
2183
+ headerAction
2183
2184
  ] }),
2184
2185
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1 shrink-0", children: [
2185
2186
  onEdit && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -2278,6 +2279,7 @@ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customAction
2278
2279
  ] })
2279
2280
  ] }) }),
2280
2281
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
2282
+ footerAction,
2281
2283
  onEdit && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2282
2284
  "button",
2283
2285
  {
@@ -2672,6 +2674,9 @@ function BulletinBoard({
2672
2674
  deleteBaseUrl,
2673
2675
  deleteIdKey = "id",
2674
2676
  serverPagination,
2677
+ footerAction,
2678
+ headerPreviewAction,
2679
+ footerPreviewAction,
2675
2680
  className
2676
2681
  }) {
2677
2682
  const [previewItem, setPreviewItem] = React10.useState(null);
@@ -2756,6 +2761,7 @@ function BulletinBoard({
2756
2761
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.Pin, { className: "h-8 w-8 opacity-20" }),
2757
2762
  emptyMessage
2758
2763
  ] }) : layout === "list" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex flex-col gap-3", children: filtered.map((item) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BulletinCard, { item, variant, layout: "list", onClick: handleCardClick }, item.id)) }) : layout === "masonry" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: cn("gap-4", COLS_CLASS[columns]), style: { display: "grid", gridTemplateRows: "masonry" }, children: filtered.map((item) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BulletinCard, { item, variant, layout: "masonry", onClick: handleCardClick }, item.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: cn("grid gap-4", COLS_CLASS[columns]), children: filtered.map((item) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BulletinCard, { item, variant, layout: "grid", onClick: handleCardClick }, item.id)) }),
2764
+ footerAction && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { children: footerAction }),
2759
2765
  serverPagination && (() => {
2760
2766
  const { pagination, currentPage: cp, goToPage } = serverPagination;
2761
2767
  const totalPages = pagination.last_page ?? Math.ceil(pagination.total / pagination.per_page);
@@ -2832,7 +2838,9 @@ function BulletinBoard({
2832
2838
  setDeleteItem(item);
2833
2839
  } : onDelete ? (item) => {
2834
2840
  onDelete(item);
2835
- } : void 0
2841
+ } : void 0,
2842
+ footerAction: footerPreviewAction,
2843
+ headerAction: headerPreviewAction
2836
2844
  }
2837
2845
  ),
2838
2846
  editItem && editBaseUrl && editFields && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
package/dist/index.d.cts CHANGED
@@ -839,8 +839,12 @@ interface BulletinPreviewProps {
839
839
  onView?: (item: BulletinItem) => void;
840
840
  /** Custom actions to add to the preview header */
841
841
  customActions?: BulletinAction[];
842
+ /** Extra React elements rendered in the preview modal header's left area */
843
+ headerAction?: React.ReactNode;
844
+ /** Extra React elements rendered in the preview modal footer's action area */
845
+ footerAction?: React.ReactNode;
842
846
  }
843
- declare function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions }: BulletinPreviewProps): React.ReactPortal;
847
+ declare function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions, headerAction, footerAction }: BulletinPreviewProps): React.ReactPortal;
844
848
  interface BulletinEditField {
845
849
  key: keyof BulletinItem | string;
846
850
  label: string;
@@ -899,10 +903,16 @@ interface BulletinBoardProps {
899
903
  serverPagination?: BulletinServerPaginationProp | null;
900
904
  /** Fired when a post card is clicked (ignored when preview=true). */
901
905
  onItemClick?: (item: BulletinItem) => void;
906
+ /** Extra React elements rendered below the board content (above pagination). */
907
+ footerAction?: React.ReactNode;
908
+ /** Extra React elements rendered in the preview modal header's left area. */
909
+ headerPreviewAction?: React.ReactNode;
910
+ /** Extra React elements rendered in the preview modal footer's action area. */
911
+ footerPreviewAction?: React.ReactNode;
902
912
  /** Additional CSS classes on the outer wrapper. */
903
913
  className?: string;
904
914
  }
905
- 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;
915
+ 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;
906
916
 
907
917
  interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
908
918
  variant?: "primary" | "secondary" | "outline" | "ghost" | "link" | "danger" | "success" | "destructive";
package/dist/index.d.ts CHANGED
@@ -839,8 +839,12 @@ interface BulletinPreviewProps {
839
839
  onView?: (item: BulletinItem) => void;
840
840
  /** Custom actions to add to the preview header */
841
841
  customActions?: BulletinAction[];
842
+ /** Extra React elements rendered in the preview modal header's left area */
843
+ headerAction?: React.ReactNode;
844
+ /** Extra React elements rendered in the preview modal footer's action area */
845
+ footerAction?: React.ReactNode;
842
846
  }
843
- declare function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions }: BulletinPreviewProps): React.ReactPortal;
847
+ declare function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions, headerAction, footerAction }: BulletinPreviewProps): React.ReactPortal;
844
848
  interface BulletinEditField {
845
849
  key: keyof BulletinItem | string;
846
850
  label: string;
@@ -899,10 +903,16 @@ interface BulletinBoardProps {
899
903
  serverPagination?: BulletinServerPaginationProp | null;
900
904
  /** Fired when a post card is clicked (ignored when preview=true). */
901
905
  onItemClick?: (item: BulletinItem) => void;
906
+ /** Extra React elements rendered below the board content (above pagination). */
907
+ footerAction?: React.ReactNode;
908
+ /** Extra React elements rendered in the preview modal header's left area. */
909
+ headerPreviewAction?: React.ReactNode;
910
+ /** Extra React elements rendered in the preview modal footer's action area. */
911
+ footerPreviewAction?: React.ReactNode;
902
912
  /** Additional CSS classes on the outer wrapper. */
903
913
  className?: string;
904
914
  }
905
- 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;
915
+ 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;
906
916
 
907
917
  interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
908
918
  variant?: "primary" | "secondary" | "outline" | "ghost" | "link" | "danger" | "success" | "destructive";
@@ -65330,7 +65330,7 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
65330
65330
  reload: () => setTick((t) => t + 1)
65331
65331
  };
65332
65332
  }
65333
- function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions }) {
65333
+ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions, headerAction, footerAction }) {
65334
65334
  const priority = item.priority ? PRIORITY_CONFIG[item.priority] : null;
65335
65335
  return (0, import_react_dom.createPortal)(
65336
65336
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -65349,7 +65349,8 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
65349
65349
  " Pinned"
65350
65350
  ] }),
65351
65351
  priority && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Badge, { variant: priority.badge, size: "sm", icon: priority.icon ?? void 0, children: priority.label }),
65352
- item.category && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Badge, { variant: "outline", size: "sm", children: item.category })
65352
+ item.category && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Badge, { variant: "outline", size: "sm", children: item.category }),
65353
+ headerAction
65353
65354
  ] }),
65354
65355
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1 shrink-0", children: [
65355
65356
  onEdit && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -65448,6 +65449,7 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
65448
65449
  ] })
65449
65450
  ] }) }),
65450
65451
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2", children: [
65452
+ footerAction,
65451
65453
  onEdit && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
65452
65454
  "button",
65453
65455
  {
@@ -65842,6 +65844,9 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
65842
65844
  deleteBaseUrl,
65843
65845
  deleteIdKey = "id",
65844
65846
  serverPagination,
65847
+ footerAction,
65848
+ headerPreviewAction,
65849
+ footerPreviewAction,
65845
65850
  className
65846
65851
  }) {
65847
65852
  const [previewItem, setPreviewItem] = React10.useState(null);
@@ -65926,6 +65931,7 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
65926
65931
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pin, { className: "h-8 w-8 opacity-20" }),
65927
65932
  emptyMessage
65928
65933
  ] }) : layout === "list" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex flex-col gap-3", children: filtered.map((item) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BulletinCard, { item, variant, layout: "list", onClick: handleCardClick }, item.id)) }) : layout === "masonry" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: cn("gap-4", COLS_CLASS[columns]), style: { display: "grid", gridTemplateRows: "masonry" }, children: filtered.map((item) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BulletinCard, { item, variant, layout: "masonry", onClick: handleCardClick }, item.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: cn("grid gap-4", COLS_CLASS[columns]), children: filtered.map((item) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BulletinCard, { item, variant, layout: "grid", onClick: handleCardClick }, item.id)) }),
65934
+ footerAction && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { children: footerAction }),
65929
65935
  serverPagination && (() => {
65930
65936
  const { pagination, currentPage: cp, goToPage } = serverPagination;
65931
65937
  const totalPages = pagination.last_page ?? Math.ceil(pagination.total / pagination.per_page);
@@ -66002,7 +66008,9 @@ ${n2.shaderPreludeCode.vertexSource}`, define: n2.shaderDefine }, defaultProject
66002
66008
  setDeleteItem(item);
66003
66009
  } : onDelete ? (item) => {
66004
66010
  onDelete(item);
66005
- } : void 0
66011
+ } : void 0,
66012
+ footerAction: footerPreviewAction,
66013
+ headerAction: headerPreviewAction
66006
66014
  }
66007
66015
  ),
66008
66016
  editItem && editBaseUrl && editFields && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
package/dist/index.js CHANGED
@@ -2034,7 +2034,7 @@ function useServerBulletin({
2034
2034
  reload: () => setTick((t) => t + 1)
2035
2035
  };
2036
2036
  }
2037
- function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions }) {
2037
+ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customActions, headerAction, footerAction }) {
2038
2038
  const priority = item.priority ? PRIORITY_CONFIG[item.priority] : null;
2039
2039
  return createPortal2(
2040
2040
  /* @__PURE__ */ jsx12(
@@ -2053,7 +2053,8 @@ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customAction
2053
2053
  " Pinned"
2054
2054
  ] }),
2055
2055
  priority && /* @__PURE__ */ jsx12(Badge, { variant: priority.badge, size: "sm", icon: priority.icon ?? void 0, children: priority.label }),
2056
- item.category && /* @__PURE__ */ jsx12(Badge, { variant: "outline", size: "sm", children: item.category })
2056
+ item.category && /* @__PURE__ */ jsx12(Badge, { variant: "outline", size: "sm", children: item.category }),
2057
+ headerAction
2057
2058
  ] }),
2058
2059
  /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1 shrink-0", children: [
2059
2060
  onEdit && /* @__PURE__ */ jsx12(
@@ -2152,6 +2153,7 @@ function BulletinPreview({ item, onClose, onEdit, onDelete, onView, customAction
2152
2153
  ] })
2153
2154
  ] }) }),
2154
2155
  /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
2156
+ footerAction,
2155
2157
  onEdit && /* @__PURE__ */ jsxs10(
2156
2158
  "button",
2157
2159
  {
@@ -2546,6 +2548,9 @@ function BulletinBoard({
2546
2548
  deleteBaseUrl,
2547
2549
  deleteIdKey = "id",
2548
2550
  serverPagination,
2551
+ footerAction,
2552
+ headerPreviewAction,
2553
+ footerPreviewAction,
2549
2554
  className
2550
2555
  }) {
2551
2556
  const [previewItem, setPreviewItem] = React10.useState(null);
@@ -2630,6 +2635,7 @@ function BulletinBoard({
2630
2635
  /* @__PURE__ */ jsx12(Pin, { className: "h-8 w-8 opacity-20" }),
2631
2636
  emptyMessage
2632
2637
  ] }) : layout === "list" ? /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-3", children: filtered.map((item) => /* @__PURE__ */ jsx12(BulletinCard, { item, variant, layout: "list", onClick: handleCardClick }, item.id)) }) : layout === "masonry" ? /* @__PURE__ */ jsx12("div", { className: cn("gap-4", COLS_CLASS[columns]), style: { display: "grid", gridTemplateRows: "masonry" }, children: filtered.map((item) => /* @__PURE__ */ jsx12(BulletinCard, { item, variant, layout: "masonry", onClick: handleCardClick }, item.id)) }) : /* @__PURE__ */ jsx12("div", { className: cn("grid gap-4", COLS_CLASS[columns]), children: filtered.map((item) => /* @__PURE__ */ jsx12(BulletinCard, { item, variant, layout: "grid", onClick: handleCardClick }, item.id)) }),
2638
+ footerAction && /* @__PURE__ */ jsx12("div", { children: footerAction }),
2633
2639
  serverPagination && (() => {
2634
2640
  const { pagination, currentPage: cp, goToPage } = serverPagination;
2635
2641
  const totalPages = pagination.last_page ?? Math.ceil(pagination.total / pagination.per_page);
@@ -2706,7 +2712,9 @@ function BulletinBoard({
2706
2712
  setDeleteItem(item);
2707
2713
  } : onDelete ? (item) => {
2708
2714
  onDelete(item);
2709
- } : void 0
2715
+ } : void 0,
2716
+ footerAction: footerPreviewAction,
2717
+ headerAction: headerPreviewAction
2710
2718
  }
2711
2719
  ),
2712
2720
  editItem && editBaseUrl && editFields && /* @__PURE__ */ jsx12(
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.5",
7
+ "version": "3.5.6",
8
8
  "description": "Reusable React UI components",
9
9
  "license": "MIT",
10
10
  "main": "dist/index.js",