@dyrected/admin 2.5.48 → 2.5.51

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
@@ -2901,6 +2901,10 @@
2901
2901
  background-color: rgb(0 0 0 / 0.4);
2902
2902
  }
2903
2903
 
2904
+ .dy-bg-black\/5{
2905
+ background-color: rgb(0 0 0 / 0.05);
2906
+ }
2907
+
2904
2908
  .dy-bg-black\/60{
2905
2909
  background-color: rgb(0 0 0 / 0.6);
2906
2910
  }
@@ -2983,6 +2987,10 @@
2983
2987
  background-color: rgb(16 185 129 / 0.1);
2984
2988
  }
2985
2989
 
2990
+ .dy-bg-green-500\/10{
2991
+ background-color: rgb(34 197 94 / 0.1);
2992
+ }
2993
+
2986
2994
  .dy-bg-muted{
2987
2995
  background-color: hsl(var(--muted));
2988
2996
  }
@@ -3185,6 +3193,11 @@
3185
3193
  padding: 1px;
3186
3194
  }
3187
3195
 
3196
+ .dy-px-0{
3197
+ padding-left: 0px;
3198
+ padding-right: 0px;
3199
+ }
3200
+
3188
3201
  .dy-px-1{
3189
3202
  padding-left: 0.25rem;
3190
3203
  padding-right: 0.25rem;
@@ -3684,6 +3697,11 @@
3684
3697
  color: rgb(34 197 94 / var(--tw-text-opacity, 1));
3685
3698
  }
3686
3699
 
3700
+ .dy-text-green-600{
3701
+ --tw-text-opacity: 1;
3702
+ color: rgb(22 163 74 / var(--tw-text-opacity, 1));
3703
+ }
3704
+
3687
3705
  .dy-text-muted-foreground{
3688
3706
  color: hsl(var(--muted-foreground));
3689
3707
  }
@@ -5474,6 +5492,10 @@
5474
5492
  padding: 0px !important;
5475
5493
  }
5476
5494
 
5495
+ .dark\:dy-bg-white\/5:is(.dy-dark *){
5496
+ background-color: rgb(255 255 255 / 0.05);
5497
+ }
5498
+
5477
5499
  .dark\:dy-text-amber-400:is(.dy-dark *){
5478
5500
  --tw-text-opacity: 1;
5479
5501
  color: rgb(251 191 36 / var(--tw-text-opacity, 1));
@@ -5489,6 +5511,11 @@
5489
5511
  color: rgb(52 211 153 / var(--tw-text-opacity, 1));
5490
5512
  }
5491
5513
 
5514
+ .dark\:dy-text-green-400:is(.dy-dark *){
5515
+ --tw-text-opacity: 1;
5516
+ color: rgb(74 222 128 / var(--tw-text-opacity, 1));
5517
+ }
5518
+
5492
5519
  .dark\:dy-text-purple-400:is(.dy-dark *){
5493
5520
  --tw-text-opacity: 1;
5494
5521
  color: rgb(192 132 252 / var(--tw-text-opacity, 1));
@@ -9,5 +9,5 @@ interface UrlFieldProps {
9
9
  siblingData: any;
10
10
  };
11
11
  }
12
- export declare function UrlField({ schema: _schema, field, disabled, context: _context }: UrlFieldProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare function UrlField({ field, disabled }: UrlFieldProps): import("react/jsx-runtime").JSX.Element;
13
13
  export {};
package/dist/index.mjs CHANGED
@@ -586,7 +586,7 @@ function NavGroup({ label, children, collapsed, defaultExpanded = true }) {
586
586
  })]
587
587
  });
588
588
  }
589
- function ThemeSelector({ collapsed = false, mobile = false }) {
589
+ function ThemeSelector({ collapsed = false, mobile = false, iconOnly = false }) {
590
590
  const { resolvedTheme, setTheme, theme } = useAdminTheme();
591
591
  const Icon = resolvedTheme === "dark" ? Moon : Sun;
592
592
  const options = [
@@ -611,11 +611,11 @@ function ThemeSelector({ collapsed = false, mobile = false }) {
611
611
  children: /* @__PURE__ */ jsxs(Button, {
612
612
  type: "button",
613
613
  variant: "ghost",
614
- size: mobile || collapsed ? "icon" : "sm",
615
- className: cn("dy-text-muted-foreground hover:dy-bg-accent hover:dy-text-foreground", collapsed || mobile ? "dy-h-8 dy-w-8" : "dy-h-7 dy-w-full dy-justify-start dy-px-2.5 dy-text-[11px]"),
614
+ size: mobile || collapsed || iconOnly ? "icon" : "sm",
615
+ className: cn("dy-text-muted-foreground hover:dy-bg-accent hover:dy-text-foreground", collapsed || mobile || iconOnly ? "dy-h-7 dy-w-7 dy-px-0 dy-justify-center" : "dy-h-7 dy-w-full dy-justify-start dy-px-2.5 dy-text-[11px]"),
616
616
  title: "Theme",
617
617
  "aria-label": "Change admin theme",
618
- children: [/* @__PURE__ */ jsx(Icon, { className: "dy-h-3.5 dy-w-3.5" }), !collapsed && !mobile && /* @__PURE__ */ jsx("span", { children: "Theme" })]
618
+ children: [/* @__PURE__ */ jsx(Icon, { className: "dy-h-3.5 dy-w-3.5" }), !collapsed && !mobile && !iconOnly && /* @__PURE__ */ jsx("span", { children: "Theme" })]
619
619
  })
620
620
  }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
621
621
  side: collapsed || mobile ? "bottom" : "top",
@@ -643,16 +643,16 @@ function ThemeSelector({ collapsed = false, mobile = false }) {
643
643
  ]
644
644
  })] });
645
645
  }
646
- function SidebarInner({ schemas, isLoading, location, logout, isEmbedded, collapsed, onToggleCollapse, onNavigate }) {
646
+ function SidebarInner({ schemas, isLoading, location, logout, isEmbedded, collapsed, onToggleCollapse, onNavigate, updateInfo }) {
647
647
  const { client, user } = useDyrected();
648
- const collections = schemas?.collections?.filter((c) => !c?.admin?.hidden && !c?.slug.startsWith("platform_")) ?? [];
649
- const globals = schemas?.globals?.filter((g) => !g?.admin?.hidden && !g?.slug.startsWith("platform_")) ?? [];
648
+ const collections = (schemas?.collections)?.filter((c) => !c?.admin?.hidden && !c?.slug.startsWith("platform_")) ?? [];
649
+ const globals = (schemas?.globals)?.filter((g) => !g?.admin?.hidden && !g?.slug.startsWith("platform_")) ?? [];
650
650
  const uploadCollections = collections.filter((c) => c.upload);
651
651
  const groupLabel = (text) => !collapsed ? /* @__PURE__ */ jsx("p", {
652
652
  className: "dy-px-3 dy-mb-1.5 dy-text-[10px] dy-font-semibold dy-uppercase dy-tracking-widest dy-text-muted-foreground/50",
653
653
  children: text
654
654
  }) : /* @__PURE__ */ jsx("div", { className: "dy-my-2 dy-mx-3 dy-h-px dy-bg-border" });
655
- const branding = schemas?.admin?.branding;
655
+ const branding = (schemas?.admin)?.branding;
656
656
  return /* @__PURE__ */ jsxs("div", {
657
657
  className: "dy-flex dy-flex-col dy-min-h-screen",
658
658
  children: [
@@ -783,12 +783,17 @@ function SidebarInner({ schemas, isLoading, location, logout, isEmbedded, collap
783
783
  /* @__PURE__ */ jsx(NavItem, {
784
784
  to: "/setup",
785
785
  icon: Sparkles,
786
- label: "Setup & Help",
786
+ label: /* @__PURE__ */ jsxs("div", {
787
+ className: "dy-flex dy-items-center dy-justify-between dy-w-full dy-min-w-0",
788
+ children: [/* @__PURE__ */ jsx("span", {
789
+ className: "dy-truncate",
790
+ children: "Setup & Help"
791
+ }), updateInfo?.hasUpdate && /* @__PURE__ */ jsx("span", { className: "dy-h-1.5 dy-w-1.5 dy-rounded-full dy-bg-primary dy-shrink-0 dy-ml-2" })]
792
+ }),
787
793
  active: location.pathname === "/setup",
788
794
  collapsed,
789
795
  onClick: onNavigate
790
796
  }),
791
- /* @__PURE__ */ jsx(ThemeSelector, { collapsed }),
792
797
  !isEmbedded && user && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
793
798
  asChild: true,
794
799
  children: /* @__PURE__ */ jsxs("button", {
@@ -834,26 +839,99 @@ function SidebarInner({ schemas, isLoading, location, logout, isEmbedded, collap
834
839
  })
835
840
  ]
836
841
  })] }),
837
- onToggleCollapse && !isEmbedded && /* @__PURE__ */ jsx("button", {
838
- onClick: onToggleCollapse,
839
- className: cn("dy-group/btn dy-mt-1 dy-flex dy-h-7 dy-w-full dy-items-center dy-gap-2 dy-rounded-md dy-px-2.5 dy-text-[11px] dy-font-medium dy-text-muted-foreground/45 dy-transition-colors hover:dy-bg-accent/40 hover:dy-text-muted-foreground focus-visible:dy-outline-none focus-visible:dy-ring-2 focus-visible:dy-ring-ring", collapsed ? "dy-justify-center dy-px-2" : ""),
840
- title: collapsed ? "Expand sidebar" : "Collapse sidebar",
841
- "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
842
- children: collapsed ? /* @__PURE__ */ jsx(PanelLeftOpen, { className: "dy-h-3.5 dy-w-3.5" }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PanelLeftClose, { className: "dy-h-3.5 dy-w-3.5 dy-transition-transform dy-group-hover/btn:dy--translate-x-0.5" }), /* @__PURE__ */ jsx("span", { children: "Collapse sidebar" })] })
842
+ /* @__PURE__ */ jsxs("div", {
843
+ className: cn("dy-flex dy-items-center dy-gap-1.5 dy-w-full dy-mt-1", collapsed ? "dy-flex-col dy-items-center" : "dy-flex-row dy-justify-between"),
844
+ children: [onToggleCollapse && !isEmbedded && /* @__PURE__ */ jsx("button", {
845
+ onClick: onToggleCollapse,
846
+ className: cn("dy-group/btn dy-flex dy-h-7 dy-items-center dy-gap-2 dy-rounded-md dy-px-2.5 dy-text-[11px] dy-font-medium dy-text-muted-foreground/45 dy-transition-colors hover:dy-bg-accent/40 hover:dy-text-muted-foreground focus-visible:dy-outline-none focus-visible:dy-ring-2 focus-visible:dy-ring-ring", collapsed ? "dy-justify-center dy-px-2 dy-w-full" : "dy-flex-1"),
847
+ title: collapsed ? "Expand sidebar" : "Collapse sidebar",
848
+ "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
849
+ children: collapsed ? /* @__PURE__ */ jsx(PanelLeftOpen, { className: "dy-h-3.5 dy-w-3.5" }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PanelLeftClose, { className: "dy-h-3.5 dy-w-3.5 dy-transition-transform dy-group-hover/btn:dy--translate-x-0.5" }), /* @__PURE__ */ jsx("span", {
850
+ className: "dy-truncate",
851
+ children: "Collapse"
852
+ })] })
853
+ }), /* @__PURE__ */ jsx(ThemeSelector, {
854
+ collapsed,
855
+ iconOnly: !collapsed
856
+ })]
843
857
  })
844
858
  ]
845
859
  })
846
860
  ]
847
861
  });
848
862
  }
863
+ function isNewerVersion(latest, current) {
864
+ if (latest === current) return false;
865
+ const lParts = latest.split(".").map(Number);
866
+ const cParts = current.split(".").map(Number);
867
+ for (let i = 0; i < 3; i++) {
868
+ const l = lParts[i] || 0;
869
+ const c = cParts[i] || 0;
870
+ if (l > c) return true;
871
+ if (l < c) return false;
872
+ }
873
+ return false;
874
+ }
875
+ function useUpdateCheck() {
876
+ const currentVersion = "2.5.49";
877
+ const [updateInfo, setUpdateInfo] = useState(() => {
878
+ if (typeof window === "undefined") return null;
879
+ const cacheKey = "dyrected_latest_release";
880
+ const cacheTimeKey = "dyrected_latest_release_timestamp";
881
+ const cachedVersion = localStorage.getItem(cacheKey);
882
+ const cachedTimestamp = localStorage.getItem(cacheTimeKey);
883
+ if (cachedVersion && cachedTimestamp && Date.now() - Number(cachedTimestamp) < 1440 * 60 * 1e3) return {
884
+ latestVersion: cachedVersion,
885
+ hasUpdate: isNewerVersion(cachedVersion, currentVersion)
886
+ };
887
+ return null;
888
+ });
889
+ useEffect(() => {
890
+ let cancelled = false;
891
+ const cacheKey = "dyrected_latest_release";
892
+ const cacheTimeKey = "dyrected_latest_release_timestamp";
893
+ const oneDay = 1440 * 60 * 1e3;
894
+ const cachedVersion = localStorage.getItem(cacheKey);
895
+ const cachedTimestamp = localStorage.getItem(cacheTimeKey);
896
+ if (cachedVersion && cachedTimestamp && Date.now() - Number(cachedTimestamp) < oneDay) return;
897
+ async function fetchLatest() {
898
+ try {
899
+ const res = await fetch("https://registry.npmjs.org/@dyrected/core/latest");
900
+ if (!res.ok) return;
901
+ const latest = (await res.json())?.version;
902
+ if (latest) {
903
+ localStorage.setItem(cacheKey, latest);
904
+ localStorage.setItem(cacheTimeKey, String(Date.now()));
905
+ if (!cancelled) setUpdateInfo({
906
+ latestVersion: latest,
907
+ hasUpdate: isNewerVersion(latest, currentVersion)
908
+ });
909
+ }
910
+ } catch (err) {
911
+ console.error("Failed to check for updates:", err);
912
+ }
913
+ }
914
+ fetchLatest();
915
+ return () => {
916
+ cancelled = true;
917
+ };
918
+ }, [currentVersion]);
919
+ return updateInfo;
920
+ }
849
921
  function AdminShell({ children, isEmbedded = false }) {
850
922
  const { client, logout, user } = useDyrected();
851
923
  const location = useLocation();
924
+ const updateInfo = useUpdateCheck();
852
925
  const [collapsed, setCollapsed] = useState(false);
853
926
  const [mobileOpen, setMobileOpen] = useState(false);
854
927
  useEffect(() => {
855
- setMobileOpen(false);
856
- }, [location.pathname]);
928
+ if (mobileOpen) {
929
+ const timer = setTimeout(() => {
930
+ setMobileOpen(false);
931
+ }, 0);
932
+ return () => clearTimeout(timer);
933
+ }
934
+ }, [location.pathname, mobileOpen]);
857
935
  useEffect(() => {
858
936
  document.body.style.overflow = mobileOpen ? "hidden" : "";
859
937
  return () => {
@@ -868,7 +946,7 @@ function AdminShell({ children, isEmbedded = false }) {
868
946
  },
869
947
  enabled: !!client
870
948
  });
871
- const mobileBranding = schemas?.admin?.branding;
949
+ const mobileBranding = (schemas?.admin)?.branding;
872
950
  return /* @__PURE__ */ jsx(BrandingProvider, { children: /* @__PURE__ */ jsxs("div", {
873
951
  className: cn("dy-flex dy-w-full dy-relative", isEmbedded ? "dy-h-full dy-min-h-[600px]" : "dy-min-h-screen"),
874
952
  children: [
@@ -881,7 +959,8 @@ function AdminShell({ children, isEmbedded = false }) {
881
959
  logout,
882
960
  isEmbedded,
883
961
  collapsed,
884
- onToggleCollapse: () => setCollapsed((v) => !v)
962
+ onToggleCollapse: () => setCollapsed((v) => !v),
963
+ updateInfo
885
964
  })
886
965
  }),
887
966
  mobileOpen && /* @__PURE__ */ jsx("div", {
@@ -901,7 +980,8 @@ function AdminShell({ children, isEmbedded = false }) {
901
980
  logout,
902
981
  isEmbedded,
903
982
  collapsed: false,
904
- onNavigate: () => setMobileOpen(false)
983
+ onNavigate: () => setMobileOpen(false),
984
+ updateInfo
905
985
  })]
906
986
  }),
907
987
  /* @__PURE__ */ jsxs("main", {
@@ -1029,6 +1109,31 @@ function getStatusLabel(doc) {
1029
1109
  }
1030
1110
  function Dashboard() {
1031
1111
  const { client, components, user } = useDyrected();
1112
+ const currentVersion = "2.5.49";
1113
+ const [latestVersion, setLatestVersion] = useState(() => {
1114
+ if (typeof window === "undefined") return null;
1115
+ return localStorage.getItem("dyrected_latest_release");
1116
+ });
1117
+ useEffect(() => {
1118
+ if (!latestVersion) fetch("https://registry.npmjs.org/@dyrected/core/latest").then((r) => r.json()).then((data) => {
1119
+ if (data?.version) {
1120
+ setLatestVersion(data.version);
1121
+ localStorage.setItem("dyrected_latest_release", data.version);
1122
+ localStorage.setItem("dyrected_latest_release_timestamp", String(Date.now()));
1123
+ }
1124
+ }).catch(() => {});
1125
+ }, [latestVersion]);
1126
+ const hasUpdate = latestVersion && latestVersion !== currentVersion && (() => {
1127
+ const lParts = latestVersion.split(".").map(Number);
1128
+ const cParts = currentVersion.split(".").map(Number);
1129
+ for (let i = 0; i < 3; i++) {
1130
+ const l = lParts[i] || 0;
1131
+ const c = cParts[i] || 0;
1132
+ if (l > c) return true;
1133
+ if (l < c) return false;
1134
+ }
1135
+ return false;
1136
+ })();
1032
1137
  const { data: schemas, isLoading: isLoadingSchemas } = useQuery({
1033
1138
  queryKey: ["schemas"],
1034
1139
  queryFn: () => client.getSchemas(),
@@ -1065,6 +1170,12 @@ function Dashboard() {
1065
1170
  return (b.updatedAt ? new Date(b.updatedAt).getTime() : 0) - aTime;
1066
1171
  }).slice(0, 6);
1067
1172
  const attentionItems = [
1173
+ ...hasUpdate ? [{
1174
+ key: "dyrected-update",
1175
+ title: `A system update is available (v${latestVersion})`,
1176
+ description: "Please notify your developer or site administrator to apply this update.",
1177
+ to: "/setup"
1178
+ }] : [],
1068
1179
  ...collections.filter((collection) => !collection.upload && !collection.admin?.useAsTitle).map((collection) => ({
1069
1180
  key: `title-${collection.slug}`,
1070
1181
  title: `${getCollectionLabel(collection)} needs a title field`,
@@ -6683,251 +6794,254 @@ function interpolateUrlPattern(pattern, doc) {
6683
6794
  }
6684
6795
  //#endregion
6685
6796
  //#region src/components/forms/fields/url-field.tsx
6686
- function UrlField({ schema: _schema, field, disabled, context: _context }) {
6687
- const { client, schemas } = useDyrected();
6688
- const [openPopover, setOpenPopover] = React$1.useState(false);
6689
- const [documents, setDocuments] = React$1.useState([]);
6690
- const [docsLoading, setDocsLoading] = React$1.useState(false);
6691
- const parseValue = (val) => {
6692
- if (!val) return {
6693
- mode: "external",
6694
- data: {
6695
- type: "custom",
6696
- url: "",
6697
- label: ""
6698
- }
6699
- };
6700
- if (typeof val === "string") return {
6701
- mode: "external",
6702
- data: {
6703
- type: "custom",
6704
- url: val,
6705
- label: ""
6706
- }
6707
- };
6708
- if (typeof val === "object" && val.type) return {
6709
- mode: val.type === "internal" ? "internal" : "external",
6710
- data: val
6797
+ var parseValue = (val) => {
6798
+ if (!val) return {
6799
+ type: "custom",
6800
+ url: ""
6801
+ };
6802
+ if (typeof val === "string") {
6803
+ let url = val;
6804
+ if (url.startsWith("/")) url = `${window.location.origin}${url}`;
6805
+ return {
6806
+ type: "custom",
6807
+ url
6711
6808
  };
6809
+ }
6810
+ if (typeof val === "object") {
6811
+ let url = val.url || "";
6812
+ if (url.startsWith("/")) url = `${window.location.origin}${url}`;
6712
6813
  return {
6713
- mode: "external",
6714
- data: {
6715
- type: "custom",
6716
- url: String(val),
6717
- label: ""
6718
- }
6814
+ type: val.type === "internal" ? "internal" : "custom",
6815
+ url,
6816
+ relationTo: val.relationTo,
6817
+ value: val.value,
6818
+ label: val.label
6719
6819
  };
6820
+ }
6821
+ return {
6822
+ type: "custom",
6823
+ url: String(val)
6720
6824
  };
6721
- const { mode: currentMode, data: currentData } = parseValue(field.value);
6722
- const [mode, setMode] = React$1.useState(currentMode);
6723
- const [urlValue, setUrlValue] = React$1.useState(currentData.url || "");
6825
+ };
6826
+ function UrlField({ field, disabled }) {
6827
+ const { client, schemas } = useDyrected();
6828
+ const [openPopover, setOpenPopover] = React$1.useState(false);
6829
+ const [documents, setDocuments] = React$1.useState([]);
6830
+ const [docsLoading, setDocsLoading] = React$1.useState(false);
6831
+ const currentData = parseValue(field.value);
6832
+ const [urlValue, setUrlValue] = React$1.useState(currentData.url);
6724
6833
  const [labelValue, setLabelValue] = React$1.useState(currentData.label || "");
6725
6834
  const [collectionValue, setCollectionValue] = React$1.useState(currentData.relationTo || "");
6726
6835
  const [docValue, setDocValue] = React$1.useState(currentData.value || "");
6727
- const [resolvedUrlValue, setResolvedUrlValue] = React$1.useState(currentMode === "internal" ? currentData.url || "" : "");
6728
- const [docSearch, setDocSearch] = React$1.useState("");
6729
- const collections = schemas?.collections || [];
6730
6836
  React$1.useEffect(() => {
6731
- if (collectionValue && client) {
6837
+ const next = parseValue(field.value);
6838
+ Promise.resolve().then(() => {
6839
+ setUrlValue((prev) => prev === next.url ? prev : next.url);
6840
+ setLabelValue((prev) => prev === next.label ? prev : next.label || "");
6841
+ setCollectionValue((prev) => prev === next.relationTo ? prev : next.relationTo || "");
6842
+ setDocValue((prev) => prev === next.value ? prev : next.value || "");
6843
+ });
6844
+ }, [field.value]);
6845
+ const collections = React$1.useMemo(() => schemas?.collections || [], [schemas?.collections]);
6846
+ React$1.useEffect(() => {
6847
+ if (!client || !schemas?.collections) return;
6848
+ const eligibleCollections = collections.filter((c) => c.admin?.previewUrl);
6849
+ if (eligibleCollections.length === 0) return;
6850
+ let active = true;
6851
+ Promise.resolve().then(() => {
6852
+ if (!active) return;
6732
6853
  setDocsLoading(true);
6733
6854
  setDocuments([]);
6734
- client.collection(collectionValue).find({ limit: 50 }).exec().then((res) => {
6735
- setDocuments(res.docs || []);
6736
- }).catch((err) => {
6737
- console.error("Failed to load documents:", err);
6738
- setDocuments([]);
6739
- }).finally(() => {
6740
- setDocsLoading(false);
6741
- });
6742
- }
6743
- }, [collectionValue, client]);
6744
- const handleUpdate = (newMode, updates) => {
6745
- let newValue;
6746
- if (newMode === "external") newValue = {
6747
- type: "custom",
6748
- url: updates.url !== void 0 ? updates.url : urlValue,
6749
- label: updates.label !== void 0 ? updates.label : labelValue
6855
+ });
6856
+ Promise.all(eligibleCollections.map((col) => client.collection(col.slug).find({ limit: 50 }).exec().then((res) => ({
6857
+ collection: col,
6858
+ docs: res.docs || []
6859
+ })).catch((err) => {
6860
+ console.error(`Failed to load documents for ${col.slug}:`, err);
6861
+ return {
6862
+ collection: col,
6863
+ docs: []
6864
+ };
6865
+ }))).then((results) => {
6866
+ if (!active) return;
6867
+ setDocuments(results.flatMap((r) => r.docs.map((doc) => ({
6868
+ ...doc,
6869
+ __collectionSlug: r.collection.slug,
6870
+ __collectionLabel: r.collection.labels?.plural || r.collection.slug,
6871
+ __previewUrl: r.collection.admin?.previewUrl
6872
+ }))));
6873
+ }).catch((err) => {
6874
+ console.error("Failed to load documents:", err);
6875
+ }).finally(() => {
6876
+ if (active) setDocsLoading(false);
6877
+ });
6878
+ return () => {
6879
+ active = false;
6750
6880
  };
6751
- else newValue = {
6752
- type: "internal",
6753
- url: (updates.url !== void 0 ? updates.url : resolvedUrlValue) || void 0,
6754
- relationTo: updates.relationTo || collectionValue,
6755
- value: updates.value || docValue,
6756
- label: updates.label !== void 0 ? updates.label : labelValue
6881
+ }, [
6882
+ client,
6883
+ schemas,
6884
+ collections
6885
+ ]);
6886
+ const handleUpdate = (url, label, relationTo, docId) => {
6887
+ const isInternal = !!(relationTo && docId);
6888
+ let cleanedUrl = url;
6889
+ if (isInternal && cleanedUrl.startsWith(window.location.origin)) cleanedUrl = cleanedUrl.substring(window.location.origin.length);
6890
+ const newValue = {
6891
+ type: isInternal ? "internal" : "custom",
6892
+ url: cleanedUrl || void 0,
6893
+ relationTo: isInternal ? relationTo : void 0,
6894
+ value: isInternal ? docId : void 0,
6895
+ label: label || void 0
6757
6896
  };
6758
6897
  field.onChange(newValue);
6759
6898
  };
6760
- const handleModeChange = (newMode) => {
6761
- setMode(newMode);
6762
- if (newMode === "external") {
6763
- setUrlValue(currentData.url || "");
6764
- setLabelValue(currentData.label || "");
6765
- setResolvedUrlValue("");
6766
- } else {
6767
- setCollectionValue(currentData.relationTo || "");
6768
- setDocValue(currentData.value || "");
6769
- setResolvedUrlValue(currentData.type === "internal" ? currentData.url || "" : "");
6770
- setLabelValue(currentData.label || "");
6771
- }
6772
- };
6773
- const handleExternalUrlChange = (value) => {
6899
+ const handleUrlInputChange = (value) => {
6774
6900
  setUrlValue(value);
6775
- handleUpdate("external", { url: value });
6901
+ setCollectionValue("");
6902
+ setDocValue("");
6903
+ handleUpdate(value, labelValue);
6776
6904
  };
6777
- const handleExternalLabelChange = (value) => {
6905
+ const handleLabelInputChange = (value) => {
6778
6906
  setLabelValue(value);
6779
- handleUpdate("external", { label: value });
6907
+ handleUpdate(urlValue, value, collectionValue, docValue);
6780
6908
  };
6781
- const handleCollectionChange = (value) => {
6782
- setCollectionValue(value);
6783
- setDocValue("");
6784
- setResolvedUrlValue("");
6785
- };
6786
- const handleDocumentSelect = (docId) => {
6787
- setDocValue(docId);
6909
+ const handleDocumentSelect = (doc) => {
6910
+ setDocValue(doc.id);
6911
+ setCollectionValue(doc.__collectionSlug);
6788
6912
  setOpenPopover(false);
6789
- const urlPattern = collections.find((c) => c.slug === collectionValue)?.admin?.urlPattern;
6790
- const doc = documents.find((d) => d.id === docId);
6791
- const resolvedUrl = urlPattern && doc ? interpolateUrlPattern(urlPattern, doc) : void 0;
6792
- setResolvedUrlValue(resolvedUrl || "");
6793
- handleUpdate("internal", {
6794
- value: docId,
6795
- url: resolvedUrl
6796
- });
6797
- };
6798
- const handleInternalLabelChange = (value) => {
6799
- setLabelValue(value);
6800
- handleUpdate("internal", { label: value });
6913
+ const urlPattern = doc.__previewUrl;
6914
+ let resolvedUrl = void 0;
6915
+ if (urlPattern) {
6916
+ if (typeof urlPattern === "function") resolvedUrl = urlPattern(doc, { locale: "en" });
6917
+ else if (typeof urlPattern === "string") if (urlPattern.includes("{{")) resolvedUrl = urlPattern.replace(/{{(.*?)}}/g, (_, key) => String(doc[key.trim()] || ""));
6918
+ else try {
6919
+ const context = {
6920
+ ...doc,
6921
+ siteUrl: window.location.origin
6922
+ };
6923
+ if (urlPattern.includes("+") || urlPattern.includes("?") || urlPattern.includes("==") || urlPattern.includes("siteUrl")) resolvedUrl = jexl.evalSync(urlPattern, context);
6924
+ else resolvedUrl = interpolateUrlPattern(urlPattern, doc);
6925
+ } catch {
6926
+ resolvedUrl = interpolateUrlPattern(urlPattern, doc);
6927
+ }
6928
+ }
6929
+ if (typeof resolvedUrl === "string" && resolvedUrl.startsWith("/")) resolvedUrl = `${window.location.origin}${resolvedUrl}`;
6930
+ const finalUrl = resolvedUrl || "";
6931
+ setUrlValue(finalUrl);
6932
+ handleUpdate(finalUrl, labelValue, doc.__collectionSlug, doc.id);
6801
6933
  };
6802
- const getCollectionDisplayField = (collectionSlug) => {
6934
+ const getCollectionDisplayField = React$1.useCallback((collectionSlug) => {
6803
6935
  return collections.find((c) => c.slug === collectionSlug)?.admin?.useAsTitle || "title";
6804
- };
6805
- const getDocumentDisplay = (doc, collectionSlug) => {
6936
+ }, [collections]);
6937
+ const getDocumentDisplay = React$1.useCallback((doc, collectionSlug) => {
6806
6938
  return doc[getCollectionDisplayField(collectionSlug)] || doc.name || doc.slug || doc.id;
6807
- };
6808
- const selectedDoc = documents.find((d) => d.id === docValue);
6939
+ }, [getCollectionDisplayField]);
6940
+ const filteredDocs = React$1.useMemo(() => {
6941
+ if (!urlValue) return documents;
6942
+ const query = urlValue.toLowerCase();
6943
+ return documents.filter((doc) => {
6944
+ const display = getDocumentDisplay(doc, doc.__collectionSlug).toLowerCase();
6945
+ const slug = String(doc.slug || "").toLowerCase();
6946
+ return display.includes(query) || slug.includes(query);
6947
+ });
6948
+ }, [
6949
+ documents,
6950
+ urlValue,
6951
+ getDocumentDisplay
6952
+ ]);
6953
+ const selectedDoc = documents.find((d) => d.id === docValue && d.__collectionSlug === collectionValue);
6809
6954
  const selectedCollectionLabel = collections.find((c) => c.slug === collectionValue)?.labels?.singular || collectionValue;
6810
- return /* @__PURE__ */ jsx("div", {
6955
+ return /* @__PURE__ */ jsxs("div", {
6811
6956
  className: "dy-space-y-3",
6812
- children: /* @__PURE__ */ jsxs(Tabs, {
6813
- value: mode,
6814
- onValueChange: (val) => handleModeChange(val),
6815
- children: [
6816
- /* @__PURE__ */ jsxs(TabsList, {
6817
- className: "dy-w-full dy-grid dy-grid-cols-2 dy-bg-muted dy-p-1 dy-rounded-lg dy-h-auto",
6818
- children: [/* @__PURE__ */ jsx(TabsTrigger, {
6819
- value: "internal",
6820
- disabled,
6821
- className: "dy-rounded-md",
6822
- children: "Internal Link"
6823
- }), /* @__PURE__ */ jsx(TabsTrigger, {
6824
- value: "external",
6825
- disabled,
6826
- className: "dy-rounded-md",
6827
- children: "External Link"
6828
- })]
6829
- }),
6830
- /* @__PURE__ */ jsxs(TabsContent, {
6831
- value: "external",
6832
- className: "dy-space-y-3 dy-mt-3",
6833
- children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
6834
- className: "dy-text-xs dy-font-semibold dy-text-muted-foreground dy-uppercase dy-tracking-wider dy-block dy-mb-2",
6835
- children: "URL"
6836
- }), /* @__PURE__ */ jsx(Input, {
6837
- type: "url",
6838
- value: urlValue,
6839
- onChange: (e) => handleExternalUrlChange(e.target.value),
6840
- placeholder: "https://example.com",
6841
- disabled,
6842
- className: "dy-border-border/50"
6843
- })] }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
6844
- className: "dy-text-xs dy-font-semibold dy-text-muted-foreground dy-uppercase dy-tracking-wider dy-block dy-mb-2",
6845
- children: "Link Label (Optional)"
6846
- }), /* @__PURE__ */ jsx(Input, {
6957
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs(Popover, {
6958
+ open: openPopover && !disabled,
6959
+ onOpenChange: setOpenPopover,
6960
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
6961
+ asChild: true,
6962
+ children: /* @__PURE__ */ jsx("div", {
6963
+ className: "dy-w-full",
6964
+ children: /* @__PURE__ */ jsx(Input, {
6847
6965
  type: "text",
6848
- value: labelValue,
6849
- onChange: (e) => handleExternalLabelChange(e.target.value),
6850
- placeholder: "e.g., Learn More",
6966
+ value: urlValue,
6967
+ onChange: (e) => handleUrlInputChange(e.target.value),
6968
+ onFocus: () => setOpenPopover(true),
6969
+ placeholder: "Type or paste a URL, or search pages...",
6851
6970
  disabled,
6852
- className: "dy-border-border/50"
6853
- })] })]
6854
- }),
6855
- /* @__PURE__ */ jsxs(TabsContent, {
6856
- value: "internal",
6857
- className: "dy-space-y-3 dy-mt-3",
6858
- children: [
6859
- /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
6860
- className: "dy-text-xs dy-font-semibold dy-text-muted-foreground dy-uppercase dy-tracking-wider dy-block dy-mb-2",
6861
- children: "Collection"
6862
- }), /* @__PURE__ */ jsxs(Select, {
6863
- value: collectionValue,
6864
- onValueChange: handleCollectionChange,
6865
- disabled,
6866
- children: [/* @__PURE__ */ jsx(SelectTrigger, {
6867
- className: "dy-bg-background dy-border-border/50",
6868
- children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select a collection..." })
6869
- }), /* @__PURE__ */ jsx(SelectContent, { children: collections.map((col) => /* @__PURE__ */ jsx(SelectItem, {
6870
- value: col.slug,
6871
- children: col.labels?.plural || col.slug
6872
- }, col.slug)) })]
6873
- })] }),
6874
- collectionValue && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
6875
- className: "dy-text-xs dy-font-semibold dy-text-muted-foreground dy-uppercase dy-tracking-wider dy-block dy-mb-2",
6876
- children: "Document"
6877
- }), /* @__PURE__ */ jsxs(Popover, {
6878
- open: disabled ? false : openPopover,
6879
- onOpenChange: setOpenPopover,
6880
- children: [/* @__PURE__ */ jsx(PopoverTrigger, {
6881
- asChild: true,
6882
- children: /* @__PURE__ */ jsxs(Button, {
6883
- variant: "outline",
6884
- role: "combobox",
6885
- "aria-expanded": openPopover,
6886
- disabled,
6887
- className: "dy-w-full dy-justify-between dy-font-normal dy-bg-background dy-border-border/50 dy-h-11",
6888
- children: [docsLoading ? "Loading..." : selectedDoc ? /* @__PURE__ */ jsx("span", {
6889
- className: "dy-truncate",
6890
- children: getDocumentDisplay(selectedDoc, collectionValue)
6891
- }) : /* @__PURE__ */ jsx("span", {
6892
- className: "dy-text-muted-foreground",
6893
- children: "Select a document..."
6894
- }), /* @__PURE__ */ jsx(ChevronsUpDown, { className: "dy-ml-2 dy-h-4 dy-w-4 dy-shrink-0 dy-opacity-50" })]
6895
- })
6896
- }), /* @__PURE__ */ jsx(PopoverContent, {
6897
- className: "dy-w-full dy-p-0",
6898
- align: "start",
6899
- children: /* @__PURE__ */ jsxs(Command$1, { children: [/* @__PURE__ */ jsx(CommandInput, {
6900
- placeholder: `Search ${selectedCollectionLabel}...`,
6901
- value: docSearch,
6902
- onValueChange: setDocSearch
6903
- }), /* @__PURE__ */ jsxs(CommandList, { children: [/* @__PURE__ */ jsx(CommandEmpty, { children: docsLoading ? "Searching..." : "No documents found." }), /* @__PURE__ */ jsx(CommandGroup, { children: documents.filter((doc) => {
6904
- if (!docSearch) return true;
6905
- return getDocumentDisplay(doc, collectionValue).toLowerCase().includes(docSearch.toLowerCase());
6906
- }).map((doc) => /* @__PURE__ */ jsxs(CommandItem, {
6907
- value: doc.id,
6908
- onSelect: () => handleDocumentSelect(doc.id),
6909
- children: [/* @__PURE__ */ jsx(Check, { className: cn("dy-mr-2 dy-h-4 dy-w-4", docValue === doc.id ? "dy-opacity-100" : "dy-opacity-0") }), /* @__PURE__ */ jsx("span", {
6971
+ className: "dy-bg-background dy-border-border/50 dy-h-11 dy-w-full"
6972
+ })
6973
+ })
6974
+ }), /* @__PURE__ */ jsx(PopoverContent, {
6975
+ className: "dy-p-0 dy-w-[var(--radix-popover-trigger-width)] dy-max-h-60 dy-overflow-y-auto",
6976
+ align: "start",
6977
+ onOpenAutoFocus: (e) => e.preventDefault(),
6978
+ children: /* @__PURE__ */ jsx(Command$1, {
6979
+ className: "dy-border-none",
6980
+ shouldFilter: false,
6981
+ children: /* @__PURE__ */ jsxs(CommandList, { children: [
6982
+ /* @__PURE__ */ jsx(CommandEmpty, {
6983
+ className: "dy-py-2.5 dy-px-3 dy-text-xs dy-text-muted-foreground",
6984
+ children: docsLoading ? "Loading pages..." : "No matching pages found. Press Enter to use as custom link."
6985
+ }),
6986
+ urlValue && /* @__PURE__ */ jsx(CommandGroup, {
6987
+ heading: "Custom Link",
6988
+ children: /* @__PURE__ */ jsxs(CommandItem, {
6989
+ value: `custom-${urlValue}`,
6990
+ onSelect: () => {
6991
+ handleUrlInputChange(urlValue);
6992
+ setOpenPopover(false);
6993
+ },
6994
+ className: "dy-text-primary",
6995
+ children: [
6996
+ "Use link \"",
6997
+ urlValue,
6998
+ "\""
6999
+ ]
7000
+ })
7001
+ }),
7002
+ (() => {
7003
+ const grouped = {};
7004
+ filteredDocs.forEach((doc) => {
7005
+ const key = doc.__collectionLabel;
7006
+ if (!grouped[key]) grouped[key] = [];
7007
+ grouped[key].push(doc);
7008
+ });
7009
+ return Object.entries(grouped).map(([groupName, docs]) => /* @__PURE__ */ jsx(CommandGroup, {
7010
+ heading: groupName,
7011
+ children: docs.map((doc) => /* @__PURE__ */ jsxs(CommandItem, {
7012
+ value: `${doc.__collectionSlug}-${doc.id}-${getDocumentDisplay(doc, doc.__collectionSlug)}`,
7013
+ onSelect: () => handleDocumentSelect(doc),
7014
+ children: [/* @__PURE__ */ jsx(Check, { className: cn("dy-mr-2 dy-h-4 dy-w-4", docValue === doc.id && collectionValue === doc.__collectionSlug ? "dy-opacity-100" : "dy-opacity-0") }), /* @__PURE__ */ jsx("span", {
6910
7015
  className: "dy-flex-1",
6911
- children: getDocumentDisplay(doc, collectionValue)
7016
+ children: getDocumentDisplay(doc, doc.__collectionSlug)
6912
7017
  })]
6913
- }, doc.id)) })] })] })
6914
- })]
6915
- })] }),
6916
- /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
6917
- className: "dy-text-xs dy-font-semibold dy-text-muted-foreground dy-uppercase dy-tracking-wider dy-block dy-mb-2",
6918
- children: "Link Label (Optional)"
6919
- }), /* @__PURE__ */ jsx(Input, {
6920
- type: "text",
6921
- value: labelValue,
6922
- onChange: (e) => handleInternalLabelChange(e.target.value),
6923
- placeholder: "e.g., Read More",
6924
- disabled,
6925
- className: "dy-bg-background dy-border-border/50"
6926
- })] })
6927
- ]
7018
+ }, `${doc.__collectionSlug}-${doc.id}`))
7019
+ }, groupName));
7020
+ })()
7021
+ ] })
6928
7022
  })
6929
- ]
6930
- })
7023
+ })]
7024
+ }), selectedDoc && /* @__PURE__ */ jsxs("p", {
7025
+ className: "dy-text-[11px] dy-text-muted-foreground dy-mt-1.5",
7026
+ children: ["Linked to internal page: ", /* @__PURE__ */ jsxs("span", {
7027
+ className: "dy-font-medium dy-text-foreground",
7028
+ children: [
7029
+ selectedCollectionLabel,
7030
+ ": ",
7031
+ getDocumentDisplay(selectedDoc, collectionValue)
7032
+ ]
7033
+ })]
7034
+ })] }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
7035
+ className: "dy-text-xs dy-font-semibold dy-text-muted-foreground dy-uppercase dy-tracking-wider dy-block dy-mb-2",
7036
+ children: "Link Label (Optional)"
7037
+ }), /* @__PURE__ */ jsx(Input, {
7038
+ type: "text",
7039
+ value: labelValue,
7040
+ onChange: (e) => handleLabelInputChange(e.target.value),
7041
+ placeholder: "e.g., Learn More",
7042
+ disabled,
7043
+ className: "dy-bg-background dy-border-border/50"
7044
+ })] })]
6931
7045
  });
6932
7046
  }
6933
7047
  //#endregion
@@ -10084,8 +10198,25 @@ function CollectionListPage({ slug }) {
10084
10198
  }
10085
10199
  return true;
10086
10200
  };
10201
+ const getPreviewUrl = (item) => {
10202
+ if (!schema?.admin?.previewUrl) return null;
10203
+ let previewUrl = typeof schema.admin.previewUrl === "function" ? schema.admin.previewUrl(item, { locale: "en" }) : schema.admin.previewUrl;
10204
+ if (typeof previewUrl === "string" && previewUrl.includes("{{")) previewUrl = previewUrl.replace(/{{(.*?)}}/g, (_, key) => String(item[key.trim()] || ""));
10205
+ else if (typeof previewUrl === "string") try {
10206
+ const context = {
10207
+ ...item,
10208
+ siteUrl: window.location.origin
10209
+ };
10210
+ if (previewUrl.includes("+") || previewUrl.includes("?") || previewUrl.includes("==") || previewUrl.includes("siteUrl")) previewUrl = jexl.evalSync(previewUrl, context);
10211
+ } catch (e) {
10212
+ console.warn("Preview URL eval failed:", e);
10213
+ }
10214
+ if (typeof previewUrl === "string" && previewUrl.startsWith("/")) previewUrl = `${window.location.origin}${previewUrl}`;
10215
+ return typeof previewUrl === "string" ? previewUrl : null;
10216
+ };
10087
10217
  const renderLinkedCell = (item, cell) => {
10088
10218
  const canDelete = canDeleteRow(item);
10219
+ const previewUrl = getPreviewUrl(item);
10089
10220
  return /* @__PURE__ */ jsxs("div", {
10090
10221
  className: "dy-flex dy-flex-col dy-gap-1",
10091
10222
  children: [/* @__PURE__ */ jsx(Link, {
@@ -10095,6 +10226,16 @@ function CollectionListPage({ slug }) {
10095
10226
  }), /* @__PURE__ */ jsxs("div", {
10096
10227
  className: "dy-flex dy-items-center dy-gap-2",
10097
10228
  children: [
10229
+ previewUrl && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("a", {
10230
+ href: previewUrl,
10231
+ target: "_blank",
10232
+ rel: "noopener noreferrer",
10233
+ className: "dy-text-[11px] dy-text-muted-foreground hover:dy-text-foreground dy-underline-offset-2 hover:dy-underline dy-transition-colors dy-duration-150",
10234
+ children: "View"
10235
+ }), /* @__PURE__ */ jsx("span", {
10236
+ className: "dy-text-muted-foreground/40 dy-text-[11px]",
10237
+ children: "|"
10238
+ })] }),
10098
10239
  /* @__PURE__ */ jsx(Link, {
10099
10240
  to: `/collections/${slug}/edit/${String(item.id)}`,
10100
10241
  className: "dy-text-[11px] dy-text-muted-foreground hover:dy-text-foreground dy-underline-offset-2 hover:dy-underline dy-transition-colors dy-duration-150",
@@ -10119,6 +10260,22 @@ function CollectionListPage({ slug }) {
10119
10260
  accessorKey: field.name,
10120
10261
  header: field.label || field.name,
10121
10262
  cell: ({ row }) => {
10263
+ if (field.name === "url") {
10264
+ if (schema?.admin?.previewUrl) {
10265
+ const resolved = getPreviewUrl(row.original);
10266
+ if (resolved) return /* @__PURE__ */ jsx("a", {
10267
+ href: resolved,
10268
+ target: "_blank",
10269
+ rel: "noopener noreferrer",
10270
+ className: "dy-text-xs dy-text-primary hover:dy-underline dy-underline-offset-2 dy-font-medium",
10271
+ children: resolved
10272
+ });
10273
+ }
10274
+ return /* @__PURE__ */ jsx("span", {
10275
+ className: "dy-text-muted-foreground",
10276
+ children: "-"
10277
+ });
10278
+ }
10122
10279
  const cell = /* @__PURE__ */ jsx(RenderCell, {
10123
10280
  value: row.getValue(field.name),
10124
10281
  field,
@@ -12702,21 +12859,8 @@ function GlobalEditorPage() {
12702
12859
  });
12703
12860
  }
12704
12861
  //#endregion
12705
- //#region src/pages/setup/setup-prompt.tsx
12862
+ //#region src/pages/setup/utils.ts
12706
12863
  var GUIDE_URL = "https://www.dyrected.com/guide";
12707
- var DOCS_URL = "https://docs.dyrected.com";
12708
- var steps = [
12709
- "Paste the prompt into your AI builder — the same one that owns the website code.",
12710
- "Review the content list the AI sends back. Correct anything missing or wrong, then say \"approved\".",
12711
- "Your Dyrected credentials are already in the prompt. Give them to the AI when it asks at Stage 4.",
12712
- "Test one real edit in Dyrected. If the change appears on the website, invite the client."
12713
- ];
12714
- var stepsNoCredentials = [
12715
- "Paste the prompt into your AI builder — the same one that owns the website code.",
12716
- "Review the content list the AI sends back. Correct anything missing or wrong, then say \"approved\".",
12717
- "When the AI reaches Stage 4, it will ask for your Site ID, Site API key, and Base URL.",
12718
- "Test one real edit in Dyrected. If the change appears on the website, invite the client."
12719
- ];
12720
12864
  function normalizeTechStack(techStack) {
12721
12865
  if (!techStack) return void 0;
12722
12866
  if (techStack === "next") return "nextjs";
@@ -12749,6 +12893,21 @@ Wait for my reply.`;
12749
12893
  - Base URL: ${baseUrl}`;
12750
12894
  return GENERATE_CMS_PROMPT.replace(placeholder, replacement);
12751
12895
  }
12896
+ //#endregion
12897
+ //#region src/pages/setup/setup-prompt.tsx
12898
+ var DOCS_URL = "https://docs.dyrected.com";
12899
+ var steps = [
12900
+ "Paste the prompt into your AI builder — the same one that owns the website code.",
12901
+ "Review the content list the AI sends back. Correct anything missing or wrong, then say \"approved\".",
12902
+ "Your Dyrected credentials are already in the prompt. Give them to the AI when it asks at Stage 4.",
12903
+ "Test one real edit in Dyrected. If the change appears on the website, invite the client."
12904
+ ];
12905
+ var stepsNoCredentials = [
12906
+ "Paste the prompt into your AI builder — the same one that owns the website code.",
12907
+ "Review the content list the AI sends back. Correct anything missing or wrong, then say \"approved\".",
12908
+ "When the AI reaches Stage 4, it will ask for your Site ID, Site API key, and Base URL.",
12909
+ "Test one real edit in Dyrected. If the change appears on the website, invite the client."
12910
+ ];
12752
12911
  function SetupPromptUI({ config }) {
12753
12912
  const [copied, setCopied] = useState(false);
12754
12913
  const guideUrl = buildGuideUrl(config);
@@ -12760,9 +12919,87 @@ function SetupPromptUI({ config }) {
12760
12919
  setCopied(true);
12761
12920
  window.setTimeout(() => setCopied(false), 1800);
12762
12921
  }
12922
+ const currentVersion = "2.5.49";
12923
+ const [latestVersion, setLatestVersion] = useState(() => {
12924
+ if (typeof window === "undefined") return null;
12925
+ return localStorage.getItem("dyrected_latest_release");
12926
+ });
12927
+ useEffect(() => {
12928
+ if (!latestVersion) fetch("https://registry.npmjs.org/@dyrected/core/latest").then((r) => r.json()).then((data) => {
12929
+ if (data?.version) {
12930
+ setLatestVersion(data.version);
12931
+ localStorage.setItem("dyrected_latest_release", data.version);
12932
+ localStorage.setItem("dyrected_latest_release_timestamp", String(Date.now()));
12933
+ }
12934
+ }).catch(() => {});
12935
+ }, [latestVersion]);
12936
+ const hasUpdate = latestVersion && latestVersion !== currentVersion && (() => {
12937
+ const lParts = latestVersion.split(".").map(Number);
12938
+ const cParts = currentVersion.split(".").map(Number);
12939
+ for (let i = 0; i < 3; i++) {
12940
+ const l = lParts[i] || 0;
12941
+ const c = cParts[i] || 0;
12942
+ if (l > c) return true;
12943
+ if (l < c) return false;
12944
+ }
12945
+ return false;
12946
+ })();
12763
12947
  return /* @__PURE__ */ jsxs("div", {
12764
12948
  className: "dy-mx-auto dy-max-w-3xl dy-space-y-10 dy-px-4 dy-py-8",
12765
12949
  children: [
12950
+ /* @__PURE__ */ jsxs("div", {
12951
+ className: "dy-rounded-xl dy-border dy-border-border dy-bg-muted/30 dy-p-5 dy-space-y-3",
12952
+ children: [/* @__PURE__ */ jsx("h2", {
12953
+ className: "dy-text-sm dy-font-semibold dy-text-foreground",
12954
+ children: "System Info & Updates"
12955
+ }), /* @__PURE__ */ jsxs("div", {
12956
+ className: "dy-flex dy-flex-col sm:dy-flex-row sm:dy-items-center dy-justify-between dy-gap-4 dy-text-xs",
12957
+ children: [/* @__PURE__ */ jsxs("div", {
12958
+ className: "dy-space-y-1",
12959
+ children: [/* @__PURE__ */ jsxs("p", {
12960
+ className: "dy-text-muted-foreground",
12961
+ children: ["Current version: ", /* @__PURE__ */ jsxs("span", {
12962
+ className: "dy-font-mono dy-font-semibold dy-text-foreground",
12963
+ children: ["v", currentVersion]
12964
+ })]
12965
+ }), latestVersion && /* @__PURE__ */ jsxs("p", {
12966
+ className: "dy-text-muted-foreground",
12967
+ children: ["Latest release: ", /* @__PURE__ */ jsxs("span", {
12968
+ className: "dy-font-mono dy-font-semibold dy-text-foreground",
12969
+ children: ["v", latestVersion]
12970
+ })]
12971
+ })]
12972
+ }), hasUpdate ? /* @__PURE__ */ jsxs("div", {
12973
+ className: "dy-flex dy-flex-col dy-items-start sm:dy-items-end dy-gap-1.5",
12974
+ children: [/* @__PURE__ */ jsxs("div", {
12975
+ className: "dy-flex dy-items-center dy-gap-2",
12976
+ children: [/* @__PURE__ */ jsx("span", {
12977
+ className: "dy-inline-flex dy-items-center dy-gap-1 dy-rounded-full dy-bg-primary/10 dy-px-2.5 dy-py-0.5 dy-text-[11px] dy-font-medium dy-text-primary",
12978
+ children: "Update available!"
12979
+ }), /* @__PURE__ */ jsxs("a", {
12980
+ href: "https://github.com/Dyrected/dyrected/blob/main/CHANGELOG.md",
12981
+ target: "_blank",
12982
+ rel: "noopener noreferrer",
12983
+ className: "dy-inline-flex dy-items-center dy-gap-1 dy-text-[11px] dy-text-primary dy-underline-offset-4 hover:dy-underline",
12984
+ children: ["View changelog", /* @__PURE__ */ jsx(ArrowUpRight, { className: "dy-h-3 dy-w-3" })]
12985
+ })]
12986
+ }), /* @__PURE__ */ jsxs("p", {
12987
+ className: "dy-text-muted-foreground",
12988
+ children: [
12989
+ "Run ",
12990
+ /* @__PURE__ */ jsx("code", {
12991
+ className: "dy-bg-black/5 dark:dy-bg-white/5 dy-px-1 dy-py-0.5 dy-rounded dy-font-mono",
12992
+ children: "npx dyrected upgrade"
12993
+ }),
12994
+ " to update."
12995
+ ]
12996
+ })]
12997
+ }) : /* @__PURE__ */ jsx("span", {
12998
+ className: "dy-inline-flex dy-items-center dy-gap-1 dy-rounded-full dy-bg-green-500/10 dy-px-2.5 dy-py-0.5 dy-text-[11px] dy-font-medium dy-text-green-600 dark:dy-text-green-400",
12999
+ children: "Up to date"
13000
+ })]
13001
+ })]
13002
+ }),
12766
13003
  /* @__PURE__ */ jsxs("div", {
12767
13004
  className: "dy-space-y-2",
12768
13005
  children: [/* @__PURE__ */ jsx("h1", {
@@ -10,5 +10,4 @@ export interface SetupPromptConfig {
10
10
  export interface SetupPromptProps {
11
11
  config: SetupPromptConfig;
12
12
  }
13
- export declare function buildGuideUrl(config: SetupPromptConfig): string;
14
13
  export declare function SetupPromptUI({ config }: SetupPromptProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ import { SetupPromptConfig } from './setup-prompt';
2
+ export declare function normalizeTechStack(techStack?: string): string | undefined;
3
+ export declare function buildGuideUrl(config: SetupPromptConfig): string;
4
+ export declare function buildPrompt(config: SetupPromptConfig): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/admin",
3
- "version": "2.5.48",
3
+ "version": "2.5.51",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -64,9 +64,9 @@
64
64
  "tailwind-merge": "^3.5.0",
65
65
  "tailwindcss-animate": "^1.0.7",
66
66
  "zod": "^3.25.76",
67
- "@dyrected/core": "^2.5.48",
68
- "@dyrected/knowledge": "^0.2.5",
69
- "@dyrected/sdk": "^2.5.48"
67
+ "@dyrected/core": "^2.5.51",
68
+ "@dyrected/knowledge": "^0.2.8",
69
+ "@dyrected/sdk": "^2.5.51"
70
70
  },
71
71
  "peerDependencies": {
72
72
  "@tanstack/react-query": "^5.0.0",