Skip to content

Commit 1e1edf7

Browse files
authored
feat: Built with webstudio badge check on publish (#5246)
## Description <img width="351" alt="image" src="https://github.com/user-attachments/assets/1afca5f0-da0f-4726-a29a-cd9ed2403967" /> ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 0000) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
1 parent 9d6cac5 commit 1e1edf7

File tree

2 files changed

+72
-37
lines changed

2 files changed

+72
-37
lines changed

apps/builder/app/builder/features/topbar/publish.tsx

Lines changed: 70 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
PopoverTitleActions,
3939
css,
4040
textVariants,
41+
SmallIconButton,
4142
} from "@webstudio-is/design-system";
4243
import { validateProjectDomain, type Project } from "@webstudio-is/project";
4344
import {
@@ -51,6 +52,7 @@ import {
5152
$instances,
5253
$pages,
5354
$project,
55+
$propsIndex,
5456
$publishedOrigin,
5557
$userPlanFeatures,
5658
} from "~/shared/nano-states";
@@ -64,11 +66,13 @@ import {
6466
CopyIcon,
6567
GearIcon,
6668
UpgradeIcon,
69+
HelpIcon,
6770
} from "@webstudio-is/icons";
6871
import { AddDomain } from "./add-domain";
6972
import { humanizeString } from "~/shared/string-utils";
7073
import { trpcClient, nativeClient } from "~/shared/trpc/trpc-client";
7174
import {
75+
findTreeInstanceIds,
7276
isPathnamePattern,
7377
parseComponentName,
7478
type Templates,
@@ -217,9 +221,12 @@ const ChangeProjectDomain = ({
217221
};
218222

219223
const $usedProFeatures = computed(
220-
[$pages, $dataSources, $instances],
221-
(pages, dataSources, instances) => {
222-
const features = new Map<string, undefined | Awareness>();
224+
[$pages, $dataSources, $instances, $propsIndex],
225+
(pages, dataSources, instances, propsIndex) => {
226+
const features = new Map<
227+
string,
228+
undefined | { awareness?: Awareness; info?: string }
229+
>();
223230
if (pages === undefined) {
224231
return features;
225232
}
@@ -229,43 +236,63 @@ const $usedProFeatures = computed(
229236
}
230237
// pages with dynamic paths
231238
for (const page of [pages.homePage, ...pages.pages]) {
239+
const awareness = {
240+
pageId: page.id,
241+
instanceSelector: [page.rootInstanceId],
242+
};
232243
if (isPathnamePattern(page.path)) {
233-
features.set("Dynamic path", {
234-
pageId: page.id,
235-
instanceSelector: [page.rootInstanceId],
236-
});
244+
features.set("Dynamic path", { awareness });
237245
}
238246
if (page.meta.status && page.meta.status !== `200`) {
239-
features.set("Page status code", {
240-
pageId: page.id,
241-
instanceSelector: [page.rootInstanceId],
242-
});
247+
features.set("Page status code", { awareness });
243248
}
244249
if (page.meta.redirect && page.meta.redirect !== `""`) {
245-
features.set("Redirect", {
246-
pageId: page.id,
247-
instanceSelector: [page.rootInstanceId],
248-
});
250+
features.set("Redirect", { awareness });
249251
}
250252
}
251253
// has resource variables
252254
for (const dataSource of dataSources.values()) {
253255
if (dataSource.type === "resource") {
254256
const instanceId = dataSource.scopeInstanceId ?? "";
255-
features.set(
256-
"Resource variable",
257-
findAwarenessByInstanceId(pages, instances, instanceId)
258-
);
257+
features.set("Resource variable", {
258+
awareness: findAwarenessByInstanceId(pages, instances, instanceId),
259+
});
259260
}
260261
}
261-
// instances with animations
262+
263+
// Instances with animations.
262264
for (const instance of instances.values()) {
263265
const [namespace] = parseComponentName(instance.component);
264266
if (namespace === "@webstudio-is/sdk-components-animation") {
265-
features.set(
266-
"Animation component",
267-
findAwarenessByInstanceId(pages, instances, instance.id)
268-
);
267+
features.set("Animation component", {
268+
awareness: findAwarenessByInstanceId(pages, instances, instance.id),
269+
});
270+
}
271+
}
272+
273+
const badgeFeature = 'No "Built with Webstudio" badge';
274+
// Badge should be rendered on free sites on every page.
275+
features.set(badgeFeature, {
276+
info: "Adding the badge to your homepage helps us offer a free version of the service. Please open the Components panel by clicking the “+” icon on the left, and add the “Built with Webstudio” component to your page. Feel free to adjust the badge’s styles to match your design.",
277+
});
278+
// We want to check the badge only on the home page
279+
const homePageInstanceIds = findTreeInstanceIds(
280+
instances,
281+
pages.homePage.rootInstanceId
282+
);
283+
for (const instanceId of homePageInstanceIds) {
284+
const instance = instances.get(instanceId);
285+
// Find a potential link that looks like a badge.
286+
if (instance?.tag === "a") {
287+
const props = propsIndex.propsByInstanceId.get(instance.id);
288+
for (const prop of props ?? []) {
289+
if (
290+
prop.name === "href" &&
291+
prop.value === "https://webstudio.is/?via=badge"
292+
) {
293+
features.delete(badgeFeature);
294+
}
295+
}
269296
}
270297
}
271298
return features;
@@ -735,19 +762,26 @@ const Content = (props: {
735762
</Text>
736763
<Text as="ul">
737764
{Array.from(usedProFeatures).map(
738-
([message, awareness], index) => (
765+
([message, { awareness, info } = {}], index) => (
739766
<li key={index}>
740-
{awareness ? (
741-
<button
742-
className={buttonLinkClass}
743-
type="button"
744-
onClick={() => $awareness.set(awareness)}
745-
>
746-
{message}
747-
</button>
748-
) : (
749-
message
750-
)}
767+
<Flex align="center" gap="1">
768+
{awareness ? (
769+
<button
770+
className={buttonLinkClass}
771+
type="button"
772+
onClick={() => $awareness.set(awareness)}
773+
>
774+
{message}
775+
</button>
776+
) : (
777+
message
778+
)}
779+
{info && (
780+
<Tooltip variant="wrapped" content={info}>
781+
<SmallIconButton icon={<HelpIcon />} />
782+
</Tooltip>
783+
)}
784+
</Flex>
751785
</li>
752786
)
753787
)}

packages/sdk/src/core-templates.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,8 @@ const builtWithWebstudioMeta: TemplateMeta = {
363363
<ws.element
364364
ws:tag="a"
365365
ws:label="Built with Webstudio"
366-
href="https://webstudio.is?via=badge"
366+
// If you change this, you need to also update this link in publish checks
367+
href="https://webstudio.is/?via=badge"
367368
target="_blank"
368369
ws:style={css`
369370
display: inline-flex;

0 commit comments

Comments
 (0)