@fluid-app/portal-sdk 0.1.206 → 0.1.207

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.
Files changed (62) hide show
  1. package/dist/{FluidProvider-kEtG78-R.mjs → FluidProvider-5TV1VHtq.mjs} +2 -2
  2. package/dist/{FluidProvider-kEtG78-R.mjs.map → FluidProvider-5TV1VHtq.mjs.map} +1 -1
  3. package/dist/{FluidProvider-DgZSqdB4.cjs → FluidProvider-DP-sbNiR.cjs} +2 -2
  4. package/dist/{FluidProvider-DgZSqdB4.cjs.map → FluidProvider-DP-sbNiR.cjs.map} +1 -1
  5. package/dist/{MessagingScreen-B5F4cJL9.mjs → MessagingScreen-BSksI6Nw.mjs} +2 -2
  6. package/dist/{MessagingScreen-B5F4cJL9.mjs.map → MessagingScreen-BSksI6Nw.mjs.map} +1 -1
  7. package/dist/{MessagingScreen-BZNsf1M6.cjs → MessagingScreen-CXAzx8Ra.cjs} +2 -2
  8. package/dist/{MessagingScreen-Rr8rNnDM.cjs → MessagingScreen-D2G4zfez.cjs} +2 -2
  9. package/dist/{MessagingScreen-Rr8rNnDM.cjs.map → MessagingScreen-D2G4zfez.cjs.map} +1 -1
  10. package/dist/{OrdersScreen-Vcd072vf.cjs → OrdersScreen-CGvcgmLz.cjs} +1 -1
  11. package/dist/{OrdersScreen-CpU_Y_qI.cjs → OrdersScreen-Cx7xpzDk.cjs} +2 -2
  12. package/dist/{OrdersScreen-CpU_Y_qI.cjs.map → OrdersScreen-Cx7xpzDk.cjs.map} +1 -1
  13. package/dist/{OrdersScreen-jvCpiDDt.mjs → OrdersScreen-D4pe5mLs.mjs} +2 -2
  14. package/dist/{OrdersScreen-jvCpiDDt.mjs.map → OrdersScreen-D4pe5mLs.mjs.map} +1 -1
  15. package/dist/{PortalContentApiProvider-DqAbDYgq.cjs → PortalContentApiProvider-C5WzWC3k.cjs} +671 -765
  16. package/dist/PortalContentApiProvider-C5WzWC3k.cjs.map +1 -0
  17. package/dist/{PortalContentApiProvider-DxCHKK04.mjs → PortalContentApiProvider-CAa1jYwz.mjs} +671 -765
  18. package/dist/PortalContentApiProvider-CAa1jYwz.mjs.map +1 -0
  19. package/dist/{PortalProductsApiProvider-DaQUq6vq.cjs → PortalProductsApiProvider-Ca1oeTtJ.cjs} +2 -2
  20. package/dist/{PortalProductsApiProvider-DaQUq6vq.cjs.map → PortalProductsApiProvider-Ca1oeTtJ.cjs.map} +1 -1
  21. package/dist/{PortalProductsApiProvider-Y_4IQZBP.mjs → PortalProductsApiProvider-DHni3Y1V.mjs} +2 -2
  22. package/dist/{PortalProductsApiProvider-Y_4IQZBP.mjs.map → PortalProductsApiProvider-DHni3Y1V.mjs.map} +1 -1
  23. package/dist/{ProductsScreen-BQ4pOdSM.cjs → ProductsScreen-BZG_hkqN.cjs} +3 -3
  24. package/dist/{ProductsScreen-BQ4pOdSM.cjs.map → ProductsScreen-BZG_hkqN.cjs.map} +1 -1
  25. package/dist/{ProductsScreen-BXVezV86.mjs → ProductsScreen-DNXJ6Pml.mjs} +3 -3
  26. package/dist/{ProductsScreen-BXVezV86.mjs.map → ProductsScreen-DNXJ6Pml.mjs.map} +1 -1
  27. package/dist/{ProductsScreen-CwuS5xwA.cjs → ProductsScreen-DfKQAJ8t.cjs} +3 -3
  28. package/dist/{ProductsScreen-BuYB8ARn.mjs → ProductsScreen-dbTX6T_M.mjs} +3 -3
  29. package/dist/{ProfileScreen-BXyMS54d.cjs → ProfileScreen-CBwwNrKI.cjs} +2 -2
  30. package/dist/{ProfileScreen-AEC-nP75.mjs → ProfileScreen-Mb6dPLPo.mjs} +2 -2
  31. package/dist/{ProfileScreen-AEC-nP75.mjs.map → ProfileScreen-Mb6dPLPo.mjs.map} +1 -1
  32. package/dist/{ProfileScreen-BwB62fMh.cjs → ProfileScreen-Xym_39QW.cjs} +2 -2
  33. package/dist/{ProfileScreen-BwB62fMh.cjs.map → ProfileScreen-Xym_39QW.cjs.map} +1 -1
  34. package/dist/{ShareablesScreen-iI_TDe30.cjs → ShareablesScreen-B-q3ovAv.cjs} +3 -3
  35. package/dist/{ShareablesScreen-BncSEvDU.mjs → ShareablesScreen-Be0YXQ2y.mjs} +3 -3
  36. package/dist/{ShareablesScreen-BncSEvDU.mjs.map → ShareablesScreen-Be0YXQ2y.mjs.map} +1 -1
  37. package/dist/{ShareablesScreen-BRLq4OJS.mjs → ShareablesScreen-CpFuhYs5.mjs} +3 -3
  38. package/dist/{ShareablesScreen-DooJX53H.cjs → ShareablesScreen-vk_JZ-_9.cjs} +3 -3
  39. package/dist/{ShareablesScreen-DooJX53H.cjs.map → ShareablesScreen-vk_JZ-_9.cjs.map} +1 -1
  40. package/dist/{ShopScreen-B-D-upOl.cjs → ShopScreen-B92DRQkQ.cjs} +3 -3
  41. package/dist/{ShopScreen-B-D-upOl.cjs.map → ShopScreen-B92DRQkQ.cjs.map} +1 -1
  42. package/dist/{ShopScreen-Ja1W8IMs.mjs → ShopScreen-BOd8LD3L.mjs} +3 -3
  43. package/dist/{ShopScreen-Ja1W8IMs.mjs.map → ShopScreen-BOd8LD3L.mjs.map} +1 -1
  44. package/dist/{ShopScreen-CzPTkvqY.cjs → ShopScreen-BRN3JY4l.cjs} +3 -3
  45. package/dist/{SubscriptionsScreen-e2lCfnL0.cjs → SubscriptionsScreen-CQQPtSbM.cjs} +1 -1
  46. package/dist/{SubscriptionsScreen-COOAJ8r5.mjs → SubscriptionsScreen-CuP9OfBI.mjs} +94 -11
  47. package/dist/SubscriptionsScreen-CuP9OfBI.mjs.map +1 -0
  48. package/dist/{SubscriptionsScreen-Cx6u1tDv.cjs → SubscriptionsScreen-YUtsF_Eq.cjs} +92 -9
  49. package/dist/SubscriptionsScreen-YUtsF_Eq.cjs.map +1 -0
  50. package/dist/index.cjs +24 -24
  51. package/dist/index.mjs +24 -24
  52. package/dist/{portal_tenant-VLrtyCZ3.mjs → portal_tenant-Mu12SQA1.mjs} +2 -2
  53. package/dist/portal_tenant-Mu12SQA1.mjs.map +1 -0
  54. package/dist/{portal_tenant-DMF89PmN.cjs → portal_tenant-dfv03Fyi.cjs} +2 -2
  55. package/dist/portal_tenant-dfv03Fyi.cjs.map +1 -0
  56. package/package.json +13 -13
  57. package/dist/PortalContentApiProvider-DqAbDYgq.cjs.map +0 -1
  58. package/dist/PortalContentApiProvider-DxCHKK04.mjs.map +0 -1
  59. package/dist/SubscriptionsScreen-COOAJ8r5.mjs.map +0 -1
  60. package/dist/SubscriptionsScreen-Cx6u1tDv.cjs.map +0 -1
  61. package/dist/portal_tenant-DMF89PmN.cjs.map +0 -1
  62. package/dist/portal_tenant-VLrtyCZ3.mjs.map +0 -1
@@ -8,7 +8,7 @@ const require_es = require("./es-DHLLltoR.cjs");
8
8
  const require_SearchSort = require("./SearchSort-ztRXlRwu.cjs");
9
9
  const require_dist$4 = require("./dist-DDZMFlal.cjs");
10
10
  const require_dist$5 = require("./dist-DWs3-WOI.cjs");
11
- const require_PortalProductsApiProvider = require("./PortalProductsApiProvider-DaQUq6vq.cjs");
11
+ const require_PortalProductsApiProvider = require("./PortalProductsApiProvider-Ca1oeTtJ.cjs");
12
12
  let react = require("react");
13
13
  react = require_chunk.__toESM(react);
14
14
  let _tanstack_react_query = require("@tanstack/react-query");
@@ -510,9 +510,76 @@ function ShareProductCard({ product, mediaCount }) {
510
510
  });
511
511
  }
512
512
  //#endregion
513
+ //#region ../../shareables/ui/src/components/shared/ShareableListLayout.tsx
514
+ /**
515
+ * Grid column class shared by all shareable listing screens (products, media,
516
+ * playlists, files). Exposed so the list-content slot can reuse it directly.
517
+ */
518
+ const SHAREABLE_GRID_CLASS = "grid grid-cols-1 gap-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-4";
519
+ /**
520
+ * Standard page chrome for shareable listing screens. Provides:
521
+ *
522
+ * - Page-level padding (matching all existing listing screens).
523
+ * - Loading skeleton (filter bar + grid of 8 card placeholders).
524
+ * - Inline error rendering.
525
+ * - Empty state rendering.
526
+ * - Optional filter/footer/sentinel slots.
527
+ *
528
+ * Domain-specific UI (cards, rows, filter controls) is slotted in via
529
+ * `filters` and `children`. This component does NOT own data fetching or
530
+ * any state — callers pass in the derived flags.
531
+ */
532
+ function ShareableListLayout({ filters, children, isLoading, error, errorMessage = "Failed to load. Please try again.", isEmpty, emptyMessage, footer, loadingFilterShape = "search", sentinelRef }) {
533
+ if (isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
534
+ className: "space-y-6 px-4 py-4 md:px-10 md:py-6",
535
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
536
+ className: "flex items-center gap-3",
537
+ children: [
538
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-10 flex-1" }),
539
+ loadingFilterShape === "search-view" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-10 w-10" }),
540
+ loadingFilterShape === "search-action" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-10 w-24" })
541
+ ]
542
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
543
+ className: SHAREABLE_GRID_CLASS,
544
+ children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
545
+ className: "space-y-2",
546
+ children: [
547
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "aspect-square w-full rounded-lg" }),
548
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-4 w-3/4" }),
549
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-3 w-1/2" })
550
+ ]
551
+ }, i))
552
+ })]
553
+ });
554
+ if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
555
+ className: "flex flex-col items-center justify-center py-16",
556
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
557
+ className: "text-destructive text-sm",
558
+ children: errorMessage
559
+ })
560
+ });
561
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
562
+ className: "space-y-6 px-4 py-4 md:px-10 md:py-6",
563
+ children: [
564
+ filters,
565
+ isEmpty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
566
+ className: "flex flex-col items-center justify-center py-16",
567
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
568
+ className: "text-muted-foreground text-sm",
569
+ children: emptyMessage
570
+ })
571
+ }) : children,
572
+ footer,
573
+ sentinelRef && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
574
+ ref: sentinelRef,
575
+ className: "h-1"
576
+ })
577
+ ]
578
+ });
579
+ }
580
+ //#endregion
513
581
  //#region ../../shareables/ui/src/components/screens/ProductsScreen.tsx
514
582
  const PAGE_SIZE$4 = 24;
515
- const GRID_CLASS$3 = "grid grid-cols-1 gap-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-4";
516
583
  const SORT_OPTIONS$1 = [
517
584
  {
518
585
  label: "Newest",
@@ -632,73 +699,42 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
632
699
  media_count: void 0
633
700
  })) ?? [];
634
701
  }, [data, usePortal]);
635
- if (isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
636
- className: "space-y-6 px-4 py-4 md:px-10 md:py-6",
637
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
638
- className: "flex items-center gap-3",
639
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-10 flex-1" })
640
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
641
- className: GRID_CLASS$3,
642
- children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
643
- className: "space-y-2",
644
- children: [
645
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "aspect-square w-full rounded-lg" }),
646
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-4 w-3/4" }),
647
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-3 w-1/2" })
648
- ]
649
- }, i))
650
- })]
651
- });
652
- if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
653
- className: "flex flex-col items-center justify-center py-16",
654
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
655
- className: "text-destructive text-sm",
656
- children: "Failed to load products. Please try again."
657
- })
658
- });
659
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
660
- className: "space-y-6 px-4 py-4 md:px-10 md:py-6",
661
- children: [
662
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
663
- className: "flex justify-end",
664
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
665
- className: "w-full max-w-sm",
666
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SearchSort.SearchSort, {
667
- searchValue: searchTerm,
668
- onSearchChange: setSearchTerm,
669
- placeholder: "Search products...",
670
- sortOptions: SORT_OPTIONS$1,
671
- sortValue,
672
- onSortChange: setSortValue
673
- })
674
- })
675
- }),
676
- products.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
677
- className: "flex flex-col items-center justify-center py-16",
678
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
679
- className: "text-muted-foreground text-sm",
680
- children: debouncedSearch ? "No products match your search." : "No products available."
702
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareableListLayout, {
703
+ isLoading,
704
+ error,
705
+ errorMessage: "Failed to load products. Please try again.",
706
+ isEmpty: products.length === 0,
707
+ emptyMessage: debouncedSearch ? "No products match your search." : "No products available.",
708
+ filters: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
709
+ className: "flex justify-end",
710
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
711
+ className: "w-full max-w-sm",
712
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SearchSort.SearchSort, {
713
+ searchValue: searchTerm,
714
+ onSearchChange: setSearchTerm,
715
+ placeholder: "Search products...",
716
+ sortOptions: SORT_OPTIONS$1,
717
+ sortValue,
718
+ onSortChange: setSortValue
681
719
  })
682
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
683
- className: GRID_CLASS$3,
684
- children: products.map((product) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareProductCard, {
685
- product: {
686
- id: product.id,
687
- title: product.title,
688
- image_url: product.image_url
689
- },
690
- mediaCount: product.media_count
691
- }, product.id))
692
- }),
693
- isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
694
- className: "flex justify-center py-4",
695
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
696
- }),
697
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
698
- ref: observerTarget,
699
- className: "h-1"
700
720
  })
701
- ]
721
+ }),
722
+ footer: isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
723
+ className: "flex justify-center py-4",
724
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
725
+ }),
726
+ sentinelRef: observerTarget,
727
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
728
+ className: SHAREABLE_GRID_CLASS,
729
+ children: products.map((product) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareProductCard, {
730
+ product: {
731
+ id: product.id,
732
+ title: product.title,
733
+ image_url: product.image_url
734
+ },
735
+ mediaCount: product.media_count
736
+ }, product.id))
737
+ })
702
738
  });
703
739
  }
704
740
  //#endregion
@@ -1217,13 +1253,85 @@ function MarketingAssetsGrid({ isLoading, error, activeTab, onTabChange, mediaIt
1217
1253
  }
1218
1254
  var MarketingAssetsGrid_default = react.default.memo(MarketingAssetsGrid);
1219
1255
  //#endregion
1256
+ //#region ../../shareables/ui/src/components/shared/ShareableDetailLayout.tsx
1257
+ /**
1258
+ * Standard page chrome for shareable detail screens (product/media/playlist).
1259
+ * Provides the two-column hero + details layout, loading/not-found states,
1260
+ * title, and the read-more description block.
1261
+ *
1262
+ * Domain-specific chrome (breadcrumbs, header actions) is still wired up by
1263
+ * each screen via the shell header context — this layout purposefully does
1264
+ * NOT touch the shell header. Dialogs (delete confirmation, etc.) belong to
1265
+ * the caller and should be rendered as siblings to this component.
1266
+ */
1267
+ function ShareableDetailLayout({ isLoading, notFound, notFoundMessage = "Not found or failed to load.", title, description, image, actions, meta, children, containerHeightClass = "md:h-[calc(100vh-140px)]" }) {
1268
+ if (isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1269
+ className: "flex items-center justify-center py-16",
1270
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Spinner, { className: "size-8" })
1271
+ });
1272
+ if (notFound) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1273
+ className: "flex flex-col items-center justify-center py-16",
1274
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1275
+ className: "text-destructive text-sm",
1276
+ children: notFoundMessage
1277
+ })
1278
+ });
1279
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1280
+ className: "flex flex-col gap-4 px-4 py-4 md:px-10 md:py-6",
1281
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1282
+ className: `mx-auto flex w-full max-w-[480px] flex-col gap-6 md:max-w-none md:flex-row ${containerHeightClass}`,
1283
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1284
+ className: "aspect-square w-full md:aspect-auto md:h-full",
1285
+ children: image
1286
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1287
+ className: "flex w-full flex-col md:overflow-y-auto",
1288
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1289
+ className: "space-y-4",
1290
+ children: [
1291
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
1292
+ className: "text-foreground text-[26px] leading-[1.2] font-semibold break-words",
1293
+ children: title
1294
+ }),
1295
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareableDescription, { html: description }),
1296
+ meta,
1297
+ actions
1298
+ ]
1299
+ }), children]
1300
+ })]
1301
+ })
1302
+ });
1303
+ }
1304
+ /**
1305
+ * Read-more description block. Extracted so detail screens don't have to
1306
+ * reimplement the identical toggle + line-clamp logic. Pass the raw HTML
1307
+ * (e.g. mediaItem.description.body) — tags are stripped internally.
1308
+ */
1309
+ function ShareableDescription({ html, threshold = 150 }) {
1310
+ const [isExpanded, setIsExpanded] = (0, react.useState)(false);
1311
+ const stripped = stripTags(html ?? "");
1312
+ if (!stripped) return null;
1313
+ const shouldShowReadMore = stripped.length > threshold;
1314
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1315
+ className: "text-foreground/70 text-sm leading-relaxed",
1316
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1317
+ className: !isExpanded && shouldShowReadMore ? "line-clamp-3" : "",
1318
+ children: stripped
1319
+ }), shouldShowReadMore && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Button, {
1320
+ onClick: () => setIsExpanded((prev) => !prev),
1321
+ variant: "ghost",
1322
+ size: "sm",
1323
+ className: "text-foreground hover:text-foreground/80 mt-1 h-auto p-0 text-xs font-normal underline",
1324
+ children: isExpanded ? "Read less" : "Read more"
1325
+ })]
1326
+ });
1327
+ }
1328
+ //#endregion
1220
1329
  //#region ../../shareables/ui/src/components/screens/ProductDetailScreen.tsx
1221
1330
  function ProductDetailScreen({ productId, countryCode, fetchProduct: fetchPortalProduct, onNavigate, onBack }) {
1222
1331
  const api = useShareablesApi();
1223
1332
  const client = useShareablesClient();
1224
1333
  const { navigate } = useShareablesUI();
1225
1334
  const [activeTab, setActiveTab] = (0, react.useState)("All");
1226
- const [isDescriptionExpanded, setIsDescriptionExpanded] = (0, react.useState)(false);
1227
1335
  const { data: portalProductResponse, isLoading: isLoadingPortalProduct } = (0, _tanstack_react_query.useQuery)({
1228
1336
  queryKey: [
1229
1337
  "portal-products",
@@ -1279,8 +1387,6 @@ function ProductDetailScreen({ productId, countryCode, fetchProduct: fetchPortal
1279
1387
  onBack,
1280
1388
  navigate
1281
1389
  ]));
1282
- const strippedDescription = stripTags(displayDescription);
1283
- const shouldShowReadMore = strippedDescription.length > 150;
1284
1390
  const displayPrice = (() => {
1285
1391
  if (legacyProduct?.display_price) return legacyProduct.display_price;
1286
1392
  const price = portalProduct?.price ?? legacyProduct?.price;
@@ -1313,114 +1419,74 @@ function ProductDetailScreen({ productId, countryCode, fetchProduct: fetchPortal
1313
1419
  const handleMediaItemClick = (0, react.useCallback)((mediaItem) => {
1314
1420
  onNavigate?.("media", String(mediaItem.id));
1315
1421
  }, [onNavigate]);
1316
- if (isLoadingProduct) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1317
- className: "flex items-center justify-center py-16",
1318
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Spinner, { className: "size-8" })
1319
- });
1320
- if (!product) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1321
- className: "flex flex-col items-center justify-center py-16",
1322
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1323
- className: "text-destructive text-sm",
1324
- children: "Product not found or failed to load."
1325
- })
1326
- });
1327
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1328
- className: "flex flex-col gap-4 px-4 py-4 md:px-10 md:py-6",
1329
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1330
- className: "mx-auto flex w-full max-w-480 flex-col gap-6 md:h-[calc(100vh-140px)] md:flex-row",
1331
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1332
- className: "aspect-square w-full md:aspect-auto md:h-full",
1333
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1334
- className: "relative h-full overflow-hidden rounded-2xl",
1335
- children: !productImage ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1336
- className: "bg-muted flex h-full w-full flex-col items-center justify-center rounded-2xl",
1337
- children: [
1338
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1339
- className: "mb-2 text-gray-400",
1340
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Image, { className: "mx-auto h-12 w-12" })
1341
- }),
1342
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1343
- className: "mb-1 text-sm text-gray-500",
1344
- children: "No Product Image"
1345
- }),
1346
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1347
- className: "text-xs text-gray-400",
1348
- children: "This product does not have any associated media"
1349
- })
1350
- ]
1351
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SharePageImageDisplay, {
1352
- displayImage: productImage,
1353
- displayTitle,
1354
- isVideo: false,
1355
- badgeLabel: "Product"
1356
- })
1357
- })
1358
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1359
- className: "flex w-full flex-col md:overflow-y-auto",
1422
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ShareableDetailLayout, {
1423
+ isLoading: isLoadingProduct,
1424
+ notFound: !product,
1425
+ notFoundMessage: "Product not found or failed to load.",
1426
+ title: displayTitle,
1427
+ description: displayDescription,
1428
+ image: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1429
+ className: "relative h-full overflow-hidden rounded-2xl",
1430
+ children: !productImage ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1431
+ className: "bg-muted flex h-full w-full flex-col items-center justify-center rounded-2xl",
1360
1432
  children: [
1361
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1362
- className: "space-y-4",
1363
- children: [
1364
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
1365
- className: "text-foreground text-[26px] leading-[1.2] font-semibold",
1366
- children: displayTitle
1367
- }),
1368
- strippedDescription && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1369
- className: "text-foreground/70 text-sm leading-relaxed",
1370
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1371
- className: !isDescriptionExpanded && shouldShowReadMore ? "line-clamp-3" : "",
1372
- children: strippedDescription
1373
- }), shouldShowReadMore && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Button, {
1374
- onClick: () => setIsDescriptionExpanded(!isDescriptionExpanded),
1375
- variant: "ghost",
1376
- size: "sm",
1377
- className: "text-foreground hover:text-foreground/80 mt-1 h-auto p-0 text-xs font-normal underline",
1378
- children: isDescriptionExpanded ? "Read less" : "Read more"
1379
- })]
1380
- }),
1381
- displayPrice && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1382
- className: "text-foreground flex items-center gap-2 text-sm",
1383
- children: [
1384
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1385
- className: "font-semibold",
1386
- children: "Price"
1387
- }),
1388
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "|" }),
1389
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1390
- className: "font-semibold",
1391
- children: displayPrice
1392
- })
1393
- ]
1394
- }),
1395
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssetActions, {
1396
- downloadUrl: productImage || null,
1397
- displayTitle,
1398
- shareLink: shareLinkError ? null : shareLink || null,
1399
- shareLinkLoading,
1400
- isVideo: false,
1401
- relateableId: Number(productId),
1402
- relateableType: "Product"
1403
- })
1404
- ]
1433
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1434
+ className: "mb-2 text-gray-400",
1435
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Image, { className: "mx-auto h-12 w-12" })
1405
1436
  }),
1406
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Separator, { className: "border-foreground my-4" }),
1407
1437
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1408
- className: "hide-scrollbar bg-background h-full overflow-y-auto rounded-lg",
1409
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketingAssetsGrid_default, {
1410
- isLoading: isLoadingMedia,
1411
- error: mediaError,
1412
- activeTab,
1413
- onTabChange: setActiveTab,
1414
- mediaItems: filteredMediaItems,
1415
- onMediaItemClick: handleMediaItemClick,
1416
- showEmptyState: !hasMedia,
1417
- activeMainTab: "Related Sharables",
1418
- relateable_type: "Product"
1419
- })
1438
+ className: "mb-1 text-sm text-gray-500",
1439
+ children: "No Product Image"
1440
+ }),
1441
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1442
+ className: "text-xs text-gray-400",
1443
+ children: "This product does not have any associated media"
1420
1444
  })
1421
1445
  ]
1422
- })]
1423
- })
1446
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SharePageImageDisplay, {
1447
+ displayImage: productImage,
1448
+ displayTitle,
1449
+ isVideo: false,
1450
+ badgeLabel: "Product"
1451
+ })
1452
+ }),
1453
+ meta: displayPrice ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1454
+ className: "text-foreground flex items-center gap-2 text-sm",
1455
+ children: [
1456
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1457
+ className: "font-semibold",
1458
+ children: "Price"
1459
+ }),
1460
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "|" }),
1461
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1462
+ className: "font-semibold",
1463
+ children: displayPrice
1464
+ })
1465
+ ]
1466
+ }) : null,
1467
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssetActions, {
1468
+ downloadUrl: productImage || null,
1469
+ displayTitle,
1470
+ shareLink: shareLinkError ? null : shareLink || null,
1471
+ shareLinkLoading,
1472
+ isVideo: false,
1473
+ relateableId: Number(productId),
1474
+ relateableType: "Product"
1475
+ }),
1476
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Separator, { className: "border-foreground my-4" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1477
+ className: "hide-scrollbar bg-background h-full overflow-y-auto rounded-lg",
1478
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketingAssetsGrid_default, {
1479
+ isLoading: isLoadingMedia,
1480
+ error: mediaError,
1481
+ activeTab,
1482
+ onTabChange: setActiveTab,
1483
+ mediaItems: filteredMediaItems,
1484
+ onMediaItemClick: handleMediaItemClick,
1485
+ showEmptyState: !hasMedia,
1486
+ activeMainTab: "Related Sharables",
1487
+ relateable_type: "Product"
1488
+ })
1489
+ })]
1424
1490
  });
1425
1491
  }
1426
1492
  //#endregion
@@ -1455,7 +1521,6 @@ function OwnerFilterTabs({ value, onValueChange, myLabel = "Mine" }) {
1455
1521
  //#endregion
1456
1522
  //#region ../../shareables/ui/src/components/screens/MediaListingScreen.tsx
1457
1523
  const PAGE_SIZE$3 = 24;
1458
- const GRID_CLASS$2 = "grid grid-cols-1 gap-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-4";
1459
1524
  function getMediaKindLabel(kind) {
1460
1525
  switch (kind) {
1461
1526
  case "video": return "Video";
@@ -1575,229 +1640,203 @@ function MediaListingScreen({ onNavigate }) {
1575
1640
  const allItems = data?.media ?? [];
1576
1641
  const ownerFiltered = ownerFilter === "all" ? allItems : ownerFilter === "my" ? allItems.filter((item) => item.owner_type === "user") : allItems.filter((item) => item.owner_type === "company");
1577
1642
  const filteredItems = kindFilter === "all" ? ownerFiltered : ownerFiltered.filter((item) => kindBucket(item.kind) === kindFilter);
1578
- if (isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1579
- className: "space-y-6 px-4 py-4 md:px-10 md:py-6",
1580
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1581
- className: "flex items-center gap-3",
1582
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-10 flex-1" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-10 w-10" })]
1583
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1584
- className: GRID_CLASS$2,
1585
- children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1586
- className: "space-y-2",
1587
- children: [
1588
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "aspect-square w-full rounded-lg" }),
1589
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-4 w-3/4" }),
1590
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-3 w-1/2" })
1591
- ]
1592
- }, i))
1593
- })]
1594
- });
1595
- if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1596
- className: "flex flex-col items-center justify-center py-16",
1597
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1598
- className: "text-destructive text-sm",
1599
- children: "Failed to load media. Please try again."
1600
- })
1601
- });
1602
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1603
- className: "space-y-6 px-4 py-4 md:px-10 md:py-6",
1604
- children: [
1605
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1606
- className: "flex items-center gap-3",
1607
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OwnerFilterTabs, {
1608
- value: ownerFilter,
1609
- onValueChange: setOwnerFilter,
1610
- myLabel: "My Media"
1611
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1612
- className: "ml-auto flex items-center gap-2",
1613
- children: [
1614
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1615
- className: "w-full max-w-sm",
1616
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SearchSort.SearchSort, {
1617
- searchValue: searchTerm,
1618
- onSearchChange: setSearchTerm,
1619
- placeholder: "Search media...",
1620
- sortOptions: [{
1621
- label: "Name (A-Z)",
1622
- value: "title_asc"
1623
- }, {
1624
- label: "Name (Z-A)",
1625
- value: "title_desc"
1626
- }],
1627
- sortValue,
1628
- onSortChange: setSortValue
1629
- })
1630
- }),
1631
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.DropdownMenuTrigger, {
1632
- asChild: true,
1633
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.Button, {
1634
- variant: "outline",
1635
- "aria-label": `Filter: ${kindFilter === "all" ? "All types" : kindFilter}`,
1636
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Filter, { className: "size-4" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1637
- className: "hidden sm:inline",
1638
- children: kindFilter === "all" ? "All types" : kindFilter === "image" ? "Images" : kindFilter === "video" ? "Videos" : "PDFs"
1639
- })]
1640
- })
1641
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.DropdownMenuContent, {
1642
- align: "end",
1643
- children: [
1644
- {
1645
- label: "All types",
1646
- value: "all"
1647
- },
1648
- {
1649
- label: "Images",
1650
- value: "image"
1651
- },
1652
- {
1653
- label: "Videos",
1654
- value: "video"
1655
- },
1656
- {
1657
- label: "PDFs",
1658
- value: "pdf"
1659
- }
1660
- ].map((opt) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.DropdownMenuItem, {
1661
- onClick: () => setKindFilter(opt.value),
1662
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1663
- className: "flex-1",
1664
- children: opt.label
1665
- }), kindFilter === opt.value && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "text-muted-foreground size-4" })]
1666
- }, opt.value))
1667
- })] }),
1668
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1669
- className: "border-input bg-muted flex items-center rounded-lg border p-0.5",
1670
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1671
- type: "button",
1672
- onClick: () => setViewMode("list"),
1673
- className: `flex h-8 w-8 items-center justify-center rounded-md transition-all ${viewMode === "list" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
1674
- title: "List view",
1675
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.List, { className: "h-4 w-4" })
1676
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1677
- type: "button",
1678
- onClick: () => setViewMode("grid"),
1679
- className: `flex h-8 w-8 items-center justify-center rounded-md transition-all ${viewMode === "grid" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
1680
- title: "Grid view",
1681
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.LayoutGrid, { className: "h-4 w-4" })
1682
- })]
1683
- })
1684
- ]
1685
- })]
1686
- }),
1687
- filteredItems.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1688
- className: "flex flex-col items-center justify-center py-16",
1689
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1690
- className: "text-muted-foreground text-sm",
1691
- children: getFilteredEmptyMessage({
1692
- searchTerm: debouncedSearch,
1693
- ownerFilter,
1694
- hasOtherFilters: kindFilter !== "all",
1695
- entityName: "media",
1696
- defaultMessage: "No media available."
1643
+ const filterBar = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1644
+ className: "flex items-center gap-3",
1645
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OwnerFilterTabs, {
1646
+ value: ownerFilter,
1647
+ onValueChange: setOwnerFilter,
1648
+ myLabel: "My Media"
1649
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1650
+ className: "ml-auto flex items-center gap-2",
1651
+ children: [
1652
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1653
+ className: "w-full max-w-sm",
1654
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SearchSort.SearchSort, {
1655
+ searchValue: searchTerm,
1656
+ onSearchChange: setSearchTerm,
1657
+ placeholder: "Search media...",
1658
+ sortOptions: [{
1659
+ label: "Name (A-Z)",
1660
+ value: "title_asc"
1661
+ }, {
1662
+ label: "Name (Z-A)",
1663
+ value: "title_desc"
1664
+ }],
1665
+ sortValue,
1666
+ onSortChange: setSortValue
1697
1667
  })
1698
- })
1699
- }) : viewMode === "grid" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1700
- className: GRID_CLASS$2,
1701
- children: filteredItems.map((item) => {
1702
- const canEdit = !readOnly && item.owner_type === "user";
1703
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1704
- className: "relative",
1705
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareItemCard, {
1706
- title: item.title ?? "",
1707
- imageUrl: item.image_url,
1708
- href: `media/${item.id}`,
1709
- badge: { text: getMediaKindLabel(item.kind) },
1710
- isVideo: item.kind === "video",
1711
- subtitle: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1712
- className: "flex items-center gap-1.5",
1713
- children: [getMediaKindLabel(item.kind), item.owner_type === "user" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Badge, {
1714
- variant: "secondary",
1715
- className: "px-1.5 py-0 text-[10px] leading-4",
1716
- children: "My Media"
1717
- })]
1718
- })
1719
- }), canEdit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1720
- className: "absolute top-2 right-2",
1721
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MediaRowActionsMenu, { onDelete: () => setPendingDeleteId(item.id) })
1668
+ }),
1669
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.DropdownMenuTrigger, {
1670
+ asChild: true,
1671
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.Button, {
1672
+ variant: "outline",
1673
+ "aria-label": `Filter: ${kindFilter === "all" ? "All types" : kindFilter}`,
1674
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Filter, { className: "size-4" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1675
+ className: "hidden sm:inline",
1676
+ children: kindFilter === "all" ? "All types" : kindFilter === "image" ? "Images" : kindFilter === "video" ? "Videos" : "PDFs"
1722
1677
  })]
1723
- }, item.id);
1724
- })
1725
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1726
- className: "divide-border divide-y rounded-lg border",
1727
- children: filteredItems.map((item) => {
1728
- const canEdit = !readOnly && item.owner_type === "user";
1729
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1730
- className: "hover:bg-muted flex items-center gap-4 px-4 py-3 transition-colors",
1731
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1732
- type: "button",
1733
- onClick: () => navigate(`media/${item.id}`),
1734
- className: "flex min-w-0 flex-1 items-center gap-4 text-left",
1735
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1736
- className: "bg-muted h-12 w-12 shrink-0 overflow-hidden rounded-md",
1737
- children: item.image_url ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1738
- src: item.image_url,
1739
- alt: item.title ?? "",
1740
- className: "h-full w-full object-cover"
1741
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "bg-muted h-full w-full" })
1742
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1743
- className: "min-w-0 flex-1",
1744
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1745
- className: "text-foreground truncate text-sm font-medium",
1746
- children: item.title ?? "Untitled"
1747
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1748
- className: "text-muted-foreground flex items-center gap-1.5 text-xs",
1749
- children: [getMediaKindLabel(item.kind), item.owner_type === "user" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Badge, {
1750
- variant: "secondary",
1751
- className: "px-1.5 py-0 text-[10px] leading-4",
1752
- children: "My Media"
1753
- })]
1754
- })]
1755
- })]
1756
- }), canEdit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MediaRowActionsMenu, { onDelete: () => setPendingDeleteId(item.id) })]
1757
- }, item.id);
1678
+ })
1679
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.DropdownMenuContent, {
1680
+ align: "end",
1681
+ children: [
1682
+ {
1683
+ label: "All types",
1684
+ value: "all"
1685
+ },
1686
+ {
1687
+ label: "Images",
1688
+ value: "image"
1689
+ },
1690
+ {
1691
+ label: "Videos",
1692
+ value: "video"
1693
+ },
1694
+ {
1695
+ label: "PDFs",
1696
+ value: "pdf"
1697
+ }
1698
+ ].map((opt) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.DropdownMenuItem, {
1699
+ onClick: () => setKindFilter(opt.value),
1700
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1701
+ className: "flex-1",
1702
+ children: opt.label
1703
+ }), kindFilter === opt.value && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "text-muted-foreground size-4" })]
1704
+ }, opt.value))
1705
+ })] }),
1706
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1707
+ className: "border-input bg-muted flex items-center rounded-lg border p-0.5",
1708
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1709
+ type: "button",
1710
+ onClick: () => setViewMode("list"),
1711
+ className: `flex h-8 w-8 items-center justify-center rounded-md transition-all ${viewMode === "list" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
1712
+ title: "List view",
1713
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.List, { className: "h-4 w-4" })
1714
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1715
+ type: "button",
1716
+ onClick: () => setViewMode("grid"),
1717
+ className: `flex h-8 w-8 items-center justify-center rounded-md transition-all ${viewMode === "grid" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
1718
+ title: "Grid view",
1719
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.LayoutGrid, { className: "h-4 w-4" })
1720
+ })]
1758
1721
  })
1722
+ ]
1723
+ })]
1724
+ });
1725
+ const paginationFooter = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1726
+ className: "flex items-center justify-center gap-4 pt-4",
1727
+ children: [
1728
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.Button, {
1729
+ variant: "outline",
1730
+ size: "sm",
1731
+ onClick: () => setPage((p) => p - 1),
1732
+ disabled: page === 1 || isFetching,
1733
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, { className: "h-4 w-4" }), "Previous"]
1759
1734
  }),
1760
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialog, {
1761
- open: pendingDeleteId !== null,
1762
- onOpenChange: (open) => !open && setPendingDeleteId(null),
1763
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogTitle, { children: "Delete this media?" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogDescription, { children: "This removes the item from your media library. Shared links that point to it will stop working." })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogCancel, {
1764
- disabled: isDeleting,
1765
- children: "Cancel"
1766
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogAction, {
1767
- onClick: (e) => {
1768
- e.preventDefault();
1769
- confirmDelete();
1770
- },
1771
- disabled: isDeleting,
1772
- className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
1773
- children: isDeleting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Spinner, { className: "size-4" }) : "Delete"
1774
- })] })] })
1735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1736
+ className: "text-muted-foreground text-sm",
1737
+ children: ["Page ", page]
1775
1738
  }),
1776
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1777
- className: "flex items-center justify-center gap-4 pt-4",
1778
- children: [
1779
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.Button, {
1780
- variant: "outline",
1781
- size: "sm",
1782
- onClick: () => setPage((p) => p - 1),
1783
- disabled: page === 1 || isFetching,
1784
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, { className: "h-4 w-4" }), "Previous"]
1785
- }),
1786
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1787
- className: "text-muted-foreground text-sm",
1788
- children: ["Page ", page]
1789
- }),
1790
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.Button, {
1791
- variant: "outline",
1792
- size: "sm",
1793
- onClick: () => setPage((p) => p + 1),
1794
- disabled: !hasNextPage || isFetching,
1795
- children: ["Next", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, { className: "h-4 w-4" })]
1796
- })
1797
- ]
1739
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.Button, {
1740
+ variant: "outline",
1741
+ size: "sm",
1742
+ onClick: () => setPage((p) => p + 1),
1743
+ disabled: !hasNextPage || isFetching,
1744
+ children: ["Next", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, { className: "h-4 w-4" })]
1745
+ })
1746
+ ]
1747
+ });
1748
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareableListLayout, {
1749
+ isLoading,
1750
+ error,
1751
+ errorMessage: "Failed to load media. Please try again.",
1752
+ isEmpty: filteredItems.length === 0,
1753
+ emptyMessage: getFilteredEmptyMessage({
1754
+ searchTerm: debouncedSearch,
1755
+ ownerFilter,
1756
+ hasOtherFilters: kindFilter !== "all",
1757
+ entityName: "media",
1758
+ defaultMessage: "No media available."
1759
+ }),
1760
+ filters: filterBar,
1761
+ footer: paginationFooter,
1762
+ loadingFilterShape: "search-view",
1763
+ children: viewMode === "grid" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1764
+ className: SHAREABLE_GRID_CLASS,
1765
+ children: filteredItems.map((item) => {
1766
+ const canEdit = !readOnly && item.owner_type === "user";
1767
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1768
+ className: "relative",
1769
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareItemCard, {
1770
+ title: item.title ?? "",
1771
+ imageUrl: item.image_url,
1772
+ href: `media/${item.id}`,
1773
+ badge: { text: getMediaKindLabel(item.kind) },
1774
+ isVideo: item.kind === "video",
1775
+ subtitle: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1776
+ className: "flex items-center gap-1.5",
1777
+ children: [getMediaKindLabel(item.kind), item.owner_type === "user" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Badge, {
1778
+ variant: "secondary",
1779
+ className: "px-1.5 py-0 text-[10px] leading-4",
1780
+ children: "My Media"
1781
+ })]
1782
+ })
1783
+ }), canEdit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1784
+ className: "absolute top-2 right-2",
1785
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MediaRowActionsMenu, { onDelete: () => setPendingDeleteId(item.id) })
1786
+ })]
1787
+ }, item.id);
1788
+ })
1789
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1790
+ className: "divide-border divide-y rounded-lg border",
1791
+ children: filteredItems.map((item) => {
1792
+ const canEdit = !readOnly && item.owner_type === "user";
1793
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1794
+ className: "hover:bg-muted flex items-center gap-4 px-4 py-3 transition-colors",
1795
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1796
+ type: "button",
1797
+ onClick: () => navigate(`media/${item.id}`),
1798
+ className: "flex min-w-0 flex-1 items-center gap-4 text-left",
1799
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1800
+ className: "bg-muted h-12 w-12 shrink-0 overflow-hidden rounded-md",
1801
+ children: item.image_url ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1802
+ src: item.image_url,
1803
+ alt: item.title ?? "",
1804
+ className: "h-full w-full object-cover"
1805
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "bg-muted h-full w-full" })
1806
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1807
+ className: "min-w-0 flex-1",
1808
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1809
+ className: "text-foreground truncate text-sm font-medium",
1810
+ children: item.title ?? "Untitled"
1811
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1812
+ className: "text-muted-foreground flex items-center gap-1.5 text-xs",
1813
+ children: [getMediaKindLabel(item.kind), item.owner_type === "user" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Badge, {
1814
+ variant: "secondary",
1815
+ className: "px-1.5 py-0 text-[10px] leading-4",
1816
+ children: "My Media"
1817
+ })]
1818
+ })]
1819
+ })]
1820
+ }), canEdit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MediaRowActionsMenu, { onDelete: () => setPendingDeleteId(item.id) })]
1821
+ }, item.id);
1798
1822
  })
1799
- ]
1800
- });
1823
+ })
1824
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialog, {
1825
+ open: pendingDeleteId !== null,
1826
+ onOpenChange: (open) => !open && setPendingDeleteId(null),
1827
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogTitle, { children: "Delete this media?" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogDescription, { children: "This removes the item from your media library. Shared links that point to it will stop working." })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogCancel, {
1828
+ disabled: isDeleting,
1829
+ children: "Cancel"
1830
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogAction, {
1831
+ onClick: (e) => {
1832
+ e.preventDefault();
1833
+ confirmDelete();
1834
+ },
1835
+ disabled: isDeleting,
1836
+ className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
1837
+ children: isDeleting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Spinner, { className: "size-4" }) : "Delete"
1838
+ })] })] })
1839
+ })] });
1801
1840
  }
1802
1841
  //#endregion
1803
1842
  //#region ../../shareables/ui/src/components/screens/MediaDetailScreen.tsx
@@ -1813,7 +1852,6 @@ function MediaDetailScreen({ mediaId, onNavigate: _onNavigate, onBack }) {
1813
1852
  const api = useShareablesApi();
1814
1853
  const repContext = useRepContext();
1815
1854
  const { navigate, readOnly } = useShareablesUI();
1816
- const [isDescriptionExpanded, setIsDescriptionExpanded] = (0, react.useState)(false);
1817
1855
  const { data: mediaResponse, isLoading } = (0, _tanstack_react_query.useQuery)({
1818
1856
  queryKey: shareablesKeys.media.detail(Number(mediaId), repContext),
1819
1857
  queryFn: () => api.media.getMediaById(Number(mediaId))
@@ -1862,75 +1900,38 @@ function MediaDetailScreen({ mediaId, onNavigate: _onNavigate, onBack }) {
1862
1900
  mediaId
1863
1901
  ]));
1864
1902
  const badgeLabel = getBadgeLabel(mediaItem?.kind ?? null);
1865
- const strippedDescription = stripTags(mediaItem?.description?.body || mediaItem?.stripped || "");
1866
- const shouldShowReadMore = strippedDescription.length > 150;
1903
+ const rawDescription = mediaItem?.description?.body || mediaItem?.stripped || "";
1867
1904
  const downloadUrl = isVideo ? displayVideo : mediaItem?.pdf_url || displayImage;
1868
- if (isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1869
- className: "flex items-center justify-center py-16",
1870
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Spinner, { className: "size-8" })
1871
- });
1872
- if (!mediaItem) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1873
- className: "flex flex-col items-center justify-center py-16",
1874
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1875
- className: "text-destructive text-sm",
1876
- children: "Media not found or failed to load."
1877
- })
1878
- });
1879
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1880
- className: "flex flex-col gap-4 px-4 py-4 md:px-10 md:py-6",
1881
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1882
- className: "mx-auto flex w-full max-w-[480px] flex-col gap-6 md:h-[calc(95vh-140px)] md:max-w-none md:flex-row",
1883
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1884
- className: "aspect-square w-full md:aspect-auto md:h-full",
1885
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1886
- className: "relative h-full overflow-hidden",
1887
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SharePageImageDisplay, {
1888
- displayImage,
1889
- displayTitle,
1890
- displayVideo: isVideo ? displayVideo : void 0,
1891
- isVideo,
1892
- badgeLabel,
1893
- rounded: true,
1894
- showBadge: false
1895
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1896
- className: "bg-background/80 text-foreground absolute top-3 right-3 z-0 inline-flex h-8 items-center rounded-full px-3 text-xs font-medium shadow-md backdrop-blur-sm",
1897
- children: badgeLabel
1898
- })]
1899
- })
1900
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1901
- className: "flex w-full flex-col md:overflow-y-auto",
1902
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1903
- className: "space-y-4",
1904
- children: [
1905
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
1906
- className: "text-foreground text-[26px] leading-[1.2] font-semibold break-words",
1907
- children: displayTitle
1908
- }),
1909
- strippedDescription && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1910
- className: "text-foreground/70 text-sm leading-relaxed",
1911
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1912
- className: !isDescriptionExpanded && shouldShowReadMore ? "line-clamp-3" : "",
1913
- children: strippedDescription
1914
- }), shouldShowReadMore && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Button, {
1915
- onClick: () => setIsDescriptionExpanded(!isDescriptionExpanded),
1916
- variant: "ghost",
1917
- size: "sm",
1918
- className: "text-foreground hover:text-foreground/80 mt-1 h-auto p-0 text-xs font-normal underline",
1919
- children: isDescriptionExpanded ? "Read less" : "Read more"
1920
- })]
1921
- }),
1922
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssetActions, {
1923
- downloadUrl: downloadUrl || null,
1924
- displayTitle,
1925
- shareLink: shareLinkError ? null : shareLink || null,
1926
- shareLinkLoading,
1927
- isVideo,
1928
- relateableId: Number(mediaId),
1929
- relateableType: "Medium"
1930
- })
1931
- ]
1932
- })
1905
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareableDetailLayout, {
1906
+ isLoading,
1907
+ notFound: !mediaItem,
1908
+ notFoundMessage: "Media not found or failed to load.",
1909
+ title: displayTitle,
1910
+ description: rawDescription,
1911
+ containerHeightClass: "md:h-[calc(95vh-140px)]",
1912
+ image: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1913
+ className: "relative h-full overflow-hidden",
1914
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SharePageImageDisplay, {
1915
+ displayImage,
1916
+ displayTitle,
1917
+ displayVideo: isVideo ? displayVideo : void 0,
1918
+ isVideo,
1919
+ badgeLabel,
1920
+ rounded: true,
1921
+ showBadge: false
1922
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1923
+ className: "bg-background/80 text-foreground absolute top-3 right-3 z-0 inline-flex h-8 items-center rounded-full px-3 text-xs font-medium shadow-md backdrop-blur-sm",
1924
+ children: badgeLabel
1933
1925
  })]
1926
+ }),
1927
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssetActions, {
1928
+ downloadUrl: downloadUrl || null,
1929
+ displayTitle,
1930
+ shareLink: shareLinkError ? null : shareLink || null,
1931
+ shareLinkLoading,
1932
+ isVideo,
1933
+ relateableId: Number(mediaId),
1934
+ relateableType: "Medium"
1934
1935
  })
1935
1936
  });
1936
1937
  }
@@ -8917,7 +8918,7 @@ function BulkSelectionBar({ selectedCount, totalCount, onSelectAll, onClearSelec
8917
8918
  //#endregion
8918
8919
  //#region ../../shareables/ui/src/components/screens/PlaylistsListingScreen.tsx
8919
8920
  const PAGE_SIZE$1 = 12;
8920
- const GRID_CLASS$1 = "grid grid-cols-1 gap-8 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4";
8921
+ const GRID_CLASS = "grid grid-cols-1 gap-8 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4";
8921
8922
  function PlaylistsListingScreen(_props) {
8922
8923
  const api = useShareablesApi();
8923
8924
  const { navigate, showToast, user, onToggleFavorite, onDeletePlaylist, readOnly } = useShareablesUI();
@@ -9096,129 +9097,108 @@ function PlaylistsListingScreen(_props) {
9096
9097
  const confirmDelete = (0, react.useCallback)(() => {
9097
9098
  if (pendingDeleteId != null) deletePlaylist(pendingDeleteId);
9098
9099
  }, [pendingDeleteId, deletePlaylist]);
9099
- if (isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9100
- className: "space-y-6 px-4 py-4 md:px-10 md:py-6",
9101
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9102
- className: "flex items-center gap-3",
9103
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-10 flex-1" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-10 w-24" })]
9100
+ const filterBar = hasSelection ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BulkSelectionBar, {
9101
+ selectedCount: selectedIds.size,
9102
+ totalCount: filteredPlaylists.length,
9103
+ onSelectAll: handleSelectAll,
9104
+ onClearSelection: handleClearSelection,
9105
+ onBulkFavorite: handleBulkFavorite
9106
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9107
+ className: "flex items-center gap-3",
9108
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OwnerFilterTabs, {
9109
+ value: ownerFilter,
9110
+ onValueChange: setOwnerFilter,
9111
+ myLabel: "My Playlists"
9104
9112
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9105
- className: GRID_CLASS$1,
9106
- children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9107
- className: "space-y-2",
9108
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "aspect-square w-full rounded-lg" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-4 w-3/4" })]
9109
- }, i))
9110
- })]
9111
- });
9112
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9113
- className: "space-y-6 px-4 py-4 md:px-10 md:py-6",
9114
- children: [
9115
- hasSelection ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BulkSelectionBar, {
9116
- selectedCount: selectedIds.size,
9117
- totalCount: filteredPlaylists.length,
9118
- onSelectAll: handleSelectAll,
9119
- onClearSelection: handleClearSelection,
9120
- onBulkFavorite: handleBulkFavorite
9121
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9122
- className: "flex items-center gap-3",
9123
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OwnerFilterTabs, {
9124
- value: ownerFilter,
9125
- onValueChange: setOwnerFilter,
9126
- myLabel: "My Playlists"
9127
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9128
- className: "ml-auto w-full max-w-sm",
9129
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SearchSort.SearchSort, {
9130
- searchValue: searchTerm,
9131
- onSearchChange: setSearchTerm,
9132
- placeholder: "Search playlists...",
9133
- sortOptions: [
9134
- {
9135
- label: "Name (A-Z)",
9136
- value: "title"
9137
- },
9138
- {
9139
- label: "Name (Z-A)",
9140
- value: "-title"
9141
- },
9142
- {
9143
- label: "Date Created (Newest)",
9144
- value: "-created_at"
9145
- },
9146
- {
9147
- label: "Date Created (Oldest)",
9148
- value: "created_at"
9149
- }
9150
- ],
9151
- sortValue,
9152
- onSortChange: setSortValue
9153
- })
9154
- })]
9155
- }),
9156
- filteredPlaylists.length === 0 && !isFetchingNextPage && !hasNextPage ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9157
- className: "flex flex-col items-center justify-center py-16",
9158
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9159
- className: "text-muted-foreground text-sm",
9160
- children: getFilteredEmptyMessage({
9161
- searchTerm,
9162
- ownerFilter,
9163
- entityName: "playlists",
9164
- defaultMessage: "There are no playlists available at the moment."
9165
- })
9166
- })
9167
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9168
- className: GRID_CLASS$1,
9169
- children: filteredPlaylists.map((playlist) => {
9170
- const firstItem = playlist.items?.[0];
9171
- const imageUrl = playlist.image_url ?? firstItem?.image_url ?? firstItem?.relateable?.image_url ?? firstItem?.relateable?.compressed_image_url;
9172
- const itemCount = playlist.items_count ?? playlist.items?.length ?? 0;
9173
- const canEdit = !readOnly && playlist.user_id === user?.id;
9174
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlaylistCard, {
9175
- title: playlist.title || "Untitled Playlist",
9176
- imageUrl,
9177
- href: `playlists/${playlist.id}`,
9178
- itemCount,
9179
- isFavorited: playlist.is_favorited,
9180
- isSelectable: true,
9181
- isSelected: selectedIds.has(playlist.id),
9182
- canEdit,
9183
- onSelectionChange: (selected) => handleToggleSelection(playlist.id, selected),
9184
- onToggleFavorite: onToggleFavorite ? () => handleFavorite(playlist.id) : void 0,
9185
- onEdit: () => handleEdit(playlist.id),
9186
- onDelete: onDeletePlaylist ? () => setPendingDeleteId(playlist.id) : void 0
9187
- }, playlist.id);
9188
- })
9189
- }),
9190
- isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9191
- className: "flex justify-center py-4",
9192
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
9193
- }),
9194
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9195
- ref: observerTarget,
9196
- className: "h-1"
9197
- }),
9198
- error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
9199
- className: "bg-destructive/10 text-destructive rounded-lg px-3 py-2",
9200
- children: ["Error: ", error.message]
9201
- }),
9202
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialog, {
9203
- open: pendingDeleteId !== null,
9204
- onOpenChange: (open) => {
9205
- if (!open && !isDeleting) setPendingDeleteId(null);
9206
- },
9207
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogTitle, { children: "Delete this playlist?" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogDescription, { children: "This removes the playlist from your library. Shared links that point to it will stop working." })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogCancel, {
9208
- disabled: isDeleting,
9209
- children: "Cancel"
9210
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogAction, {
9211
- onClick: (e) => {
9212
- e.preventDefault();
9213
- confirmDelete();
9113
+ className: "ml-auto w-full max-w-sm",
9114
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SearchSort.SearchSort, {
9115
+ searchValue: searchTerm,
9116
+ onSearchChange: setSearchTerm,
9117
+ placeholder: "Search playlists...",
9118
+ sortOptions: [
9119
+ {
9120
+ label: "Name (A-Z)",
9121
+ value: "title"
9214
9122
  },
9215
- disabled: isDeleting,
9216
- className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
9217
- children: isDeleting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Spinner, { className: "size-4" }) : "Delete"
9218
- })] })] })
9123
+ {
9124
+ label: "Name (Z-A)",
9125
+ value: "-title"
9126
+ },
9127
+ {
9128
+ label: "Date Created (Newest)",
9129
+ value: "-created_at"
9130
+ },
9131
+ {
9132
+ label: "Date Created (Oldest)",
9133
+ value: "created_at"
9134
+ }
9135
+ ],
9136
+ sortValue,
9137
+ onSortChange: setSortValue
9219
9138
  })
9220
- ]
9139
+ })]
9221
9140
  });
9141
+ const footer = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9142
+ className: "flex justify-center py-4",
9143
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
9144
+ }), error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
9145
+ className: "bg-destructive/10 text-destructive rounded-lg px-3 py-2",
9146
+ children: ["Error: ", error.message]
9147
+ })] });
9148
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareableListLayout, {
9149
+ isLoading,
9150
+ filters: filterBar,
9151
+ isEmpty: filteredPlaylists.length === 0 && !isFetchingNextPage && !hasNextPage,
9152
+ emptyMessage: getFilteredEmptyMessage({
9153
+ searchTerm,
9154
+ ownerFilter,
9155
+ entityName: "playlists",
9156
+ defaultMessage: "There are no playlists available at the moment."
9157
+ }),
9158
+ footer,
9159
+ sentinelRef: observerTarget,
9160
+ loadingFilterShape: "search-action",
9161
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9162
+ className: GRID_CLASS,
9163
+ children: filteredPlaylists.map((playlist) => {
9164
+ const firstItem = playlist.items?.[0];
9165
+ const imageUrl = playlist.image_url ?? firstItem?.image_url ?? firstItem?.relateable?.image_url ?? firstItem?.relateable?.compressed_image_url;
9166
+ const itemCount = playlist.items_count ?? playlist.items?.length ?? 0;
9167
+ const canEdit = !readOnly && playlist.user_id === user?.id;
9168
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlaylistCard, {
9169
+ title: playlist.title || "Untitled Playlist",
9170
+ imageUrl,
9171
+ href: `playlists/${playlist.id}`,
9172
+ itemCount,
9173
+ isFavorited: playlist.is_favorited,
9174
+ isSelectable: true,
9175
+ isSelected: selectedIds.has(playlist.id),
9176
+ canEdit,
9177
+ onSelectionChange: (selected) => handleToggleSelection(playlist.id, selected),
9178
+ onToggleFavorite: onToggleFavorite ? () => handleFavorite(playlist.id) : void 0,
9179
+ onEdit: () => handleEdit(playlist.id),
9180
+ onDelete: onDeletePlaylist ? () => setPendingDeleteId(playlist.id) : void 0
9181
+ }, playlist.id);
9182
+ })
9183
+ })
9184
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialog, {
9185
+ open: pendingDeleteId !== null,
9186
+ onOpenChange: (open) => {
9187
+ if (!open && !isDeleting) setPendingDeleteId(null);
9188
+ },
9189
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogTitle, { children: "Delete this playlist?" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogDescription, { children: "This removes the playlist from your library. Shared links that point to it will stop working." })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.AlertDialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogCancel, {
9190
+ disabled: isDeleting,
9191
+ children: "Cancel"
9192
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.AlertDialogAction, {
9193
+ onClick: (e) => {
9194
+ e.preventDefault();
9195
+ confirmDelete();
9196
+ },
9197
+ disabled: isDeleting,
9198
+ className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
9199
+ children: isDeleting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Spinner, { className: "size-4" }) : "Delete"
9200
+ })] })] })
9201
+ })] });
9222
9202
  }
9223
9203
  //#endregion
9224
9204
  //#region ../../shareables/ui/src/constants.ts
@@ -9484,7 +9464,6 @@ const DEFAULT_IMAGE$1 = "https://assets.fluid.app/fluid-admin/images/we-commerce
9484
9464
  function PlaylistDetailScreen({ playlistId, onNavigate }) {
9485
9465
  const api = useShareablesApi();
9486
9466
  const { navigate, user, readOnly } = useShareablesUI();
9487
- const [isDescriptionExpanded, setIsDescriptionExpanded] = (0, react.useState)(false);
9488
9467
  const [selectedPlaylistItemIndex, setSelectedPlaylistItemIndex] = (0, react.useState)(0);
9489
9468
  const { data: playlistResponse, isLoading } = (0, _tanstack_react_query.useQuery)({
9490
9469
  queryKey: shareablesKeys.playlists.detail(Number(playlistId)),
@@ -9530,94 +9509,53 @@ function PlaylistDetailScreen({ playlistId, onNavigate }) {
9530
9509
  }) }), [displayTitle, navigate]));
9531
9510
  const selectedPlaylistItem = playlist?.items?.[selectedPlaylistItemIndex];
9532
9511
  const displayImage = selectedPlaylistItem?.image_url ?? selectedPlaylistItem?.relateable?.image_url ?? selectedPlaylistItem?.relateable?.compressed_image_url ?? DEFAULT_IMAGE$1;
9533
- const strippedDescription = stripTags(playlist?.description || playlist?.search_engine_optimizer?.description || "");
9534
- const shouldShowReadMore = strippedDescription.length > 150;
9512
+ const displayDescription = playlist?.description || playlist?.search_engine_optimizer?.description || "";
9535
9513
  const selectedKind = selectedPlaylistItem?.kind ?? selectedPlaylistItem?.relateable?.kind;
9536
9514
  const displayVideo = selectedKind === "video" ? selectedPlaylistItem?.video_url ?? selectedPlaylistItem?.relateable?.video_url ?? void 0 : void 0;
9537
9515
  const isVideo = selectedKind === "video" && !!displayVideo;
9538
9516
  const taggedProducts = playlist?.items?.filter((item) => item.relateable_type === "Product").map((item) => item.relateable).filter((p) => !!p) || [];
9539
- if (isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9540
- className: "flex items-center justify-center py-16",
9541
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Spinner, { className: "size-8" })
9542
- });
9543
- if (!playlist) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9544
- className: "flex flex-col items-center justify-center py-16",
9545
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9546
- className: "text-destructive text-sm",
9547
- children: "Playlist not found or failed to load."
9548
- })
9549
- });
9550
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9551
- className: "flex flex-col gap-4 px-4 py-4 md:px-10 md:py-6",
9552
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9553
- className: "mx-auto flex w-full max-w-[480px] flex-col gap-6 md:h-[calc(100vh-140px)] md:max-w-none md:flex-row",
9554
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9555
- className: "aspect-square w-full md:aspect-auto md:h-full",
9556
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9557
- className: "relative h-full overflow-hidden rounded-2xl",
9558
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SharePageImageDisplay, {
9559
- displayImage,
9560
- displayTitle,
9561
- displayVideo,
9562
- isVideo,
9563
- badgeLabel: "Playlist"
9564
- })
9565
- })
9566
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9567
- className: "flex w-full flex-col md:overflow-y-auto",
9568
- children: [
9569
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9570
- className: "space-y-4",
9571
- children: [
9572
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
9573
- className: "text-foreground text-[26px] leading-[1.2] font-semibold",
9574
- children: displayTitle
9575
- }),
9576
- strippedDescription && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9577
- className: "text-foreground/70 text-sm leading-relaxed",
9578
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9579
- className: !isDescriptionExpanded && shouldShowReadMore ? "line-clamp-3" : "",
9580
- children: strippedDescription
9581
- }), shouldShowReadMore && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Button, {
9582
- onClick: () => setIsDescriptionExpanded(!isDescriptionExpanded),
9583
- variant: "ghost",
9584
- size: "sm",
9585
- className: "text-foreground hover:text-foreground/80 mt-1 h-auto p-0 text-xs font-normal underline",
9586
- children: isDescriptionExpanded ? "Read less" : "Read more"
9587
- })]
9588
- }),
9589
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssetActions, {
9590
- downloadUrl: null,
9591
- displayTitle,
9592
- shareLink: shareLinkError ? null : shareLink || null,
9593
- shareLinkLoading,
9594
- isVideo,
9595
- relateableId: Number(playlistId),
9596
- relateableType: "Library"
9597
- })
9598
- ]
9599
- }),
9600
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Separator, { className: "border-foreground my-4" }),
9601
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9602
- className: "bg-background rounded-lg",
9603
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaggedProductsList, {
9604
- products: taggedProducts,
9605
- onProductClick: (productId) => onNavigate?.("product", String(productId))
9606
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlaylistItemsList, {
9607
- items: playlist?.items || [],
9608
- onSelectItem: setSelectedPlaylistItemIndex,
9609
- selectedItemIndex: selectedPlaylistItemIndex,
9610
- onNavigateToItem: (itemId, relateableType) => {
9611
- if (!NAVIGABLE_RELATEABLE_TYPES.has(relateableType ?? "")) return;
9612
- if (relateableType === "Product") onNavigate?.("product", String(itemId));
9613
- else if (relateableType === "Page") onNavigate?.("page", String(itemId));
9614
- else if (relateableType === "Medium") onNavigate?.("media", String(itemId));
9615
- }
9616
- })]
9617
- })
9618
- ]
9517
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ShareableDetailLayout, {
9518
+ isLoading,
9519
+ notFound: !playlist,
9520
+ notFoundMessage: "Playlist not found or failed to load.",
9521
+ title: displayTitle,
9522
+ description: displayDescription,
9523
+ image: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9524
+ className: "relative h-full overflow-hidden rounded-2xl",
9525
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SharePageImageDisplay, {
9526
+ displayImage,
9527
+ displayTitle,
9528
+ displayVideo,
9529
+ isVideo,
9530
+ badgeLabel: "Playlist"
9531
+ })
9532
+ }),
9533
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssetActions, {
9534
+ downloadUrl: null,
9535
+ displayTitle,
9536
+ shareLink: shareLinkError ? null : shareLink || null,
9537
+ shareLinkLoading,
9538
+ isVideo,
9539
+ relateableId: Number(playlistId),
9540
+ relateableType: "Library"
9541
+ }),
9542
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Separator, { className: "border-foreground my-4" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9543
+ className: "bg-background rounded-lg",
9544
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaggedProductsList, {
9545
+ products: taggedProducts,
9546
+ onProductClick: (productId) => onNavigate?.("product", String(productId))
9547
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlaylistItemsList, {
9548
+ items: playlist?.items || [],
9549
+ onSelectItem: setSelectedPlaylistItemIndex,
9550
+ selectedItemIndex: selectedPlaylistItemIndex,
9551
+ onNavigateToItem: (itemId, relateableType) => {
9552
+ if (!NAVIGABLE_RELATEABLE_TYPES.has(relateableType ?? "")) return;
9553
+ if (relateableType === "Product") onNavigate?.("product", String(itemId));
9554
+ else if (relateableType === "Page") onNavigate?.("page", String(itemId));
9555
+ else if (relateableType === "Medium") onNavigate?.("media", String(itemId));
9556
+ }
9619
9557
  })]
9620
- })
9558
+ })]
9621
9559
  });
9622
9560
  }
9623
9561
  //#endregion
@@ -10674,7 +10612,6 @@ function PlaylistCreateScreen({ playlistId, onBack, hideHeader, renderHeaderSlot
10674
10612
  //#endregion
10675
10613
  //#region ../../shareables/ui/src/components/screens/FilesListingScreen.tsx
10676
10614
  const PAGE_SIZE = 24;
10677
- const GRID_CLASS = "grid grid-cols-1 gap-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-4";
10678
10615
  function formatFileSize(bytes) {
10679
10616
  if (bytes < 1024) return `${bytes} B`;
10680
10617
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -10734,110 +10671,79 @@ function FilesListingScreen({ onNavigate }) {
10734
10671
  observer.observe(target);
10735
10672
  return () => observer.disconnect();
10736
10673
  }, [handleIntersect]);
10737
- if (isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10738
- className: "space-y-6 px-4 py-4 md:px-10 md:py-6",
10739
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10740
- className: "flex items-center gap-3",
10741
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-10 flex-1" })
10742
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10743
- className: GRID_CLASS,
10744
- children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10745
- className: "space-y-2",
10746
- children: [
10747
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "aspect-square w-full rounded-lg" }),
10748
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-4 w-3/4" }),
10749
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Skeleton, { className: "h-3 w-1/2" })
10750
- ]
10751
- }, i))
10752
- })]
10753
- });
10754
- if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10755
- className: "flex flex-col items-center justify-center py-16",
10756
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10757
- className: "text-destructive text-sm",
10758
- children: "Failed to load files. Please try again."
10759
- })
10760
- });
10761
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10762
- className: "space-y-6 px-4 py-4 md:px-10 md:py-6",
10763
- children: [
10764
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10765
- className: "flex justify-end",
10766
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10767
- className: "w-full max-w-sm",
10768
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SearchSort.SearchSort, {
10769
- searchValue: searchTerm,
10770
- onSearchChange: setSearchTerm,
10771
- placeholder: "Search files..."
10772
- })
10773
- })
10774
- }),
10775
- files.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10776
- className: "flex flex-col items-center justify-center py-16",
10777
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10778
- className: "text-muted-foreground text-sm",
10779
- children: debouncedSearch ? `No files match "${debouncedSearch}". Try a different search term.` : "No files available."
10674
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareableListLayout, {
10675
+ isLoading,
10676
+ error,
10677
+ errorMessage: "Failed to load files. Please try again.",
10678
+ isEmpty: files.length === 0,
10679
+ emptyMessage: debouncedSearch ? `No files match "${debouncedSearch}". Try a different search term.` : "No files available.",
10680
+ filters: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10681
+ className: "flex justify-end",
10682
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10683
+ className: "w-full max-w-sm",
10684
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SearchSort.SearchSort, {
10685
+ searchValue: searchTerm,
10686
+ onSearchChange: setSearchTerm,
10687
+ placeholder: "Search files..."
10780
10688
  })
10781
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10782
- className: GRID_CLASS,
10783
- children: files.map((file) => {
10784
- const isVideo = file.content_type?.startsWith("video/");
10785
- const fileUrl = file.url || "#";
10786
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.Card, {
10787
- role: "button",
10788
- tabIndex: 0,
10789
- onClick: () => window.open(fileUrl, "_blank"),
10790
- onKeyDown: (e) => {
10791
- if (e.key === "Enter" || e.key === " ") {
10792
- e.preventDefault();
10793
- window.open(fileUrl, "_blank");
10794
- }
10795
- },
10796
- className: "group hover:bg-muted cursor-pointer gap-0 overflow-hidden rounded-lg border-0 p-0 shadow-none transition-colors",
10797
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10798
- className: "bg-muted relative aspect-square overflow-hidden rounded-lg",
10799
- children: [
10800
- renderImage({
10801
- src: file.preview_image_url || DEFAULT_IMAGE,
10802
- alt: file.filename || "Untitled File",
10803
- fill: true,
10804
- className: "object-cover transition-transform group-hover:scale-105"
10805
- }),
10806
- isVideo && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10807
- className: "absolute inset-0 flex items-center justify-center",
10808
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10809
- className: "bg-foreground/50 flex h-16 w-16 items-center justify-center rounded-full backdrop-blur-sm",
10810
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CirclePlay, { className: "text-background h-12 w-12" })
10811
- })
10812
- }),
10813
- !isVideo && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Badge, {
10814
- className: "absolute top-2 right-2 shadow-lg",
10815
- variant: "default",
10816
- children: formatFileSize(file.content_size)
10689
+ })
10690
+ }),
10691
+ footer: isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10692
+ className: "flex justify-center py-4",
10693
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
10694
+ }),
10695
+ sentinelRef: observerTarget,
10696
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10697
+ className: SHAREABLE_GRID_CLASS,
10698
+ children: files.map((file) => {
10699
+ const isVideo = file.content_type?.startsWith("video/");
10700
+ const fileUrl = file.url || "#";
10701
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.Card, {
10702
+ role: "button",
10703
+ tabIndex: 0,
10704
+ onClick: () => window.open(fileUrl, "_blank"),
10705
+ onKeyDown: (e) => {
10706
+ if (e.key === "Enter" || e.key === " ") {
10707
+ e.preventDefault();
10708
+ window.open(fileUrl, "_blank");
10709
+ }
10710
+ },
10711
+ className: "group hover:bg-muted cursor-pointer gap-0 overflow-hidden rounded-lg border-0 p-0 shadow-none transition-colors",
10712
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10713
+ className: "bg-muted relative aspect-square overflow-hidden rounded-lg",
10714
+ children: [
10715
+ renderImage({
10716
+ src: file.preview_image_url || DEFAULT_IMAGE,
10717
+ alt: file.filename || "Untitled File",
10718
+ fill: true,
10719
+ className: "object-cover transition-transform group-hover:scale-105"
10720
+ }),
10721
+ isVideo && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10722
+ className: "absolute inset-0 flex items-center justify-center",
10723
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10724
+ className: "bg-foreground/50 flex h-16 w-16 items-center justify-center rounded-full backdrop-blur-sm",
10725
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CirclePlay, { className: "text-background h-12 w-12" })
10817
10726
  })
10818
- ]
10819
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10820
- className: "px-2 pt-2 pb-4",
10821
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
10822
- className: "text-foreground line-clamp-2 text-sm leading-tight font-bold",
10823
- children: file.filename || "Untitled File"
10824
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10825
- className: "text-muted-foreground mt-1 text-xs",
10826
- children: file.content_type || "File"
10827
- })]
10727
+ }),
10728
+ !isVideo && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Badge, {
10729
+ className: "absolute top-2 right-2 shadow-lg",
10730
+ variant: "default",
10731
+ children: formatFileSize(file.content_size)
10732
+ })
10733
+ ]
10734
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10735
+ className: "px-2 pt-2 pb-4",
10736
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
10737
+ className: "text-foreground line-clamp-2 text-sm leading-tight font-bold",
10738
+ children: file.filename || "Untitled File"
10739
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10740
+ className: "text-muted-foreground mt-1 text-xs",
10741
+ children: file.content_type || "File"
10828
10742
  })]
10829
- }, file.id);
10830
- })
10831
- }),
10832
- isFetchingNextPage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10833
- className: "flex justify-center py-4",
10834
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
10835
- }),
10836
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10837
- ref: observerTarget,
10838
- className: "h-1"
10743
+ })]
10744
+ }, file.id);
10839
10745
  })
10840
- ]
10746
+ })
10841
10747
  });
10842
10748
  }
10843
10749
  //#endregion
@@ -12002,4 +11908,4 @@ Object.defineProperty(exports, "usePortalContentContext", {
12002
11908
  }
12003
11909
  });
12004
11910
 
12005
- //# sourceMappingURL=PortalContentApiProvider-DqAbDYgq.cjs.map
11911
+ //# sourceMappingURL=PortalContentApiProvider-C5WzWC3k.cjs.map