camox 0.48.0 → 0.52.1

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 (127) hide show
  1. package/dist/core/createApp.d.ts +1 -0
  2. package/dist/core/createApp.js +1 -0
  3. package/dist/core/editing/contentEdits.js +78 -0
  4. package/dist/core/editing/createEditableBlock.js +277 -311
  5. package/dist/core/layoutContext.d.ts +1 -0
  6. package/dist/core/lib/contentType.js +2 -2
  7. package/dist/core/recordItem.js +3 -3
  8. package/dist/features/canvas/CamoxCanvas.js +15 -15
  9. package/dist/features/canvas/CanvasLoadingScreen.js +15 -11
  10. package/dist/features/canvas/CanvasPageFrame.js +81 -87
  11. package/dist/features/canvas/CanvasPreviewPageSlot.js +1 -1
  12. package/dist/features/canvas/DerivedPagePathInput.js +12 -12
  13. package/dist/features/canvas/canvasOverlayGeometry.js +1 -1
  14. package/dist/features/canvas/canvasStore.js +2 -2
  15. package/dist/features/canvas/sidebar/CanvasInspector.js +1 -1
  16. package/dist/features/canvas/sidebar/CuratedPageInspector.js +1 -1
  17. package/dist/features/canvas/sidebar/LayoutPageBlocks.js +23 -31
  18. package/dist/features/canvas/useCanvasCamera.js +1 -1
  19. package/dist/features/canvas/useDiscoveredPreviewPaths.js +16 -9
  20. package/dist/features/content/ContentAssets.js +84 -88
  21. package/dist/features/page/CuratedPageContent.js +1 -1
  22. package/dist/features/preview/CamoxPreview.d.ts +8 -4
  23. package/dist/features/preview/CamoxPreview.js +258 -443
  24. package/dist/features/preview/EditablePageContent.js +244 -2
  25. package/dist/features/preview/EditablePageExperience.js +19 -13
  26. package/dist/features/preview/components/AddBlockSheet.js +55 -15
  27. package/dist/features/preview/components/BlockActionsPopover.js +23 -51
  28. package/dist/features/preview/components/BlockPeek.js +66 -0
  29. package/dist/features/preview/components/BlockThumbnail.js +134 -119
  30. package/dist/features/preview/components/CanvasPreview.js +1 -1
  31. package/dist/features/preview/components/CreatePageModal.js +150 -156
  32. package/dist/features/preview/components/FieldToolbar.js +1 -1
  33. package/dist/features/preview/components/Frame.js +27 -4
  34. package/dist/features/preview/components/MobilePreviewDrawer.js +6 -9
  35. package/dist/features/preview/components/PageInfo.js +161 -126
  36. package/dist/features/preview/components/PagePicker.js +2 -2
  37. package/dist/features/preview/components/PageTree.js +174 -190
  38. package/dist/features/preview/components/PreviewPanel.js +13 -67
  39. package/dist/features/preview/components/PreviewToolbar.js +1 -1
  40. package/dist/features/preview/components/ReferenceFieldEditor.js +4 -4
  41. package/dist/features/preview/components/ReferenceListFieldEditor.js +4 -4
  42. package/dist/features/preview/components/RemovedBlockPlaceholder.js +37 -70
  43. package/dist/features/preview/components/RepeatableItemsList.js +1 -1
  44. package/dist/features/preview/components/SelectionEditor.js +248 -256
  45. package/dist/features/preview/components/StudioEditingFallback.js +22 -26
  46. package/dist/features/preview/components/useFieldWriter.js +19 -19
  47. package/dist/features/preview/components/useRecordView.js +105 -147
  48. package/dist/features/preview/components/useRepeatableItemActions.js +0 -2
  49. package/dist/features/preview/pageRecord.js +30 -24
  50. package/dist/features/preview/previewConstants.js +2 -8
  51. package/dist/features/preview/previewStore.js +2 -0
  52. package/dist/features/preview/studioNotice.js +20 -0
  53. package/dist/features/preview/usePreviewViewportActions.js +1 -1
  54. package/dist/features/provider/AuthenticatedCamoxProvider.js +47 -31
  55. package/dist/features/provider/components/CommandPalette.js +2 -5
  56. package/dist/features/provider/signInShortcut.js +1 -2
  57. package/dist/features/provider/useAdminShortcuts.js +47 -53
  58. package/dist/features/renderer/RendererApp.js +542 -0
  59. package/dist/features/renderer/RendererFrames.js +450 -0
  60. package/dist/features/renderer/definitionAnswers.js +26 -0
  61. package/dist/features/renderer/protocol.js +35 -0
  62. package/dist/features/renderer/rendererHost.js +249 -0
  63. package/dist/features/renderer/studioConnection.js +186 -0
  64. package/dist/features/routes/pageRuntime.d.ts +2 -1
  65. package/dist/features/routes/pageRuntime.js +34 -22
  66. package/dist/features/runtime/RuntimeChrome.js +2 -5
  67. package/dist/features/runtime/assetPrefix.js +14 -0
  68. package/dist/features/runtime/breadcrumbs.js +1 -1
  69. package/dist/features/runtime/editingFallback.js +4 -8
  70. package/dist/features/runtime/livePages.js +1 -1
  71. package/dist/features/runtime/pageApp.js +57 -83
  72. package/dist/features/runtime/pageClient.js +1 -1
  73. package/dist/features/runtime/pageNavigation.js +3 -2
  74. package/dist/features/runtime/publicCache.js +95 -0
  75. package/dist/features/runtime/publishingDraft.js +23 -0
  76. package/dist/features/runtime/rendererClient.d.ts +8 -0
  77. package/dist/features/runtime/rendererClient.js +77 -0
  78. package/dist/features/runtime/runtime.d.ts +22 -3
  79. package/dist/features/runtime/runtime.js +209 -96
  80. package/dist/features/runtime/runtimeRoutes.d.ts +2 -0
  81. package/dist/features/runtime/runtimeRoutes.js +2 -0
  82. package/dist/features/runtime/worker.d.ts +14 -1
  83. package/dist/features/runtime/worker.js +34 -4
  84. package/dist/features/settings/AnalyticsSettings.js +622 -0
  85. package/dist/features/settings/CamoxSettings.js +95 -0
  86. package/dist/features/settings/FaviconSettings.js +254 -0
  87. package/dist/features/settings/ProjectNameSettings.js +145 -0
  88. package/dist/features/settings/SettingsPart.js +73 -0
  89. package/dist/features/settings/SocialProfilesSettings.js +245 -0
  90. package/dist/features/settings/useProjectMutation.js +61 -0
  91. package/dist/features/studio/FollowAgentProvider.js +10 -10
  92. package/dist/features/studio/components/DraftsControl.js +136 -131
  93. package/dist/features/studio/components/LocalePicker.js +178 -174
  94. package/dist/features/studio/components/Navbar.js +107 -66
  95. package/dist/features/studio/components/ProjectMenu.js +119 -171
  96. package/dist/features/studio/components/ReviewDraftSheet.js +1151 -991
  97. package/dist/features/studio/components/ThreadMessages.js +100 -65
  98. package/dist/features/studio/components/UserButton.js +2 -2
  99. package/dist/features/studio/routes.js +3 -1
  100. package/dist/features/studio/showInCanvas.js +129 -0
  101. package/dist/features/vite/definitionsSync.js +5 -6
  102. package/dist/features/vite/hostedChecks.js +127 -0
  103. package/dist/features/vite/runtimeDev.js +58 -17
  104. package/dist/features/vite/staticAssetHeaders.js +52 -0
  105. package/dist/features/vite/vite.d.ts +1 -1
  106. package/dist/features/vite/vite.js +49 -9
  107. package/dist/hooks/use-page-destinations.js +10 -10
  108. package/dist/lib/api-client-server.d.ts +1 -1
  109. package/dist/lib/api-client.d.ts +2 -1
  110. package/dist/lib/draft-scope.d.ts +1 -0
  111. package/dist/lib/locale-path.d.ts +3 -11
  112. package/dist/lib/locale-path.js +3 -27
  113. package/dist/lib/queries.js +7 -1
  114. package/dist/lib/revisions.js +20 -0
  115. package/dist/lib/shortcuts.js +29 -0
  116. package/dist/lib/studio-definitions.js +160 -0
  117. package/dist/lib/studio-reads.d.ts +1 -0
  118. package/dist/lib/studio-reads.js +65 -21
  119. package/dist/lib/use-project-room.js +16 -10
  120. package/dist/lib/utils.js +1 -104
  121. package/dist/package.js +1 -1
  122. package/dist/studio.css +1 -1
  123. package/package.json +9 -4
  124. package/skills/camox/SKILL.md +4 -0
  125. package/skills/camox/references/artifacts.md +1 -1
  126. package/dist/features/studio/components/FaviconDialog.js +0 -318
  127. package/dist/features/studio/components/SocialProfilesDialog.js +0 -342
@@ -0,0 +1,254 @@
1
+ import { getApiUrl } from "../../lib/api-client.js";
2
+ import { reads, useStudioCache } from "../../lib/studio-reads.js";
3
+ import { buildDefaultFaviconUrl, buildFaviconUrl } from "../routes/pageRuntime.js";
4
+ import { SettingsPart } from "./SettingsPart.js";
5
+ import { useProjectMutation } from "./useProjectMutation.js";
6
+ import { c } from "react/compiler-runtime";
7
+ import * as React from "react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ import { Button } from "@camox/ui/button";
10
+ import { Trash2 } from "lucide-react";
11
+ import { toast } from "@camox/ui/toaster";
12
+ import { Spinner } from "@camox/ui/spinner";
13
+ import { FaviconDropZone, validateFaviconFile } from "@camox/ui/favicon-drop-zone";
14
+
15
+ //#region src/features/settings/FaviconSettings.tsx
16
+ /** Points the studio tab's own icon at the saved favicon, so the change shows without a reload. */
17
+ function refreshDocumentFavicon(href) {
18
+ for (const link of document.querySelectorAll("link[rel~=\"icon\"]")) link.href = href;
19
+ }
20
+ function FaviconSettings(t0) {
21
+ const $ = c(51);
22
+ const { project } = t0;
23
+ const studioCache = useStudioCache();
24
+ const [pending, setPending] = React.useState(null);
25
+ const [hasCurrent, setHasCurrent] = React.useState(false);
26
+ let t1;
27
+ if ($[0] !== project) {
28
+ t1 = buildFaviconUrl(getApiUrl(), project, { withDefault: false });
29
+ $[0] = project;
30
+ $[1] = t1;
31
+ } else t1 = $[1];
32
+ const currentUrl = t1;
33
+ let t2;
34
+ let t3;
35
+ if ($[2] !== currentUrl) {
36
+ t2 = () => {
37
+ const probe = new Image();
38
+ probe.onload = () => setHasCurrent(true);
39
+ probe.onerror = () => setHasCurrent(false);
40
+ probe.src = currentUrl;
41
+ return () => {
42
+ probe.onload = null;
43
+ probe.onerror = null;
44
+ };
45
+ };
46
+ t3 = [currentUrl];
47
+ $[2] = currentUrl;
48
+ $[3] = t2;
49
+ $[4] = t3;
50
+ } else {
51
+ t2 = $[3];
52
+ t3 = $[4];
53
+ }
54
+ React.useEffect(t2, t3);
55
+ const pendingFile = pending?.type === "upload" ? pending.file : null;
56
+ let t4;
57
+ if ($[5] !== pendingFile) {
58
+ t4 = pendingFile ? URL.createObjectURL(pendingFile) : null;
59
+ $[5] = pendingFile;
60
+ $[6] = t4;
61
+ } else t4 = $[6];
62
+ const pendingUrl = t4;
63
+ let t5;
64
+ let t6;
65
+ if ($[7] !== pendingUrl) {
66
+ t5 = () => {
67
+ if (!pendingUrl) return;
68
+ return () => URL.revokeObjectURL(pendingUrl);
69
+ };
70
+ t6 = [pendingUrl];
71
+ $[7] = pendingUrl;
72
+ $[8] = t5;
73
+ $[9] = t6;
74
+ } else {
75
+ t5 = $[8];
76
+ t6 = $[9];
77
+ }
78
+ React.useEffect(t5, t6);
79
+ let t7;
80
+ if ($[10] !== project || $[11] !== studioCache) {
81
+ t7 = () => {
82
+ const updated = studioCache.get(reads.project({ projectUuid: project.uuid }));
83
+ refreshDocumentFavicon(buildFaviconUrl(getApiUrl(), updated ?? project));
84
+ setPending(null);
85
+ };
86
+ $[10] = project;
87
+ $[11] = studioCache;
88
+ $[12] = t7;
89
+ } else t7 = $[12];
90
+ const onSuccess = t7;
91
+ let t8;
92
+ if ($[13] !== onSuccess) {
93
+ t8 = {
94
+ success: "Favicon updated",
95
+ failure: "Could not save the favicon",
96
+ onSuccess
97
+ };
98
+ $[13] = onSuccess;
99
+ $[14] = t8;
100
+ } else t8 = $[14];
101
+ const upload = useProjectMutation(project, "uploadFavicon", t8);
102
+ let t9;
103
+ if ($[15] !== onSuccess) {
104
+ t9 = {
105
+ success: "Favicon removed",
106
+ failure: "Could not save the favicon",
107
+ onSuccess
108
+ };
109
+ $[15] = onSuccess;
110
+ $[16] = t9;
111
+ } else t9 = $[16];
112
+ const remove = useProjectMutation(project, "deleteFavicon", t9);
113
+ const isSaving = upload.isPending || remove.isPending;
114
+ let t10;
115
+ if ($[17] === Symbol.for("react.memo_cache_sentinel")) {
116
+ t10 = (file) => {
117
+ const error = validateFaviconFile(file);
118
+ if (error) {
119
+ toast.error(error);
120
+ return;
121
+ }
122
+ setPending({
123
+ type: "upload",
124
+ file
125
+ });
126
+ };
127
+ $[17] = t10;
128
+ } else t10 = $[17];
129
+ const choose = t10;
130
+ let t11;
131
+ if ($[18] !== pending || $[19] !== project || $[20] !== remove || $[21] !== upload) {
132
+ t11 = () => {
133
+ if (pending?.type === "upload") {
134
+ upload.mutate({
135
+ projectId: project.id,
136
+ file: pending.file
137
+ });
138
+ return;
139
+ }
140
+ if (pending?.type === "remove") remove.mutate({ projectId: project.id });
141
+ };
142
+ $[18] = pending;
143
+ $[19] = project;
144
+ $[20] = remove;
145
+ $[21] = upload;
146
+ $[22] = t11;
147
+ } else t11 = $[22];
148
+ const save = t11;
149
+ const showsFavicon = pending ? pending.type === "upload" : hasCurrent;
150
+ let t12;
151
+ if ($[23] !== currentUrl || $[24] !== pendingUrl || $[25] !== showsFavicon) {
152
+ t12 = pendingUrl ?? (showsFavicon ? currentUrl : buildDefaultFaviconUrl(getApiUrl()));
153
+ $[23] = currentUrl;
154
+ $[24] = pendingUrl;
155
+ $[25] = showsFavicon;
156
+ $[26] = t12;
157
+ } else t12 = $[26];
158
+ const previewUrl = t12;
159
+ const prompt = pendingFile ? pendingFile.name : showsFavicon ? "Drop a new favicon, or click to browse" : "Drop your favicon here, or click to browse";
160
+ let t13;
161
+ if ($[27] !== isSaving || $[28] !== previewUrl || $[29] !== prompt) {
162
+ t13 = /* @__PURE__ */ jsx(FaviconDropZone, {
163
+ previewUrl,
164
+ prompt,
165
+ disabled: isSaving,
166
+ onFileSelected: choose
167
+ });
168
+ $[27] = isSaving;
169
+ $[28] = previewUrl;
170
+ $[29] = prompt;
171
+ $[30] = t13;
172
+ } else t13 = $[30];
173
+ const t14 = !pending || isSaving;
174
+ let t15;
175
+ if ($[31] !== isSaving) {
176
+ t15 = isSaving && /* @__PURE__ */ jsx(Spinner, {});
177
+ $[31] = isSaving;
178
+ $[32] = t15;
179
+ } else t15 = $[32];
180
+ let t16;
181
+ if ($[33] !== save || $[34] !== t14 || $[35] !== t15) {
182
+ t16 = /* @__PURE__ */ jsxs(Button, {
183
+ type: "button",
184
+ onClick: save,
185
+ disabled: t14,
186
+ children: [t15, "Save"]
187
+ });
188
+ $[33] = save;
189
+ $[34] = t14;
190
+ $[35] = t15;
191
+ $[36] = t16;
192
+ } else t16 = $[36];
193
+ let t17;
194
+ if ($[37] !== isSaving || $[38] !== pending) {
195
+ t17 = pending && /* @__PURE__ */ jsx(Button, {
196
+ type: "button",
197
+ variant: "outline",
198
+ disabled: isSaving,
199
+ onClick: () => setPending(null),
200
+ children: "Cancel"
201
+ });
202
+ $[37] = isSaving;
203
+ $[38] = pending;
204
+ $[39] = t17;
205
+ } else t17 = $[39];
206
+ let t18;
207
+ if ($[40] !== hasCurrent || $[41] !== isSaving || $[42] !== showsFavicon) {
208
+ t18 = showsFavicon && /* @__PURE__ */ jsxs(Button, {
209
+ type: "button",
210
+ variant: "ghost",
211
+ className: "ml-auto",
212
+ disabled: isSaving,
213
+ onClick: () => setPending(hasCurrent ? { type: "remove" } : null),
214
+ children: [/* @__PURE__ */ jsx(Trash2, {}), "Remove favicon"]
215
+ });
216
+ $[40] = hasCurrent;
217
+ $[41] = isSaving;
218
+ $[42] = showsFavicon;
219
+ $[43] = t18;
220
+ } else t18 = $[43];
221
+ let t19;
222
+ if ($[44] !== t16 || $[45] !== t17 || $[46] !== t18) {
223
+ t19 = /* @__PURE__ */ jsxs("div", {
224
+ className: "flex flex-wrap gap-2",
225
+ children: [
226
+ t16,
227
+ t17,
228
+ t18
229
+ ]
230
+ });
231
+ $[44] = t16;
232
+ $[45] = t17;
233
+ $[46] = t18;
234
+ $[47] = t19;
235
+ } else t19 = $[47];
236
+ let t20;
237
+ if ($[48] !== t13 || $[49] !== t19) {
238
+ t20 = /* @__PURE__ */ jsx(SettingsPart, {
239
+ title: "Favicon",
240
+ description: "The icon shown in browser tabs and bookmarks.",
241
+ children: /* @__PURE__ */ jsxs("div", {
242
+ className: "flex flex-col gap-4",
243
+ children: [t13, t19]
244
+ })
245
+ });
246
+ $[48] = t13;
247
+ $[49] = t19;
248
+ $[50] = t20;
249
+ } else t20 = $[50];
250
+ return t20;
251
+ }
252
+
253
+ //#endregion
254
+ export { FaviconSettings };
@@ -0,0 +1,145 @@
1
+ import { SettingsPart } from "./SettingsPart.js";
2
+ import { useProjectMutation } from "./useProjectMutation.js";
3
+ import { c } from "react/compiler-runtime";
4
+ import * as React from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { Button } from "@camox/ui/button";
7
+ import { Input } from "@camox/ui/input";
8
+ import { Label } from "@camox/ui/label";
9
+ import { Spinner } from "@camox/ui/spinner";
10
+
11
+ //#region src/features/settings/ProjectNameSettings.tsx
12
+ function ProjectNameSettings(t0) {
13
+ const $ = c(27);
14
+ const { project } = t0;
15
+ const [name, setName] = React.useState(project.name);
16
+ const [error, setError] = React.useState(null);
17
+ let t1;
18
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
19
+ t1 = {
20
+ success: "Project name updated",
21
+ failure: "Could not update the project name"
22
+ };
23
+ $[0] = t1;
24
+ } else t1 = $[0];
25
+ const save = useProjectMutation(project, "update", t1);
26
+ let t2;
27
+ if ($[1] !== name) {
28
+ t2 = name.trim();
29
+ $[1] = name;
30
+ $[2] = t2;
31
+ } else t2 = $[2];
32
+ const trimmedName = t2;
33
+ let t3;
34
+ if ($[3] !== project.id || $[4] !== save || $[5] !== trimmedName) {
35
+ t3 = (event) => {
36
+ event.preventDefault();
37
+ if (!trimmedName) {
38
+ setError("Project name is required");
39
+ return;
40
+ }
41
+ save.mutate({
42
+ id: project.id,
43
+ name: trimmedName
44
+ });
45
+ };
46
+ $[3] = project.id;
47
+ $[4] = save;
48
+ $[5] = trimmedName;
49
+ $[6] = t3;
50
+ } else t3 = $[6];
51
+ const submit = t3;
52
+ let t4;
53
+ if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
54
+ t4 = /* @__PURE__ */ jsx(Label, {
55
+ htmlFor: "camox-project-name",
56
+ children: "Project name"
57
+ });
58
+ $[7] = t4;
59
+ } else t4 = $[7];
60
+ const t5 = error ? true : void 0;
61
+ let t6;
62
+ if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
63
+ t6 = (event_0) => {
64
+ setName(event_0.target.value);
65
+ setError(null);
66
+ };
67
+ $[8] = t6;
68
+ } else t6 = $[8];
69
+ let t7;
70
+ if ($[9] !== name || $[10] !== save.isPending || $[11] !== t5) {
71
+ t7 = /* @__PURE__ */ jsx(Input, {
72
+ id: "camox-project-name",
73
+ value: name,
74
+ "aria-invalid": t5,
75
+ disabled: save.isPending,
76
+ onChange: t6
77
+ });
78
+ $[9] = name;
79
+ $[10] = save.isPending;
80
+ $[11] = t5;
81
+ $[12] = t7;
82
+ } else t7 = $[12];
83
+ let t8;
84
+ if ($[13] !== error) {
85
+ t8 = error && /* @__PURE__ */ jsx("p", {
86
+ className: "text-destructive text-xs",
87
+ children: error
88
+ });
89
+ $[13] = error;
90
+ $[14] = t8;
91
+ } else t8 = $[14];
92
+ let t9;
93
+ if ($[15] !== t7 || $[16] !== t8) {
94
+ t9 = /* @__PURE__ */ jsxs("div", {
95
+ className: "flex flex-col gap-2",
96
+ children: [
97
+ t4,
98
+ t7,
99
+ t8
100
+ ]
101
+ });
102
+ $[15] = t7;
103
+ $[16] = t8;
104
+ $[17] = t9;
105
+ } else t9 = $[17];
106
+ const t10 = trimmedName === project.name || save.isPending;
107
+ let t11;
108
+ if ($[18] !== save.isPending) {
109
+ t11 = save.isPending && /* @__PURE__ */ jsx(Spinner, {});
110
+ $[18] = save.isPending;
111
+ $[19] = t11;
112
+ } else t11 = $[19];
113
+ let t12;
114
+ if ($[20] !== t10 || $[21] !== t11) {
115
+ t12 = /* @__PURE__ */ jsxs(Button, {
116
+ type: "submit",
117
+ className: "self-start",
118
+ disabled: t10,
119
+ children: [t11, "Save changes"]
120
+ });
121
+ $[20] = t10;
122
+ $[21] = t11;
123
+ $[22] = t12;
124
+ } else t12 = $[22];
125
+ let t13;
126
+ if ($[23] !== submit || $[24] !== t12 || $[25] !== t9) {
127
+ t13 = /* @__PURE__ */ jsx(SettingsPart, {
128
+ title: "Project name",
129
+ description: "How the project is named in Camox.",
130
+ children: /* @__PURE__ */ jsxs("form", {
131
+ onSubmit: submit,
132
+ className: "flex flex-col gap-4",
133
+ children: [t9, t12]
134
+ })
135
+ });
136
+ $[23] = submit;
137
+ $[24] = t12;
138
+ $[25] = t9;
139
+ $[26] = t13;
140
+ } else t13 = $[26];
141
+ return t13;
142
+ }
143
+
144
+ //#endregion
145
+ export { ProjectNameSettings };
@@ -0,0 +1,73 @@
1
+ import { c } from "react/compiler-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+
4
+ //#region src/features/settings/SettingsPart.tsx
5
+ /** One part of the Settings Section: what it sets, beside the form that sets it. */
6
+ function SettingsPart(t0) {
7
+ const $ = c(17);
8
+ const { title, description, children } = t0;
9
+ let t1;
10
+ if ($[0] !== title) {
11
+ let t2;
12
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
13
+ t2 = /\W+/g;
14
+ $[2] = t2;
15
+ } else t2 = $[2];
16
+ t1 = title.toLowerCase().replaceAll(t2, "-");
17
+ $[0] = title;
18
+ $[1] = t1;
19
+ } else t1 = $[1];
20
+ const id = `camox-settings-${t1}`;
21
+ let t2;
22
+ if ($[3] !== id || $[4] !== title) {
23
+ t2 = /* @__PURE__ */ jsx("h2", {
24
+ id,
25
+ className: "font-semibold",
26
+ children: title
27
+ });
28
+ $[3] = id;
29
+ $[4] = title;
30
+ $[5] = t2;
31
+ } else t2 = $[5];
32
+ let t3;
33
+ if ($[6] !== description) {
34
+ t3 = /* @__PURE__ */ jsx("p", {
35
+ className: "text-muted-foreground mt-1 text-sm",
36
+ children: description
37
+ });
38
+ $[6] = description;
39
+ $[7] = t3;
40
+ } else t3 = $[7];
41
+ let t4;
42
+ if ($[8] !== t2 || $[9] !== t3) {
43
+ t4 = /* @__PURE__ */ jsxs("div", { children: [t2, t3] });
44
+ $[8] = t2;
45
+ $[9] = t3;
46
+ $[10] = t4;
47
+ } else t4 = $[10];
48
+ let t5;
49
+ if ($[11] !== children) {
50
+ t5 = /* @__PURE__ */ jsx("div", {
51
+ className: "md:col-span-2",
52
+ children
53
+ });
54
+ $[11] = children;
55
+ $[12] = t5;
56
+ } else t5 = $[12];
57
+ let t6;
58
+ if ($[13] !== id || $[14] !== t4 || $[15] !== t5) {
59
+ t6 = /* @__PURE__ */ jsxs("section", {
60
+ "aria-labelledby": id,
61
+ className: "grid gap-4 md:grid-cols-3 md:gap-8",
62
+ children: [t4, t5]
63
+ });
64
+ $[13] = id;
65
+ $[14] = t4;
66
+ $[15] = t5;
67
+ $[16] = t6;
68
+ } else t6 = $[16];
69
+ return t6;
70
+ }
71
+
72
+ //#endregion
73
+ export { SettingsPart };
@@ -0,0 +1,245 @@
1
+ import { SettingsPart } from "./SettingsPart.js";
2
+ import { useProjectMutation } from "./useProjectMutation.js";
3
+ import { c } from "react/compiler-runtime";
4
+ import * as React from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { Button } from "@camox/ui/button";
7
+ import { Input } from "@camox/ui/input";
8
+ import { ArrowDown, ArrowUp, Plus, Trash2 } from "lucide-react";
9
+ import { Spinner } from "@camox/ui/spinner";
10
+ import { socialProfileUrlSchema } from "@camox/api-contract/projects";
11
+
12
+ //#region src/features/settings/SocialProfilesSettings.tsx
13
+ let nextRowKey = 0;
14
+ const row = (url) => ({
15
+ key: nextRowKey++,
16
+ url
17
+ });
18
+ function SocialProfilesSettings(t0) {
19
+ const $ = c(33);
20
+ const { project } = t0;
21
+ let t1;
22
+ if ($[0] !== project) {
23
+ t1 = () => project.socialProfiles.map(row);
24
+ $[0] = project;
25
+ $[1] = t1;
26
+ } else t1 = $[1];
27
+ const [rows, setRows] = React.useState(t1);
28
+ const [showErrors, setShowErrors] = React.useState(false);
29
+ let t2;
30
+ if ($[2] !== rows) {
31
+ t2 = rows.map(_temp).filter(_temp2);
32
+ $[2] = rows;
33
+ $[3] = t2;
34
+ } else t2 = $[3];
35
+ const filled = t2;
36
+ let t3;
37
+ if ($[4] !== filled) {
38
+ t3 = {
39
+ success: "Social profiles updated",
40
+ failure: "Could not save the social profiles",
41
+ onSuccess: () => {
42
+ setRows(filled);
43
+ setShowErrors(false);
44
+ }
45
+ };
46
+ $[4] = filled;
47
+ $[5] = t3;
48
+ } else t3 = $[5];
49
+ const save = useProjectMutation(project, "updateSocialProfiles", t3);
50
+ const errorOf = _temp3;
51
+ let t4;
52
+ if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
53
+ t4 = (key, url_0) => setRows((current) => current.map((entry_0) => entry_0.key === key ? {
54
+ ...entry_0,
55
+ url: url_0
56
+ } : entry_0));
57
+ $[6] = t4;
58
+ } else t4 = $[6];
59
+ const update = t4;
60
+ let t5;
61
+ if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
62
+ t5 = (key_0) => setRows((current_0) => current_0.filter((entry_1) => entry_1.key !== key_0));
63
+ $[7] = t5;
64
+ } else t5 = $[7];
65
+ const remove = t5;
66
+ let t6;
67
+ if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
68
+ t6 = (index, by) => setRows((current_1) => {
69
+ const next = [...current_1];
70
+ const [moved] = next.splice(index, 1);
71
+ next.splice(index + by, 0, moved);
72
+ return next;
73
+ });
74
+ $[8] = t6;
75
+ } else t6 = $[8];
76
+ const move = t6;
77
+ let t7;
78
+ if ($[9] !== filled || $[10] !== project || $[11] !== save) {
79
+ t7 = (event) => {
80
+ event.preventDefault();
81
+ if (filled.some((entry_2) => errorOf(entry_2.url))) {
82
+ setShowErrors(true);
83
+ return;
84
+ }
85
+ save.mutate({
86
+ id: project.id,
87
+ socialProfiles: filled.map(_temp4)
88
+ });
89
+ };
90
+ $[9] = filled;
91
+ $[10] = project;
92
+ $[11] = save;
93
+ $[12] = t7;
94
+ } else t7 = $[12];
95
+ const submit = t7;
96
+ let t8;
97
+ if ($[13] !== rows || $[14] !== save.isPending || $[15] !== showErrors) {
98
+ t8 = rows.length > 0 && /* @__PURE__ */ jsx("ol", {
99
+ className: "flex flex-col gap-2",
100
+ children: rows.map((entry_4, index_0) => {
101
+ const error = showErrors && entry_4.url.trim() ? errorOf(entry_4.url) : void 0;
102
+ return /* @__PURE__ */ jsxs("li", {
103
+ className: "flex flex-col gap-1",
104
+ children: [/* @__PURE__ */ jsxs("div", {
105
+ className: "flex items-center gap-1",
106
+ children: [
107
+ /* @__PURE__ */ jsx(Input, {
108
+ type: "url",
109
+ value: entry_4.url,
110
+ placeholder: "https://www.linkedin.com/company/…",
111
+ "aria-label": `Profile ${index_0 + 1}`,
112
+ "aria-invalid": error ? true : void 0,
113
+ disabled: save.isPending,
114
+ onChange: (event_0) => update(entry_4.key, event_0.target.value)
115
+ }),
116
+ /* @__PURE__ */ jsx(Button, {
117
+ type: "button",
118
+ variant: "ghost",
119
+ size: "icon",
120
+ "aria-label": "Move up",
121
+ disabled: index_0 === 0 || save.isPending,
122
+ onClick: () => move(index_0, -1),
123
+ children: /* @__PURE__ */ jsx(ArrowUp, {})
124
+ }),
125
+ /* @__PURE__ */ jsx(Button, {
126
+ type: "button",
127
+ variant: "ghost",
128
+ size: "icon",
129
+ "aria-label": "Move down",
130
+ disabled: index_0 === rows.length - 1 || save.isPending,
131
+ onClick: () => move(index_0, 1),
132
+ children: /* @__PURE__ */ jsx(ArrowDown, {})
133
+ }),
134
+ /* @__PURE__ */ jsx(Button, {
135
+ type: "button",
136
+ variant: "ghost",
137
+ size: "icon",
138
+ "aria-label": "Remove",
139
+ disabled: save.isPending,
140
+ onClick: () => remove(entry_4.key),
141
+ children: /* @__PURE__ */ jsx(Trash2, {})
142
+ })
143
+ ]
144
+ }), error && /* @__PURE__ */ jsx("p", {
145
+ className: "text-destructive text-xs",
146
+ children: error
147
+ })]
148
+ }, entry_4.key);
149
+ })
150
+ });
151
+ $[13] = rows;
152
+ $[14] = save.isPending;
153
+ $[15] = showErrors;
154
+ $[16] = t8;
155
+ } else t8 = $[16];
156
+ let t9;
157
+ if ($[17] !== save.isPending) {
158
+ t9 = save.isPending && /* @__PURE__ */ jsx(Spinner, {});
159
+ $[17] = save.isPending;
160
+ $[18] = t9;
161
+ } else t9 = $[18];
162
+ let t10;
163
+ if ($[19] !== save.isPending || $[20] !== t9) {
164
+ t10 = /* @__PURE__ */ jsxs(Button, {
165
+ type: "submit",
166
+ disabled: save.isPending,
167
+ children: [t9, "Save"]
168
+ });
169
+ $[19] = save.isPending;
170
+ $[20] = t9;
171
+ $[21] = t10;
172
+ } else t10 = $[21];
173
+ let t11;
174
+ let t12;
175
+ if ($[22] === Symbol.for("react.memo_cache_sentinel")) {
176
+ t11 = () => setRows(_temp5);
177
+ t12 = /* @__PURE__ */ jsx(Plus, {});
178
+ $[22] = t11;
179
+ $[23] = t12;
180
+ } else {
181
+ t11 = $[22];
182
+ t12 = $[23];
183
+ }
184
+ let t13;
185
+ if ($[24] !== save.isPending) {
186
+ t13 = /* @__PURE__ */ jsxs(Button, {
187
+ type: "button",
188
+ variant: "ghost",
189
+ disabled: save.isPending,
190
+ onClick: t11,
191
+ children: [t12, "Add profile"]
192
+ });
193
+ $[24] = save.isPending;
194
+ $[25] = t13;
195
+ } else t13 = $[25];
196
+ let t14;
197
+ if ($[26] !== t10 || $[27] !== t13) {
198
+ t14 = /* @__PURE__ */ jsxs("div", {
199
+ className: "flex flex-wrap gap-2",
200
+ children: [t10, t13]
201
+ });
202
+ $[26] = t10;
203
+ $[27] = t13;
204
+ $[28] = t14;
205
+ } else t14 = $[28];
206
+ let t15;
207
+ if ($[29] !== submit || $[30] !== t14 || $[31] !== t8) {
208
+ t15 = /* @__PURE__ */ jsx(SettingsPart, {
209
+ title: "Social profiles",
210
+ description: "The project's accounts elsewhere, such as on LinkedIn, X or GitHub. Search engines and AI answer engines use them to tie the website to your organization.",
211
+ children: /* @__PURE__ */ jsxs("form", {
212
+ onSubmit: submit,
213
+ className: "flex flex-col gap-4",
214
+ children: [t8, t14]
215
+ })
216
+ });
217
+ $[29] = submit;
218
+ $[30] = t14;
219
+ $[31] = t8;
220
+ $[32] = t15;
221
+ } else t15 = $[32];
222
+ return t15;
223
+ }
224
+ function _temp5(current_2) {
225
+ return [...current_2, row("")];
226
+ }
227
+ function _temp4(entry_3) {
228
+ return entry_3.url;
229
+ }
230
+ function _temp3(url) {
231
+ const result = socialProfileUrlSchema.safeParse(url.trim());
232
+ return result.success ? void 0 : result.error.issues[0]?.message;
233
+ }
234
+ function _temp2(e) {
235
+ return e.url;
236
+ }
237
+ function _temp(entry) {
238
+ return {
239
+ ...entry,
240
+ url: entry.url.trim()
241
+ };
242
+ }
243
+
244
+ //#endregion
245
+ export { SocialProfilesSettings };