@dyrected/admin 2.5.30 → 2.5.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/admin.css CHANGED
@@ -1383,10 +1383,6 @@
1383
1383
  margin-top: 1rem;
1384
1384
  }
1385
1385
 
1386
- .dy-mt-5{
1387
- margin-top: 1.25rem;
1388
- }
1389
-
1390
1386
  .dy-mt-8{
1391
1387
  margin-top: 2rem;
1392
1388
  }
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import { HashRouter, Link, MemoryRouter, Route, Routes, useLocation, useNavigate
5
5
  import { QueryClient, QueryClientProvider, useInfiniteQuery, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
6
6
  import { createClient } from "@dyrected/sdk";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
- import { AlertCircle, AlignCenter, AlignLeft, AlignRight, Archive, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, ArrowUpRight, Bold, BookOpen, Braces, Calendar, Check, CheckCircle, CheckCircle2, ChevronDown, ChevronDownIcon, ChevronLeft, ChevronLeftIcon, ChevronRight, ChevronRightIcon, ChevronUp, ChevronsUpDown, Circle, Clock, Clock3, Code, Code2, Compass, Copy, Database, ExternalLink, Eye, EyeOff, FileDown, FileIcon, FileText, Filter, Globe, GripVertical, Heading1, Heading2, Image as Image$1, Info, Italic, KeyRound, Layers, LayoutDashboard, Library, Link as Link$1, List, ListOrdered, Loader2, Lock, LogOut, Mail, Menu, Monitor, MoreHorizontal, PanelLeftClose, PanelLeftOpen, Pencil, Plus, Quote, RotateCcw, Save, Scissors, Search, Settings, Settings2, Share2, Shield, ShieldCheck, Smartphone, Sparkles, Strikethrough, Table, Trash2, Underline, Upload, UploadCloud, Users, Video, Volume2, X, XCircle, icons } from "lucide-react";
8
+ import { AlertCircle, AlignCenter, AlignLeft, AlignRight, Archive, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, ArrowUpRight, Bold, BookOpen, Braces, Calendar, Check, CheckCircle, CheckCircle2, ChevronDown, ChevronDownIcon, ChevronLeft, ChevronLeftIcon, ChevronRight, ChevronRightIcon, ChevronUp, ChevronsUpDown, Circle, Clock, Clock3, Code, Code2, Compass, Copy, Database, ExternalLink, Eye, EyeOff, FileDown, FileIcon, FileText, Filter, Globe, GripVertical, Heading1, Heading2, Image as Image$1, Info, Italic, KeyRound, Layers, LayoutDashboard, Library, Link as Link$1, List, ListOrdered, Loader2, Lock, LogOut, Mail, Menu, Monitor, MoreHorizontal, PanelLeftClose, PanelLeftOpen, Pencil, Plus, Quote, RotateCcw, Save, Scissors, Search, Settings, Settings2, Share2, Shield, Smartphone, Sparkles, Strikethrough, Table, Trash2, Underline, Upload, UploadCloud, Users, Video, Volume2, X, XCircle, icons } from "lucide-react";
9
9
  import { clsx } from "clsx";
10
10
  import { extendTailwindMerge } from "tailwind-merge";
11
11
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
@@ -9048,11 +9048,12 @@ function EditEntryPage() {
9048
9048
  enabled: !!client
9049
9049
  });
9050
9050
  const schema = schemas?.collections.find((c) => c.slug === slug);
9051
- const [prevSchemaSlug, setPrevSchemaSlug] = useState(null);
9052
- if (schema && schema.slug !== prevSchemaSlug) {
9053
- setPrevSchemaSlug(schema.slug);
9054
- if (schema.admin?.previewUrl) setShowPreview(false);
9055
- }
9051
+ const schemaSlug = schema?.slug;
9052
+ const schemaPreviewUrl = schema?.admin?.previewUrl;
9053
+ useEffect(() => {
9054
+ if (!schemaSlug || !schemaPreviewUrl) return;
9055
+ setShowPreview((previous) => previous ? false : previous);
9056
+ }, [schemaSlug, schemaPreviewUrl]);
9056
9057
  const { data: entry, isLoading: isEntryLoading } = useQuery({
9057
9058
  queryKey: [
9058
9059
  "entry",
@@ -9062,11 +9063,14 @@ function EditEntryPage() {
9062
9063
  queryFn: () => client.collection(slug).findOne(id),
9063
9064
  enabled: !!client && isEdit
9064
9065
  });
9065
- const [prevEntry, setPrevEntry] = useState(null);
9066
- if (entry && entry !== prevEntry) {
9067
- setPrevEntry(entry);
9068
- setPreviewData(entry);
9069
- }
9066
+ useEffect(() => {
9067
+ if (!entry) return;
9068
+ setPreviewData((previous) => previous === entry ? previous : entry);
9069
+ }, [
9070
+ entry,
9071
+ id,
9072
+ slug
9073
+ ]);
9070
9074
  const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
9071
9075
  const isSelf = !!user && !!id && (user.id === id || user.sub === id);
9072
9076
  const passwordChangeMode = schema?.auth ? isSelf ? "self" : isAdminUser ? "admin" : null : null;
@@ -9519,7 +9523,7 @@ Separator.displayName = SeparatorPrimitive.Root.displayName;
9519
9523
  //#endregion
9520
9524
  //#region src/pages/media/media-page.tsx
9521
9525
  function MediaPage({ collectionSlug, schema }) {
9522
- const { client } = useDyrected();
9526
+ const { client, components, user } = useDyrected();
9523
9527
  const queryClient = useQueryClient();
9524
9528
  const [search, setSearch] = React$1.useState("");
9525
9529
  const [isUploadOpen, setIsUploadOpen] = React$1.useState(false);
@@ -9545,6 +9549,49 @@ function MediaPage({ collectionSlug, schema }) {
9545
9549
  const mediaResponse = React$1.useMemo(() => {
9546
9550
  return data?.pages.flatMap((page) => page.docs) || [];
9547
9551
  }, [data]);
9552
+ const readAccess = schema.access?.read;
9553
+ const createAccess = schema.access?.create;
9554
+ const evaluateAccess = (access) => {
9555
+ if (access === false) return false;
9556
+ if (typeof access !== "string") return true;
9557
+ try {
9558
+ return Boolean(jexl.evalSync(access, { user }));
9559
+ } catch (error) {
9560
+ console.warn("Media collection access evaluation failed:", error);
9561
+ return false;
9562
+ }
9563
+ };
9564
+ const canRead = evaluateAccess(readAccess);
9565
+ const canCreate = evaluateAccess(createAccess);
9566
+ const lastPage = data?.pages[data.pages.length - 1];
9567
+ const collectionComponentProps = {
9568
+ client,
9569
+ user,
9570
+ collection: schema,
9571
+ collectionSlug,
9572
+ response: lastPage ? {
9573
+ ...lastPage,
9574
+ docs: mediaResponse
9575
+ } : void 0,
9576
+ documents: mediaResponse,
9577
+ isLoading,
9578
+ pagination: {
9579
+ page: lastPage?.page ?? 1,
9580
+ totalPages: lastPage?.totalPages ?? 1,
9581
+ total: lastPage?.total ?? 0,
9582
+ hasNextPage: Boolean(hasNextPage),
9583
+ hasPrevPage: Boolean(lastPage?.hasPrevPage)
9584
+ },
9585
+ permissions: {
9586
+ canRead,
9587
+ canCreate
9588
+ },
9589
+ urls: {
9590
+ collection: `/collections/${collectionSlug}`,
9591
+ create: `/collections/${collectionSlug}/new`
9592
+ }
9593
+ };
9594
+ const collectionSlots = schema.admin?.components;
9548
9595
  const observerRef = React$1.useRef(null);
9549
9596
  const sentinelRef = React$1.useCallback((node) => {
9550
9597
  if (observerRef.current) {
@@ -9584,12 +9631,13 @@ function MediaPage({ collectionSlug, schema }) {
9584
9631
  }
9585
9632
  });
9586
9633
  const onDrop = React$1.useCallback((acceptedFiles) => {
9587
- if (acceptedFiles.length > 0) {
9634
+ if (canCreate && acceptedFiles.length > 0) {
9588
9635
  setUploadFiles(acceptedFiles);
9589
9636
  setIsUploadOpen(true);
9590
9637
  }
9591
- }, []);
9638
+ }, [canCreate]);
9592
9639
  const handlePaste = React$1.useCallback((e) => {
9640
+ if (!canCreate) return;
9593
9641
  const items = e.clipboardData?.items;
9594
9642
  if (!items) return;
9595
9643
  const filesToUpload = [];
@@ -9602,7 +9650,7 @@ function MediaPage({ collectionSlug, schema }) {
9602
9650
  setUploadFiles((prev) => [...prev, ...filesToUpload]);
9603
9651
  setIsUploadOpen(true);
9604
9652
  }
9605
- }, []);
9653
+ }, [canCreate]);
9606
9654
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
9607
9655
  onDrop,
9608
9656
  noClick: true
@@ -9611,6 +9659,19 @@ function MediaPage({ collectionSlug, schema }) {
9611
9659
  setIsUploadOpen(open);
9612
9660
  if (!open) setUploadFiles([]);
9613
9661
  };
9662
+ if (!canRead) return /* @__PURE__ */ jsx("div", {
9663
+ className: "dy-flex dy-h-[calc(100vh-200px)] dy-items-center dy-justify-center",
9664
+ children: /* @__PURE__ */ jsxs("div", {
9665
+ className: "dy-space-y-2 dy-text-center",
9666
+ children: [/* @__PURE__ */ jsx("h3", {
9667
+ className: "dy-text-lg dy-font-bold",
9668
+ children: "Access Denied"
9669
+ }), /* @__PURE__ */ jsx("p", {
9670
+ className: "dy-text-sm dy-text-muted-foreground",
9671
+ children: "You do not have permission to view this collection."
9672
+ })]
9673
+ })
9674
+ });
9614
9675
  return /* @__PURE__ */ jsxs("div", {
9615
9676
  ...getRootProps(),
9616
9677
  onPaste: handlePaste,
@@ -9630,6 +9691,12 @@ function MediaPage({ collectionSlug, schema }) {
9630
9691
  })]
9631
9692
  })
9632
9693
  }),
9694
+ /* @__PURE__ */ jsx(AdminComponentSlot, {
9695
+ slot: "beforeList",
9696
+ componentKeys: collectionSlots?.beforeList,
9697
+ registry: components?.collectionList,
9698
+ componentProps: collectionComponentProps
9699
+ }),
9633
9700
  /* @__PURE__ */ jsxs("div", {
9634
9701
  className: "dy-flex dy-flex-col dy-gap-4 dy-border-b dy-border-border/50 dy-pb-5 sm:dy-flex-row sm:dy-items-end sm:dy-justify-between sm:dy-pb-6",
9635
9702
  children: [/* @__PURE__ */ jsxs("div", {
@@ -9638,13 +9705,13 @@ function MediaPage({ collectionSlug, schema }) {
9638
9705
  className: "dy-flex dy-items-center dy-gap-2 dy-mb-1",
9639
9706
  children: [/* @__PURE__ */ jsx(Image$1, { className: "dy-h-5 dy-w-5 dy-flex-shrink-0 dy-text-primary" }), /* @__PURE__ */ jsx("h1", {
9640
9707
  className: "dy-min-w-0 dy-break-words dy-text-2xl dy-font-bold dy-tracking-tight dy-text-foreground sm:dy-text-3xl",
9641
- children: schema?.labels?.plural ?? schema?.label ?? (collectionSlug && collectionSlug !== "media" ? collectionSlug.charAt(0).toUpperCase() + collectionSlug.slice(1) : "Media Library")
9708
+ children: schema.labels?.plural ?? (collectionSlug !== "media" ? collectionSlug.charAt(0).toUpperCase() + collectionSlug.slice(1) : "Media Library")
9642
9709
  })]
9643
9710
  }), /* @__PURE__ */ jsx("p", {
9644
9711
  className: "dy-text-sm dy-leading-5 dy-text-muted-foreground",
9645
9712
  children: "Manage your images, documents, and other assets for this site."
9646
9713
  })]
9647
- }), /* @__PURE__ */ jsxs(Dialog, {
9714
+ }), canCreate && /* @__PURE__ */ jsxs(Dialog, {
9648
9715
  open: isUploadOpen,
9649
9716
  onOpenChange: handleUploadOpenChange,
9650
9717
  children: [/* @__PURE__ */ jsx(DialogTrigger, {
@@ -9685,6 +9752,12 @@ function MediaPage({ collectionSlug, schema }) {
9685
9752
  })]
9686
9753
  })
9687
9754
  }),
9755
+ /* @__PURE__ */ jsx(AdminComponentSlot, {
9756
+ slot: "beforeListTable",
9757
+ componentKeys: collectionSlots?.beforeListTable,
9758
+ registry: components?.collectionList,
9759
+ componentProps: collectionComponentProps
9760
+ }),
9688
9761
  /* @__PURE__ */ jsx(ScrollArea, {
9689
9762
  className: "dy-h-auto dy-pr-0 md:dy-h-[calc(100vh-320px)] md:dy-pr-4",
9690
9763
  children: isLoading ? /* @__PURE__ */ jsx("div", {
@@ -9721,6 +9794,18 @@ function MediaPage({ collectionSlug, schema }) {
9721
9794
  })]
9722
9795
  })
9723
9796
  }),
9797
+ /* @__PURE__ */ jsx(AdminComponentSlot, {
9798
+ slot: "afterListTable",
9799
+ componentKeys: collectionSlots?.afterListTable,
9800
+ registry: components?.collectionList,
9801
+ componentProps: collectionComponentProps
9802
+ }),
9803
+ /* @__PURE__ */ jsx(AdminComponentSlot, {
9804
+ slot: "afterList",
9805
+ componentKeys: collectionSlots?.afterList,
9806
+ registry: components?.collectionList,
9807
+ componentProps: collectionComponentProps
9808
+ }),
9724
9809
  /* @__PURE__ */ jsx(MediaDetailsDialog, {
9725
9810
  item: selectedItem,
9726
9811
  collectionSlug,
@@ -10417,41 +10502,34 @@ function SetupPromptUI({ config }) {
10417
10502
  className: "dy-relative dy-flex dy-items-end lg:dy-justify-end",
10418
10503
  children: /* @__PURE__ */ jsxs("div", {
10419
10504
  className: "dy-w-full dy-max-w-sm dy-rounded-2xl dy-border dy-border-border dy-bg-background/90 dy-p-5 dy-text-foreground dy-shadow-lg dy-backdrop-blur-sm",
10420
- children: [
10421
- /* @__PURE__ */ jsxs("div", {
10422
- className: "dy-mb-5 dy-flex dy-items-center dy-justify-between",
10423
- children: [/* @__PURE__ */ jsx("span", {
10424
- className: "dy-text-xs dy-font-bold dy-uppercase dy-tracking-[0.12em] dy-text-muted-foreground",
10425
- children: "Context included"
10426
- }), /* @__PURE__ */ jsx(Code2, { className: "dy-h-4 dy-w-4 dy-text-primary" })]
10427
- }),
10428
- /* @__PURE__ */ jsxs("dl", {
10429
- className: "dy-space-y-4 dy-text-sm",
10430
- children: [/* @__PURE__ */ jsxs("div", {
10431
- className: "dy-flex dy-items-center dy-justify-between dy-gap-4",
10432
- children: [/* @__PURE__ */ jsx("dt", {
10433
- className: "dy-text-muted-foreground",
10434
- children: "Tech stack"
10435
- }), /* @__PURE__ */ jsx("dd", {
10436
- className: "dy-font-mono dy-font-semibold dy-text-foreground",
10437
- children: stack ?? "Choose in guide"
10438
- })]
10439
- }), /* @__PURE__ */ jsxs("div", {
10440
- className: "dy-flex dy-items-center dy-justify-between dy-gap-4",
10441
- children: [/* @__PURE__ */ jsx("dt", {
10442
- className: "dy-text-muted-foreground",
10443
- children: "Site"
10444
- }), /* @__PURE__ */ jsx("dd", {
10445
- className: "dy-max-w-[180px] dy-truncate dy-font-mono dy-font-semibold dy-text-foreground",
10446
- children: config.siteId || "Not provided"
10447
- })]
10505
+ children: [/* @__PURE__ */ jsxs("div", {
10506
+ className: "dy-mb-5 dy-flex dy-items-center dy-justify-between",
10507
+ children: [/* @__PURE__ */ jsx("span", {
10508
+ className: "dy-text-xs dy-font-bold dy-uppercase dy-tracking-[0.12em] dy-text-muted-foreground",
10509
+ children: "Context included"
10510
+ }), /* @__PURE__ */ jsx(Code2, { className: "dy-h-4 dy-w-4 dy-text-primary" })]
10511
+ }), /* @__PURE__ */ jsxs("dl", {
10512
+ className: "dy-space-y-4 dy-text-sm",
10513
+ children: [/* @__PURE__ */ jsxs("div", {
10514
+ className: "dy-flex dy-items-center dy-justify-between dy-gap-4",
10515
+ children: [/* @__PURE__ */ jsx("dt", {
10516
+ className: "dy-text-muted-foreground",
10517
+ children: "Tech stack"
10518
+ }), /* @__PURE__ */ jsx("dd", {
10519
+ className: "dy-font-mono dy-font-semibold dy-text-foreground",
10520
+ children: stack ?? "Choose in guide"
10448
10521
  })]
10449
- }),
10450
- /* @__PURE__ */ jsxs("div", {
10451
- className: "dy-mt-5 dy-flex dy-items-start dy-gap-2 dy-border-t dy-border-border dy-pt-4 dy-text-xs dy-leading-5 dy-text-muted-foreground",
10452
- children: [/* @__PURE__ */ jsx(ShieldCheck, { className: "dy-mt-0.5 dy-h-4 dy-w-4 dy-shrink-0 dy-text-primary" }), "API keys and authentication tokens never leave this admin page."]
10453
- })
10454
- ]
10522
+ }), /* @__PURE__ */ jsxs("div", {
10523
+ className: "dy-flex dy-items-center dy-justify-between dy-gap-4",
10524
+ children: [/* @__PURE__ */ jsx("dt", {
10525
+ className: "dy-text-muted-foreground",
10526
+ children: "Site"
10527
+ }), /* @__PURE__ */ jsx("dd", {
10528
+ className: "dy-max-w-[180px] dy-truncate dy-font-mono dy-font-semibold dy-text-foreground",
10529
+ children: config.siteId || "Not provided"
10530
+ })]
10531
+ })]
10532
+ })]
10455
10533
  })
10456
10534
  })]
10457
10535
  })
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,9 +1,5 @@
1
+ import { CollectionConfig } from '@dyrected/core';
1
2
  export declare function MediaPage({ collectionSlug, schema }: {
2
- collectionSlug?: string;
3
- schema?: {
4
- labels?: {
5
- plural?: string;
6
- };
7
- label?: string;
8
- };
3
+ collectionSlug: string;
4
+ schema: CollectionConfig;
9
5
  }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,12 @@
1
- import { SetupPromptConfig } from '@dyrected/sdk';
2
- export type { SetupPromptConfig };
1
+ export interface SetupPromptConfig {
2
+ siteName?: string;
3
+ siteId?: string;
4
+ apiKey?: string;
5
+ baseUrl?: string;
6
+ isSelfHosted?: boolean;
7
+ existingSite?: boolean;
8
+ defaultTechStack?: string;
9
+ }
3
10
  export interface SetupPromptProps {
4
11
  config: SetupPromptConfig;
5
12
  }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/admin",
3
- "version": "2.5.30",
3
+ "version": "2.5.33",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -62,8 +62,8 @@
62
62
  "tailwind-merge": "^3.5.0",
63
63
  "tailwindcss-animate": "^1.0.7",
64
64
  "zod": "^3.25.76",
65
- "@dyrected/core": "^2.5.30",
66
- "@dyrected/sdk": "^2.5.30"
65
+ "@dyrected/core": "^2.5.33",
66
+ "@dyrected/sdk": "^2.5.33"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "@tanstack/react-query": "^5.0.0",