diff --git a/apps/builder/app/builder/features/publish/publish.tsx b/apps/builder/app/builder/features/publish/publish.tsx index 7f404d35e081..7939bcdb5e51 100644 --- a/apps/builder/app/builder/features/publish/publish.tsx +++ b/apps/builder/app/builder/features/publish/publish.tsx @@ -491,6 +491,30 @@ const usePublishCountdown = (isPublishing: boolean) => { const renderModeStorageKey = (projectId: string) => `publish:renderMode:${projectId}`; const hostStorageKey = (projectId: string) => `publish:host:${projectId}`; +const coolifyWebhookStorageKey = (projectId: string) => + `publish:coolifyWebhook:${projectId}`; +const coolifyWebhookTokenStorageKey = (projectId: string) => + `publish:coolifyWebhookToken:${projectId}`; + +const readStored = (key: string) => { + try { + return localStorage.getItem(key) ?? ""; + } catch { + return ""; + } +}; + +const writeStored = (key: string, value: string) => { + try { + if (value === "") { + localStorage.removeItem(key); + } else { + localStorage.setItem(key, value); + } + } catch { + // localStorage unavailable — in-memory state only + } +}; /** * One-time seed from the pre-split `buildMode:` key so a project keeps the @@ -549,7 +573,32 @@ export const usePublishTarget = (projectId: string) => { localStorage.setItem(hostStorageKey(projectId), value); setHostState(value); }; - return { renderMode, setRenderMode, host, setHost }; + // host: "coolify" — the target app's deploy webhook, per project. Not stored + // server-side; forwarded to the publisher on each publish. + const [coolifyWebhookUrl, setCoolifyWebhookUrlState] = useState(() => + readStored(coolifyWebhookStorageKey(projectId)) + ); + const [coolifyWebhookToken, setCoolifyWebhookTokenState] = useState(() => + readStored(coolifyWebhookTokenStorageKey(projectId)) + ); + const setCoolifyWebhookUrl = (value: string) => { + writeStored(coolifyWebhookStorageKey(projectId), value); + setCoolifyWebhookUrlState(value); + }; + const setCoolifyWebhookToken = (value: string) => { + writeStored(coolifyWebhookTokenStorageKey(projectId), value); + setCoolifyWebhookTokenState(value); + }; + return { + renderMode, + setRenderMode, + host, + setHost, + coolifyWebhookUrl, + setCoolifyWebhookUrl, + coolifyWebhookToken, + setCoolifyWebhookToken, + }; }; const advancedPublishOpenStorageKey = "publish:advancedOpen"; @@ -592,6 +641,10 @@ const Publish = ({ onRenderModeChange, host, onHostChange, + coolifyWebhookUrl, + onCoolifyWebhookUrlChange, + coolifyWebhookToken, + onCoolifyWebhookTokenChange, }: { project: Project; timesLeft: number; @@ -602,6 +655,10 @@ const Publish = ({ onRenderModeChange: (value: RenderMode) => void; host: PublishHost; onHostChange: (value: PublishHost) => void; + coolifyWebhookUrl: string; + onCoolifyWebhookUrlChange: (value: string) => void; + coolifyWebhookToken: string; + onCoolifyWebhookTokenChange: (value: string) => void; }) => { const { maxDailyPublishesPerUser } = useStore($permissions); const { userPublishCount } = useUserPublishCount(); @@ -689,6 +746,12 @@ const Publish = ({ destination: "saas", renderMode, host, + coolifyWebhookUrl: + host === "coolify" ? coolifyWebhookUrl.trim() : undefined, + coolifyWebhookToken: + host === "coolify" && coolifyWebhookToken.trim() !== "" + ? coolifyWebhookToken.trim() + : undefined, }); if (publishResult.success === false) { @@ -837,6 +900,9 @@ const Publish = ({ const showPendingState = isPublishInProgress && (countdown === undefined || countdown === 0); + const missingCoolifyWebhook = + host === "coolify" && coolifyWebhookUrl.trim() === ""; + return ( {publishError && {publishError}} @@ -900,6 +966,41 @@ const Publish = ({ }} onChange={onHostChange} /> + {host === "coolify" && ( + + + + onCoolifyWebhookUrlChange(event.target.value) + } + /> + + onCoolifyWebhookTokenChange(event.target.value) + } + /> + + Create a Docker Image app on your Coolify, then paste its + deploy webhook here.{" "} + + Host with Coolify + + + + )} @@ -910,9 +1011,11 @@ const Publish = ({ content={ isPublishInProgress ? "Publish process in progress" - : hasSelectedDomains - ? undefined - : "Select at least one domain to publish" + : missingCoolifyWebhook + ? "Enter the Coolify deploy webhook URL in Advanced settings" + : hasSelectedDomains + ? undefined + : "Select at least one domain to publish" } >