From db828229c1f34c80279902b5905d03980afdd415 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Sun, 22 Jun 2025 11:04:47 +0300 Subject: [PATCH 01/12] feat: improve automatic domain configuration --- .../app/builder/features/topbar/entri.tsx | 105 +++++++++--- .../app/builder/features/topbar/publish.tsx | 2 +- apps/builder/app/shared/entri/entri.ts | 158 ------------------ apps/builder/package.json | 2 +- pnpm-lock.yaml | 24 ++- 5 files changed, 98 insertions(+), 193 deletions(-) delete mode 100644 apps/builder/app/shared/entri/entri.ts diff --git a/apps/builder/app/builder/features/topbar/entri.tsx b/apps/builder/app/builder/features/topbar/entri.tsx index cffbf3c01f5f..d56033b9d64a 100644 --- a/apps/builder/app/builder/features/topbar/entri.tsx +++ b/apps/builder/app/builder/features/topbar/entri.tsx @@ -1,20 +1,89 @@ -import { Button, Text } from "@webstudio-is/design-system"; -import { - useEntri, - entriGlobalStyles, - type DnsRecord, - type EntriCloseDetail, -} from "~/shared/entri/entri"; - -export const Entri = ({ - dnsRecords, - domain, - onClose, -}: { - dnsRecords: DnsRecord[]; +import * as entri from "entrijs"; +import { useEffect, useState } from "react"; +import { globalCss, Button, Text } from "@webstudio-is/design-system"; +import { trpcClient } from "~/shared/trpc/trpc-client"; + +// https://developers.entri.com/docs/install +type DnsRecord = { + type: "CNAME" | "TXT"; + host: string; + value: string; + ttl: number; +}; + +type EntriCloseEvent = CustomEvent; + +declare global { + // https://developers.entri.com/docs/integrate-with-dns-providers + interface WindowEventMap { + onEntriClose: EntriCloseEvent; + } +} + +/** + * Our FloatingPanelPopover adds pointerEvents: "none" to the body. + * We open the entry dialog from the popover, so we need to allow pointer events on the entri dialog. + */ +const entriGlobalStyles = globalCss({ + body: { + "&>#entriApp": { + pointerEvents: "all", + }, + }, +}); + +type EntriProps = { domain: string; - onClose: (detail: EntriCloseDetail) => void; -}) => { + dnsRecords: DnsRecord[]; + onClose: (detail: entri.EntriCloseEventDetail) => void; +}; + +const useEntri = ({ domain, dnsRecords, onClose }: EntriProps) => { + const [isOpen, setIsOpen] = useState(false); + + const { + load: entriTokenLoad, + data: entriTokenData, + error: entriTokenSystemError, + } = trpcClient.domain.getEntriToken.useQuery(); + + useEffect(() => { + const handleOnEntriClose = (event: EntriCloseEvent) => { + if (event.detail.domain === domain) { + onClose(event.detail); + setIsOpen(false); + } + }; + window.addEventListener("onEntriClose", handleOnEntriClose, false); + return () => { + window.removeEventListener("onEntriClose", handleOnEntriClose, false); + }; + }, [domain, onClose]); + + const showDialog = () => { + setIsOpen(true); + entriTokenLoad(undefined, async (data) => { + if (data.success) { + await entri.showEntri({ + applicationId: data.applicationId, + token: data.token, + dnsRecords, + prefilledDomain: domain, + }); + } + }); + }; + + return { + isOpen, + showDialog, + error: + entriTokenSystemError ?? + (entriTokenData?.success === false ? entriTokenData.error : undefined), + }; +}; + +export const Entri = ({ domain, dnsRecords, onClose }: EntriProps) => { entriGlobalStyles(); const { error, isOpen, showDialog } = useEntri({ onClose, @@ -31,9 +100,7 @@ export const Entri = ({ color="neutral" css={{ width: "100%", flexShrink: 0 }} type="button" - onClick={() => { - showDialog(); - }} + onClick={showDialog} > Configure automatically diff --git a/apps/builder/app/builder/features/topbar/publish.tsx b/apps/builder/app/builder/features/topbar/publish.tsx index 1b301e5795a3..63ec90737d6c 100644 --- a/apps/builder/app/builder/features/topbar/publish.tsx +++ b/apps/builder/app/builder/features/topbar/publish.tsx @@ -184,7 +184,7 @@ const ChangeProjectDomain = ({ - Proceed to ${pageUrl.toString()} + Proceed to {pageUrl.toString()} } variant="wrapped" diff --git a/apps/builder/app/shared/entri/entri.ts b/apps/builder/app/shared/entri/entri.ts deleted file mode 100644 index eac003e12fd0..000000000000 --- a/apps/builder/app/shared/entri/entri.ts +++ /dev/null @@ -1,158 +0,0 @@ -import useScript from "react-script-hook"; -import { globalCss } from "@webstudio-is/design-system"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { trpcClient } from "../trpc/trpc-client"; - -const scriptAttributes = { - async: true, - defer: true, - importance: "low", - src: "https://cdn.goentri.com/entri.js", -}; - -// https://developers.entri.com/docs/install -export type DnsRecord = { - type: "CNAME" | "TXT"; - host: string; - value: string; - ttl: number; -}; - -// https://developers.entri.com/docs/integrate-with-dns-providers -export type EntriCloseDetail = { - domain: string; - lastStatus: - | "FINISHED_SUCCESSFULLY" - | "IN_PROGRESS" - | "EXISTING_RECORDS" - | "LOGIN" - | "MANUAL_CONFIGURATION" - | "EXIT_WITH_ERROR" - | "DKIM_SETUP"; - - provider: string; - setupType: "automatic" | "manual" | "sharedLogin" | null; - success: boolean; -}; - -declare global { - interface Window { - // https://developers.entri.com/docs/api-reference - entri?: { - showEntri: (options: { - applicationId: string; - token: string; - dnsRecords: DnsRecord[]; - prefilledDomain: string; - }) => void; - }; - } - - // https://developers.entri.com/docs/integrate-with-dns-providers - interface WindowEventMap { - onEntriClose: CustomEvent; - } -} - -/** - * Our FloatingPanelPopover adds pointerEvents: "none" to the body. - * We open the entry dialog from the popover, so we need to allow pointer events on the entri dialog. - */ -export const entriGlobalStyles = globalCss({ - body: { - "&>#entriApp": { - pointerEvents: "all", - }, - }, -}); - -type UseEntriProps = { - domain: string; - onClose: (detail: EntriCloseDetail) => void; - dnsRecords: DnsRecord[]; -}; - -export const useEntri = ({ domain, dnsRecords, onClose }: UseEntriProps) => { - const [isScriptLoading, scriptLoadingError] = useScript(scriptAttributes); - const [error, setError] = useState(undefined); - const [isOpen, setIsOpen] = useState(false); - const showDialogInternalRef = useRef< - undefined | ((token: string, applicationId: string) => void) - >(undefined); - - const { - load: entriTokenLoad, - data: entriTokenData, - error: entriTokenSystemError, - } = trpcClient.domain.getEntriToken.useQuery(); - - useEffect(() => { - const handleOnEntriClose = (event: CustomEvent) => { - if (event.detail.domain !== domain) { - return; - } - - onClose(event.detail); - setIsOpen(false); - }; - - window.addEventListener("onEntriClose", handleOnEntriClose, false); - - return () => { - window.removeEventListener("onEntriClose", handleOnEntriClose); - }; - }, [domain, onClose]); - - const showDialogInternal = useCallback( - (token: string, applicationId: string) => { - const entri = window.entri; - - if (entri === undefined) { - if (isScriptLoading) { - setError("Entri is not loaded, try again later"); - return; - } - - setError("Entri is not loaded"); - return; - } - - entri.showEntri({ - applicationId, - token, - dnsRecords, - prefilledDomain: domain, - }); - }, - [dnsRecords, domain, isScriptLoading] - ); - - showDialogInternalRef.current = showDialogInternal; - - const showDialog = useCallback(() => { - setIsOpen(true); - entriTokenLoad(undefined, (data) => { - if (data.success === false) { - return; - } - - if (showDialogInternalRef.current === undefined) { - return; - } - - const { token, applicationId } = data; - - showDialogInternalRef.current(token, applicationId); - }); - }, [entriTokenLoad]); - - return { - isOpen, - showDialog, - error: - error ?? - entriTokenSystemError ?? - scriptLoadingError?.message ?? - (entriTokenData?.success === false ? entriTokenData.error : undefined), - }; -}; diff --git a/apps/builder/package.json b/apps/builder/package.json index 6dc3d55362a9..471bf9273a83 100644 --- a/apps/builder/package.json +++ b/apps/builder/package.json @@ -91,6 +91,7 @@ "css-tree": "^2.3.1", "debug": "^4.3.7", "downshift": "^6.1.7", + "entrijs": "^0.0.11", "fast-deep-equal": "^3.1.3", "immer": "^10.1.1", "immerhin": "^0.10.0", @@ -109,7 +110,6 @@ "react-colorful": "^5.6.1", "react-dom": "18.3.0-canary-14898b6a9-20240318", "react-error-boundary": "^5.0.0", - "react-script-hook": "^1.7.2", "remix-auth": "^3.7.0", "remix-auth-form": "^1.5.0", "remix-auth-github": "^1.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bb0be8fc4b3..45ade6760091 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -334,6 +334,9 @@ importers: downshift: specifier: ^6.1.7 version: 6.1.7(react@18.3.0-canary-14898b6a9-20240318) + entrijs: + specifier: ^0.0.11 + version: 0.0.11 fast-deep-equal: specifier: ^3.1.3 version: 3.1.3 @@ -388,9 +391,6 @@ importers: react-error-boundary: specifier: ^5.0.0 version: 5.0.0(react@18.3.0-canary-14898b6a9-20240318) - react-script-hook: - specifier: ^1.7.2 - version: 1.7.2(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318) remix-auth: specifier: ^3.7.0 version: 3.7.0(@remix-run/react@2.16.5(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318)(typescript@5.8.2))(@remix-run/server-runtime@2.16.5(typescript@5.8.2)) @@ -6483,6 +6483,9 @@ packages: resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==} engines: {node: '>=0.12'} + entrijs@0.0.11: + resolution: {integrity: sha512-ufH4h0JdumikGIHuVQjP6uKJMG00Yv7Av0ZsBvM7PicejO8e9eRAxXKUdIHqoo1ZqogFrquDmmP63x3T0djHlQ==} + env-paths@3.0.0: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8445,12 +8448,6 @@ packages: react-dom: optional: true - react-script-hook@1.7.2: - resolution: {integrity: sha512-fhyCEfXb94fag34UPRF0zry1XGwmVY+79iibWwTqAoOiCzYJQOYTiWJ7CnqglA9tMSV8g45cQpHCMcBwr7dwhA==} - peerDependencies: - react: 18.3.0-canary-14898b6a9-20240318 - react-dom: 18.3.0-canary-14898b6a9-20240318 - react-shallow-renderer@16.15.0: resolution: {integrity: sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==} peerDependencies: @@ -14107,6 +14104,10 @@ snapshots: entities@6.0.0: {} + entrijs@0.0.11: + dependencies: + react: 18.3.0-canary-14898b6a9-20240318 + env-paths@3.0.0: {} err-code@2.0.3: {} @@ -16605,11 +16606,6 @@ snapshots: optionalDependencies: react-dom: 18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318) - react-script-hook@1.7.2(react-dom@18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318))(react@18.3.0-canary-14898b6a9-20240318): - dependencies: - react: 18.3.0-canary-14898b6a9-20240318 - react-dom: 18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318) - react-shallow-renderer@16.15.0(react@18.3.0-canary-14898b6a9-20240318): dependencies: object-assign: 4.1.1 From f6926523ba9b862442e7f862c52aaa7857a37bfd Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Sun, 22 Jun 2025 21:53:05 +0300 Subject: [PATCH 02/12] Enable www redirect --- apps/builder/app/builder/features/topbar/entri.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/builder/app/builder/features/topbar/entri.tsx b/apps/builder/app/builder/features/topbar/entri.tsx index d56033b9d64a..f94282549898 100644 --- a/apps/builder/app/builder/features/topbar/entri.tsx +++ b/apps/builder/app/builder/features/topbar/entri.tsx @@ -69,6 +69,7 @@ const useEntri = ({ domain, dnsRecords, onClose }: EntriProps) => { token: data.token, dnsRecords, prefilledDomain: domain, + wwwRedirect: true, }); } }); From 4027722cfdb45d80eff22cc2ce3b34521a19f495 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Mon, 23 Jun 2025 14:17:10 +0300 Subject: [PATCH 03/12] Use type=button for website link --- apps/builder/app/builder/features/topbar/domains.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/builder/app/builder/features/topbar/domains.tsx b/apps/builder/app/builder/features/topbar/domains.tsx index b3db2809883a..77e555db28ba 100644 --- a/apps/builder/app/builder/features/topbar/domains.tsx +++ b/apps/builder/app/builder/features/topbar/domains.tsx @@ -329,6 +329,7 @@ const DomainItem = (props: { { From 9f51b978fb9cd995aadadc4dc6f798572fea871f Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Tue, 24 Jun 2025 01:19:21 +0300 Subject: [PATCH 04/12] Add type=button to icon buttons --- apps/builder/app/builder/features/topbar/publish.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/builder/app/builder/features/topbar/publish.tsx b/apps/builder/app/builder/features/topbar/publish.tsx index 63ec90737d6c..cab03192b49f 100644 --- a/apps/builder/app/builder/features/topbar/publish.tsx +++ b/apps/builder/app/builder/features/topbar/publish.tsx @@ -190,6 +190,7 @@ const ChangeProjectDomain = ({ variant="wrapped" > { window.open(pageUrl, "_blank"); @@ -1231,6 +1232,7 @@ export const PublishButton = ({ projectId }: PublishProps) => { suffix={ { $openProjectSettings.set("publish"); }} From 67f30ea3196791c2f772df64daec5acbe570f5a9 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Tue, 24 Jun 2025 13:20:11 +0300 Subject: [PATCH 05/12] Detect cname flattening and alias support --- .../builder/features/topbar/add-domain.tsx | 14 +- .../features/topbar/domain-checkbox.tsx | 4 +- .../app/builder/features/topbar/domains.tsx | 241 +++++++++--------- .../app/builder/features/topbar/entri.tsx | 8 +- .../app/builder/features/topbar/publish.tsx | 2 +- .../app/services/trcp-router.server.ts | 2 +- packages/domain/src/trpc/domain.ts | 37 +++ 7 files changed, 177 insertions(+), 131 deletions(-) diff --git a/apps/builder/app/builder/features/topbar/add-domain.tsx b/apps/builder/app/builder/features/topbar/add-domain.tsx index e5575c5ee3da..a2002ed48d4a 100644 --- a/apps/builder/app/builder/features/topbar/add-domain.tsx +++ b/apps/builder/app/builder/features/topbar/add-domain.tsx @@ -14,6 +14,7 @@ import type { Project } from "@webstudio-is/project"; import { useId, useOptimistic, useRef, useState } from "react"; import { TerminalIcon } from "@webstudio-is/icons"; import { nativeClient } from "~/shared/trpc/trpc-client"; +import { extractCname } from "./cname"; type DomainsAddProps = { projectId: Project["id"]; @@ -38,7 +39,7 @@ export const AddDomain = ({ // Will be automatically reset on action end setIsPendingOptimistic(true); - const domain = formData.get("domain")?.toString() ?? ""; + let domain = formData.get("domain")?.toString() ?? ""; const validationResult = validateDomain(domain); if (validationResult.success === false) { @@ -46,6 +47,17 @@ export const AddDomain = ({ return; } + // detect provider only when root domain is specified + if (extractCname(domain) === "@") { + const registrar = await nativeClient.domain.findDomainRegistrar.query({ + domain, + }); + // enforce www subdomain when no support for cname flattening or alias + if (!registrar.cnameFlattening && !registrar.alias) { + domain = `www.${domain}`; + } + } + const result = await nativeClient.domain.create.mutate({ domain, projectId, diff --git a/apps/builder/app/builder/features/topbar/domain-checkbox.tsx b/apps/builder/app/builder/features/topbar/domain-checkbox.tsx index c68aeae817ca..849a3ff339f8 100644 --- a/apps/builder/app/builder/features/topbar/domain-checkbox.tsx +++ b/apps/builder/app/builder/features/topbar/domain-checkbox.tsx @@ -20,7 +20,7 @@ interface DomainCheckboxProps { disabled?: boolean; } -const DomainCheckbox = (props: DomainCheckboxProps) => { +export const DomainCheckbox = (props: DomainCheckboxProps) => { const hasProPlan = useStore($userPlanFeatures).hasProPlan; const project = useStore($project); @@ -78,5 +78,3 @@ const DomainCheckbox = (props: DomainCheckboxProps) => { ); }; - -export default DomainCheckbox; diff --git a/apps/builder/app/builder/features/topbar/domains.tsx b/apps/builder/app/builder/features/topbar/domains.tsx index 77e555db28ba..60220ec7e561 100644 --- a/apps/builder/app/builder/features/topbar/domains.tsx +++ b/apps/builder/app/builder/features/topbar/domains.tsx @@ -21,6 +21,7 @@ import { } from "@webstudio-is/icons"; import { CollapsibleDomainSection } from "./collapsible-domain-section"; import { + Fragment, startTransition, useEffect, useOptimistic, @@ -29,17 +30,18 @@ import { type ReactNode, } from "react"; import { Entri } from "./entri"; -import { nativeClient } from "~/shared/trpc/trpc-client"; +import { nativeClient, trpcClient } from "~/shared/trpc/trpc-client"; import { useStore } from "@nanostores/react"; import { $publisherHost } from "~/shared/nano-states"; import { extractCname } from "./cname"; import { useEffectEvent } from "~/shared/hook-utils/effect-event"; -import DomainCheckbox from "./domain-checkbox"; +import { DomainCheckbox } from "./domain-checkbox"; import { CopyToClipboard } from "~/builder/shared/copy-to-clipboard"; import { RelativeTime } from "~/builder/shared/relative-time"; export type Domain = Project["domainsVirtual"][number]; -type DomainStatus = Project["domainsVirtual"][number]["status"]; + +type DomainStatus = Domain["status"]; const InputEllipsis = styled(InputField, { "&>input": { @@ -164,6 +166,119 @@ const StatusIcon = (props: { projectDomain: Domain; isLoading: boolean }) => { ); }; +const DomainConfig = ({ + projectDomain, + onUpdateStatus, +}: { + projectDomain: Domain; + onUpdateStatus: () => void; +}) => { + const { load: findDomainRegistrar, data: registrar } = + trpcClient.domain.findDomainRegistrar.useQuery(); + const cname = extractCname(projectDomain.domain); + useEffect(() => { + if (cname === "@") { + findDomainRegistrar({ domain: projectDomain.domain }); + } + }, [projectDomain.domain, cname]); + const publisherHost = useStore($publisherHost); + + const cnameRecord = { + // use alias for domain root when supported to avoid conflicting + // with MX, NS etc records + type: cname === "@" && registrar?.alias ? "ALIAS" : "CNAME", + host: cname, + value: `${projectDomain.cname}.customers.${publisherHost}`, + ttl: 300, + } as const; + + const txtRecord = { + type: "TXT", + host: cname === "@" ? "_webstudio_is" : `_webstudio_is.${cname}`, + value: projectDomain.expectedTxtRecord, + ttl: 300, + } as const; + + const dnsRecords = [cnameRecord, txtRecord]; + + return ( + <> + + To verify your domain: +
+ Visit the admin console of your domain registrar (the website you + purchased your domain from) and create one CNAME record + and one TXT record with the values shown below: +
+ + + + TYPE + + + NAME + + + VALUE + + + {dnsRecords.map((record, index) => ( + + + + + + + + } + /> + + + + + + } + /> + + ))} + + + + + OR + + + + { + // Sometimes Entri modal dialog hangs even if it's successful, + // until they fix that, we'll just refresh the status here on every onClose event + onUpdateStatus(); + }} + /> + + ); +}; + const DomainItem = (props: { initiallyOpen: boolean; projectDomain: Domain; @@ -272,33 +387,8 @@ const DomainItem = (props: { }); }, [status, handleVerify, handleUpdateStatus, isStatusLoading]); - const publisherHost = useStore($publisherHost); - const cnameEntryName = extractCname(props.projectDomain.domain); - const cnameEntryValue = `${props.projectDomain.cname}.customers.${publisherHost}`; - - const txtEntryName = - cnameEntryName === "@" - ? "_webstudio_is" - : `_webstudio_is.${cnameEntryName}`; - const domainStatus = getStatus(props.projectDomain); - const cnameRecord = { - type: "CNAME", - host: cnameEntryName, - value: cnameEntryValue, - ttl: 300, - } as const; - - const txtRecord = { - type: "TXT", - host: txtEntryName, - value: props.projectDomain.expectedTxtRecord, - ttl: 300, - } as const; - - const dnsRecords = [cnameRecord, txtRecord]; - const { isVerifiedActive, text } = getStatusText({ projectDomain: props.projectDomain, isLoading: false, @@ -413,98 +503,9 @@ const DomainItem = (props: { )} - - To verify your domain: -
- Visit the admin console of your domain registrar (the website you - purchased your domain from) and create one CNAME{" "} - record and one TXT record with the values shown - below: -
- - - - TYPE - - - NAME - - - VALUE - - - - - - - - - } - /> - - - - - - } - /> - - - - - - - - } - /> - - - - - - } - /> - - - - - OR - - - - { - // Sometimes Entri modal dialog hangs even if it's successful, - // until they fix that, we'll just refresh the status here on every onClose event + { if (status === "UNVERIFIED") { startTransition(async () => { await handleVerify(); diff --git a/apps/builder/app/builder/features/topbar/entri.tsx b/apps/builder/app/builder/features/topbar/entri.tsx index f94282549898..db0da8f2e432 100644 --- a/apps/builder/app/builder/features/topbar/entri.tsx +++ b/apps/builder/app/builder/features/topbar/entri.tsx @@ -5,7 +5,7 @@ import { trpcClient } from "~/shared/trpc/trpc-client"; // https://developers.entri.com/docs/install type DnsRecord = { - type: "CNAME" | "TXT"; + type: "CNAME" | "ALIAS" | "TXT"; host: string; value: string; ttl: number; @@ -27,7 +27,7 @@ declare global { const entriGlobalStyles = globalCss({ body: { "&>#entriApp": { - pointerEvents: "all", + pointerEvents: "auto", }, }, }); @@ -87,15 +87,13 @@ const useEntri = ({ domain, dnsRecords, onClose }: EntriProps) => { export const Entri = ({ domain, dnsRecords, onClose }: EntriProps) => { entriGlobalStyles(); const { error, isOpen, showDialog } = useEntri({ - onClose, domain, dnsRecords, + onClose, }); - return ( <> {error !== undefined && {error}} - From 853137483a8c6e5e2f92fea48d74c3e375459af1 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Wed, 25 Jun 2025 13:55:11 +0300 Subject: [PATCH 11/12] Forbid automatic domain configuration on free plan --- apps/builder/app/builder/features/topbar/entri.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/builder/app/builder/features/topbar/entri.tsx b/apps/builder/app/builder/features/topbar/entri.tsx index 85c5daa5ed1a..eacb1dcd6f7c 100644 --- a/apps/builder/app/builder/features/topbar/entri.tsx +++ b/apps/builder/app/builder/features/topbar/entri.tsx @@ -1,14 +1,8 @@ import * as entri from "entrijs"; import { useEffect, useState } from "react"; -import { - globalCss, - Button, - Text, - ProBadge, - toast, -} from "@webstudio-is/design-system"; -import { trpcClient } from "~/shared/trpc/trpc-client"; import { useStore } from "@nanostores/react"; +import { globalCss, Button, Text, toast } from "@webstudio-is/design-system"; +import { trpcClient } from "~/shared/trpc/trpc-client"; import { $userPlanFeatures } from "~/shared/nano-states"; import { extractCname } from "./cname"; @@ -112,7 +106,7 @@ export const Entri = ({ domain, dnsRecords, onClose }: EntriProps) => { type="button" onClick={() => { // @todo temporary for testing - if (hasProPlan || true) { + if (hasProPlan) { showDialog(); } else { toast.error( From d1872409ad3fdcdbe6945e39b7eebc2a72f01dba Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Thu, 26 Jun 2025 11:55:35 +0300 Subject: [PATCH 12/12] Trigger rebuild