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