@drawnagency/primitives 0.1.15 → 0.1.17

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.
@@ -1,9 +1,7 @@
1
1
  interface BuildStatusIndicatorProps {
2
2
  state: "idle" | "building" | "ready" | "error";
3
3
  deployUrl: string | null;
4
- visible: boolean;
5
- onDismiss: () => void;
6
4
  }
7
- export declare function BuildStatusIndicator({ state, deployUrl, visible, onDismiss, }: BuildStatusIndicatorProps): import("react/jsx-runtime").JSX.Element | null;
5
+ export declare function BuildStatusIndicator({ state, deployUrl, }: BuildStatusIndicatorProps): import("react/jsx-runtime").JSX.Element | null;
8
6
  export {};
9
7
  //# sourceMappingURL=BuildStatusIndicator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BuildStatusIndicator.d.ts","sourceRoot":"","sources":["../../../src/components/shell/BuildStatusIndicator.tsx"],"names":[],"mappings":"AAGA,UAAU,yBAAyB;IACjC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;IAC/C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAoBD,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,SAAS,EACT,OAAO,EACP,SAAS,GACV,EAAE,yBAAyB,kDAiC3B"}
1
+ {"version":3,"file":"BuildStatusIndicator.d.ts","sourceRoot":"","sources":["../../../src/components/shell/BuildStatusIndicator.tsx"],"names":[],"mappings":"AAGA,UAAU,yBAAyB;IACjC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;IAC/C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AA6BD,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,SAAS,GACV,EAAE,yBAAyB,kDAoB3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAYjD,OAAO,sBAAsB,CAAC;AAmC9B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAcxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;CACjE;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,GACZ,EAAE,KAAK,2CAykBP"}
1
+ {"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAYjD,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAcxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;CACjE;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,GACZ,EAAE,KAAK,2CAskBP"}
@@ -2,8 +2,6 @@ type BuildState = "idle" | "building" | "ready" | "error";
2
2
  interface BuildStatusResult {
3
3
  state: BuildState;
4
4
  deployUrl: string | null;
5
- visible: boolean;
6
- dismiss: () => void;
7
5
  startTracking: () => void;
8
6
  }
9
7
  export declare function useBuildStatus(): BuildStatusResult;
@@ -1 +1 @@
1
- {"version":3,"file":"useBuildStatus.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuildStatus.ts"],"names":[],"mappings":"AAEA,KAAK,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAS1D,UAAU,iBAAiB;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAKD,wBAAgB,cAAc,IAAI,iBAAiB,CAoHlD"}
1
+ {"version":3,"file":"useBuildStatus.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuildStatus.ts"],"names":[],"mappings":"AAEA,KAAK,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAS1D,UAAU,iBAAiB;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAID,wBAAgB,cAAc,IAAI,iBAAiB,CA0FlD"}
@@ -18,8 +18,9 @@ interface PublishDeps {
18
18
  onShasUpdated: (savedSha: string | null, mainSha: string | null) => void;
19
19
  onPublishComplete?: () => void;
20
20
  }
21
+ export type PublishAction = "idle" | "saving" | "publishing";
21
22
  export declare function useEditorPublish({ flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, onPublishComplete, }: PublishDeps): {
22
- isPublishing: boolean;
23
+ publishAction: PublishAction;
23
24
  publishFeedback: string | null;
24
25
  handleSave: () => Promise<void>;
25
26
  handlePublish: () => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAe/D,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,EAAE,SAAS,EAAE,CAAC;IAC/B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gBAAgB,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtF,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AASD,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,EAAE,WAAW;;;;;;EAuSb"}
1
+ {"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAe/D,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,EAAE,SAAS,EAAE,CAAC;IAC/B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gBAAgB,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtF,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AASD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,EAAE,WAAW;;;;;;EAuSb"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawnagency/primitives",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./package.json": "./package.json",
@@ -1,27 +1,34 @@
1
1
  import { cn } from "../../lib/cn";
2
- import { X } from "lucide-react";
2
+ import { useEffect, useState } from "react";
3
3
 
4
4
  interface BuildStatusIndicatorProps {
5
5
  state: "idle" | "building" | "ready" | "error";
6
6
  deployUrl: string | null;
7
- visible: boolean;
8
- onDismiss: () => void;
7
+ }
8
+
9
+ function LoadingEllipsis() {
10
+ const [dots, setDots] = useState(0);
11
+ useEffect(() => {
12
+ const id = setInterval(() => setDots((d) => (d + 1) % 4), 400);
13
+ return () => clearInterval(id);
14
+ }, []);
15
+ return <>{".".repeat(dots)}</>;
9
16
  }
10
17
 
11
18
  const stateConfig = {
12
19
  building: {
13
- label: "Deploying...",
14
- className: "border-orange-500 text-orange-600 hover:bg-orange-50",
20
+ label: "Publishing",
21
+ className: "border-orange-500 text-orange-600",
15
22
  dotClassName: "bg-orange-500 animate-pulse",
16
23
  },
17
24
  ready: {
18
- label: "Live",
19
- className: "border-green-600 text-green-600 hover:bg-green-50",
25
+ label: "Published",
26
+ className: "border-green-600 text-green-600",
20
27
  dotClassName: "bg-green-600",
21
28
  },
22
29
  error: {
23
- label: "Deploy failed",
24
- className: "border-red-600 text-red-600 hover:bg-red-50",
30
+ label: "Publish failed",
31
+ className: "border-red-600 text-red-600",
25
32
  dotClassName: "bg-red-600",
26
33
  },
27
34
  } as const;
@@ -29,39 +36,24 @@ const stateConfig = {
29
36
  export function BuildStatusIndicator({
30
37
  state,
31
38
  deployUrl,
32
- visible,
33
- onDismiss,
34
39
  }: BuildStatusIndicatorProps) {
35
- if (!visible || state === "idle") return null;
40
+ if (state === "idle") return null;
36
41
 
37
42
  const config = stateConfig[state];
38
43
 
39
44
  return (
40
- <span className="inline-flex items-center gap-0">
41
- <a
42
- href={deployUrl ?? "#"}
43
- target="_blank"
44
- rel="noopener noreferrer"
45
- aria-label={config.label}
46
- className={cn(
47
- "inline-flex items-center gap-1.5 rounded-l px-3 py-1.5 text-xs font-medium border transition-colors",
48
- config.className,
49
- )}
50
- >
51
- <span className={cn("h-1.5 w-1.5 rounded-full", config.dotClassName)} />
52
- {config.label}
53
- </a>
54
- <button
55
- type="button"
56
- onClick={onDismiss}
57
- aria-label="Dismiss build status"
58
- className={cn(
59
- "inline-flex items-center rounded-r border border-l-0 px-1.5 py-1.5 transition-colors cursor-pointer",
60
- config.className,
61
- )}
62
- >
63
- <X size={12} />
64
- </button>
65
- </span>
45
+ <a
46
+ href={deployUrl ?? "#"}
47
+ target="_blank"
48
+ rel="noopener noreferrer"
49
+ aria-label={config.label}
50
+ className={cn(
51
+ "inline-flex items-center gap-1.5 rounded px-3 py-1.5 text-xs font-medium border transition-colors",
52
+ config.className,
53
+ )}
54
+ >
55
+ <span className={cn("h-1.5 w-1.5 rounded-full", config.dotClassName)} />
56
+ {config.label}{state === "building" && <LoadingEllipsis />}
57
+ </a>
66
58
  );
67
59
  }
@@ -37,7 +37,6 @@ import { useEditorPersistence } from "../../hooks/useEditorPersistence";
37
37
  import { useEditorPublish } from "../../hooks/useEditorPublish";
38
38
  import { useContentLifecycle } from "../../hooks/useContentLifecycle";
39
39
  import { useBuildStatus } from "../../hooks/useBuildStatus";
40
- import { BuildStatusIndicator } from "./BuildStatusIndicator";
41
40
  import { useMediaPipeline } from "../../hooks/useMediaPipeline";
42
41
  import { formatTimestamp } from "../../lib/timestamp";
43
42
  import { generateNavLinks } from "../../lib/nav";
@@ -127,7 +126,7 @@ export default function EditorShell({
127
126
 
128
127
  const buildStatus = useBuildStatus();
129
128
 
130
- const { isPublishing, publishFeedback, handleSave, handlePublish, handleSaveAndPublish } = useEditorPublish({
129
+ const { publishAction, publishFeedback, handleSave, handlePublish, handleSaveAndPublish } = useEditorPublish({
131
130
  flushNow: persistence.flushNow,
132
131
  cancelPendingFlush: persistence.cancelPendingFlush,
133
132
  isConfigDirty: persistence.isConfigDirty,
@@ -574,7 +573,7 @@ export default function EditorShell({
574
573
  <EditorToolbar
575
574
  buttonState={buttonState}
576
575
  localChangesExist={localChangesExist}
577
- isPublishing={isPublishing}
576
+ publishAction={publishAction}
578
577
  publishFeedback={publishFeedback}
579
578
  onSave={handleSave}
580
579
  onPublish={handlePublish}
@@ -587,9 +586,6 @@ export default function EditorShell({
587
586
  }}
588
587
  processingItems={mediaPipeline.processingItems}
589
588
  buildState={buildStatus.state}
590
- buildDeployUrl={buildStatus.deployUrl}
591
- buildVisible={buildStatus.visible}
592
- onBuildDismiss={buildStatus.dismiss}
593
589
  />
594
590
 
595
591
  <EditorContent
@@ -597,7 +593,7 @@ export default function EditorShell({
597
593
  audiences={audiences}
598
594
  dirtySectionIds={dirtySectionIds}
599
595
  deletedSections={deletedSections}
600
- isPublishing={isPublishing}
596
+ isPublishing={publishAction !== "idle"}
601
597
  onSectionChange={onSectionChange}
602
598
  onAddSection={onAddSection}
603
599
  onDeleteSection={onDeleteSection}
@@ -876,10 +872,47 @@ function GlobalModal() {
876
872
  );
877
873
  }
878
874
 
875
+ function StatusText({
876
+ publishAction,
877
+ publishFeedback,
878
+ buildState,
879
+ }: {
880
+ publishAction: "idle" | "saving" | "publishing";
881
+ publishFeedback: string | null;
882
+ buildState: "idle" | "building" | "ready" | "error";
883
+ }) {
884
+ if (publishAction === "saving") {
885
+ return <span className="text-xs font-medium text-base-content/60">Saving...</span>;
886
+ }
887
+ if (publishAction === "publishing") {
888
+ return <span className="text-xs font-medium text-base-content/60">Publishing...</span>;
889
+ }
890
+ if (buildState === "building") {
891
+ return <span className="text-xs font-medium text-orange-600">Publishing...</span>;
892
+ }
893
+ if (buildState === "ready") {
894
+ return <span className="text-xs font-medium text-green-600">Published</span>;
895
+ }
896
+ if (buildState === "error") {
897
+ return <span className="text-xs font-medium text-red-600">Publish failed</span>;
898
+ }
899
+ if (publishFeedback) {
900
+ return (
901
+ <span className={cn(
902
+ "text-xs font-medium",
903
+ publishFeedback === "Saved" ? "text-green-600" : "text-red-600",
904
+ )}>
905
+ {publishFeedback}
906
+ </span>
907
+ );
908
+ }
909
+ return null;
910
+ }
911
+
879
912
  function EditorToolbar({
880
913
  buttonState,
881
914
  localChangesExist,
882
- isPublishing,
915
+ publishAction,
883
916
  publishFeedback,
884
917
  onSave,
885
918
  onPublish,
@@ -889,13 +922,10 @@ function EditorToolbar({
889
922
  onMediaClick,
890
923
  processingItems,
891
924
  buildState,
892
- buildDeployUrl,
893
- buildVisible,
894
- onBuildDismiss,
895
925
  }: {
896
926
  buttonState: "synced" | "publish" | "saveAndPublish";
897
927
  localChangesExist: boolean;
898
- isPublishing: boolean;
928
+ publishAction: "idle" | "saving" | "publishing";
899
929
  publishFeedback: string | null;
900
930
  onSave: () => void;
901
931
  onPublish: () => void;
@@ -905,33 +935,19 @@ function EditorToolbar({
905
935
  onMediaClick: () => void;
906
936
  processingItems: QueueItem[];
907
937
  buildState: "idle" | "building" | "ready" | "error";
908
- buildDeployUrl: string | null;
909
- buildVisible: boolean;
910
- onBuildDismiss: () => void;
911
938
  }) {
912
939
  const { isEditMode, viewBranch, setViewBranch, toggleEditMode } = useEditorContext();
913
940
 
914
941
  return (
915
942
  <>
916
943
  {isEditMode && (
917
- <div className="fixed top-0 right-0 left-0 z-50 flex items-center justify-between border-b border-base-200 bg-base px-4 py-2">
944
+ <div className="fixed top-0 right-0 left-0 z-50 grid grid-cols-3 items-center border-b border-base-200 bg-base px-4 py-2">
918
945
  <div className="flex items-center gap-2">
919
- {publishFeedback && (
920
- <span className={cn(
921
- "text-xs font-medium",
922
- publishFeedback === "Published" || publishFeedback === "Saved"
923
- ? "text-green-600"
924
- : "text-red-600",
925
- )}>
926
- {publishFeedback}
927
- </span>
928
- )}
929
946
  {buttonState === "saveAndPublish" && (
930
947
  <SplitButton
931
948
  label="Save & Publish"
932
949
  onClick={onSaveAndPublish}
933
- isLoading={isPublishing}
934
- loadingLabel="Publishing..."
950
+ disabled={publishAction !== "idle"}
935
951
  options={[{ label: "Save", onClick: onSave }]}
936
952
  />
937
953
  )}
@@ -939,8 +955,7 @@ function EditorToolbar({
939
955
  <SplitButton
940
956
  label="Publish"
941
957
  onClick={onPublish}
942
- isLoading={isPublishing}
943
- loadingLabel="Publishing..."
958
+ disabled={publishAction !== "idle"}
944
959
  options={[]}
945
960
  />
946
961
  )}
@@ -952,24 +967,24 @@ function EditorToolbar({
952
967
  options={[]}
953
968
  />
954
969
  )}
955
- {localChangesExist && !isPublishing && (
970
+ {localChangesExist && publishAction === "idle" && (
956
971
  <Button
957
972
  type="button"
958
973
  variant="destructive"
959
974
  onClick={onDiscardClick}
960
- disabled={isPublishing}
961
975
  >
962
976
  Discard Changes
963
977
  </Button>
964
978
  )}
965
- <BuildStatusIndicator
966
- state={buildState}
967
- deployUrl={buildDeployUrl}
968
- visible={buildVisible}
969
- onDismiss={onBuildDismiss}
979
+ </div>
980
+ <div className="flex items-center justify-center">
981
+ <StatusText
982
+ publishAction={publishAction}
983
+ publishFeedback={publishFeedback}
984
+ buildState={buildState}
970
985
  />
971
986
  </div>
972
- <div className="flex items-center gap-2">
987
+ <div className="flex items-center justify-end gap-2">
973
988
  <ProcessingIndicator items={processingItems} />
974
989
  <IconButton
975
990
  icon={<ImageIcon size={16} />}
@@ -12,20 +12,15 @@ interface BuildStatusResponse {
12
12
  interface BuildStatusResult {
13
13
  state: BuildState;
14
14
  deployUrl: string | null;
15
- visible: boolean;
16
- dismiss: () => void;
17
15
  startTracking: () => void;
18
16
  }
19
17
 
20
18
  const POLL_INTERVAL = 5000;
21
- const AUTO_DISMISS_DELAY = 10000;
22
19
 
23
20
  export function useBuildStatus(): BuildStatusResult {
24
21
  const [state, setState] = useState<BuildState>("idle");
25
22
  const [deployUrl, setDeployUrl] = useState<string | null>(null);
26
- const [dismissed, setDismissed] = useState(false);
27
23
  const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
28
- const dismissRef = useRef<ReturnType<typeof setTimeout> | null>(null);
29
24
  const isPolling = useRef(false);
30
25
 
31
26
  const stopPolling = useCallback(() => {
@@ -65,16 +60,9 @@ export function useBuildStatus(): BuildStatusResult {
65
60
 
66
61
  setState(data.state);
67
62
  setDeployUrl(data.deployUrl);
68
- setDismissed(false);
69
63
 
70
64
  if (data.state === "ready" || data.state === "error") {
71
65
  stopPolling();
72
-
73
- if (data.state === "ready") {
74
- dismissRef.current = setTimeout(() => {
75
- setDismissed(true);
76
- }, AUTO_DISMISS_DELAY);
77
- }
78
66
  }
79
67
  },
80
68
  [stopPolling],
@@ -109,31 +97,14 @@ export function useBuildStatus(): BuildStatusResult {
109
97
  return () => {
110
98
  cancelled = true;
111
99
  stopPolling();
112
- if (dismissRef.current) clearTimeout(dismissRef.current);
113
100
  };
114
101
  }, [fetchStatus, handleStatusUpdate, startPolling, stopPolling]);
115
102
 
116
- const dismiss = useCallback(() => {
117
- setDismissed(true);
118
- stopPolling();
119
- if (dismissRef.current) {
120
- clearTimeout(dismissRef.current);
121
- dismissRef.current = null;
122
- }
123
- }, [stopPolling]);
124
-
125
103
  const startTracking = useCallback(() => {
126
104
  setState("building");
127
105
  setDeployUrl(null);
128
- setDismissed(false);
129
- if (dismissRef.current) {
130
- clearTimeout(dismissRef.current);
131
- dismissRef.current = null;
132
- }
133
106
  startPolling();
134
107
  }, [startPolling]);
135
108
 
136
- const visible = state !== "idle" && !dismissed;
137
-
138
- return { state, deployUrl, visible, dismiss, startTracking };
109
+ return { state, deployUrl, startTracking };
139
110
  }
@@ -41,6 +41,8 @@ interface GatheredMedia {
41
41
  hasMediaChanges: boolean;
42
42
  }
43
43
 
44
+ export type PublishAction = "idle" | "saving" | "publishing";
45
+
44
46
  export function useEditorPublish({
45
47
  flushNow,
46
48
  cancelPendingFlush,
@@ -58,7 +60,7 @@ export function useEditorPublish({
58
60
  onShasUpdated,
59
61
  onPublishComplete,
60
62
  }: PublishDeps) {
61
- const [isPublishing, setIsPublishing] = useState(false);
63
+ const [publishAction, setPublishAction] = useState<PublishAction>("idle");
62
64
  const [publishFeedback, setPublishFeedback] = useState<string | null>(null);
63
65
  const feedbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
64
66
 
@@ -136,7 +138,7 @@ export function useEditorPublish({
136
138
  const handleSave = useCallback(async () => {
137
139
  if (!siteConfig) return;
138
140
 
139
- setIsPublishing(true);
141
+ setPublishAction("saving");
140
142
  setPublishFeedback(null);
141
143
 
142
144
  try {
@@ -147,7 +149,7 @@ export function useEditorPublish({
147
149
  const hasMediaChanges = pendingMediaItems.length > 0 || pendingMediaDeletions.length > 0;
148
150
  const hasDeletedSections = (deletedSectionIds?.length ?? 0) > 0;
149
151
  if (!hasChanges && !isConfigDirty() && !hasMediaChanges && !hasDeletedSections) {
150
- setIsPublishing(false);
152
+ setPublishAction("idle");
151
153
  return;
152
154
  }
153
155
 
@@ -216,12 +218,12 @@ export function useEditorPublish({
216
218
  console.error("Save failed:", error);
217
219
  showFeedback("Save failed", 5000);
218
220
  } finally {
219
- setIsPublishing(false);
221
+ setPublishAction("idle");
220
222
  }
221
223
  }, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback]);
222
224
 
223
225
  const handlePublish = useCallback(async () => {
224
- setIsPublishing(true);
226
+ setPublishAction("publishing");
225
227
  setPublishFeedback(null);
226
228
 
227
229
  try {
@@ -238,20 +240,19 @@ export function useEditorPublish({
238
240
  const { sha } = await response.json();
239
241
 
240
242
  onShasUpdated(null, sha);
241
- showFeedback("Published", 3000);
242
243
  onPublishComplete?.();
243
244
  } catch (error) {
244
245
  console.error("Publish failed:", error);
245
246
  showFeedback("Publish failed", 5000);
246
247
  } finally {
247
- setIsPublishing(false);
248
+ setPublishAction("idle");
248
249
  }
249
250
  }, [onShasUpdated, showFeedback, onPublishComplete]);
250
251
 
251
252
  const handleSaveAndPublish = useCallback(async () => {
252
253
  if (!siteConfig) return;
253
254
 
254
- setIsPublishing(true);
255
+ setPublishAction("saving");
255
256
  setPublishFeedback(null);
256
257
 
257
258
  try {
@@ -316,6 +317,8 @@ export function useEditorPublish({
316
317
  }
317
318
  }
318
319
 
320
+ setPublishAction("publishing");
321
+
319
322
  const publishResponse = await fetch("/api/publish", {
320
323
  method: "POST",
321
324
  headers: { "Content-Type": "application/json" },
@@ -341,15 +344,14 @@ export function useEditorPublish({
341
344
  }
342
345
 
343
346
  onShasUpdated(null, sha);
344
- showFeedback("Published", 3000);
345
347
  onPublishComplete?.();
346
348
  } catch (error) {
347
349
  console.error("Publish failed:", error);
348
350
  showFeedback("Publish failed", 5000);
349
351
  } finally {
350
- setIsPublishing(false);
352
+ setPublishAction("idle");
351
353
  }
352
354
  }, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback, onPublishComplete]);
353
355
 
354
- return { isPublishing, publishFeedback, handleSave, handlePublish, handleSaveAndPublish };
356
+ return { publishAction, publishFeedback, handleSave, handlePublish, handleSaveAndPublish };
355
357
  }