Skip to content

Commit 9f66c8d

Browse files
authored
feat: Show custom domain in the dashboard if it exists (#5466)
## Description 1. What is this PR about (link the issue and add a short description) ## 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 5239670 commit 9f66c8d

File tree

3 files changed

+95
-26
lines changed

3 files changed

+95
-26
lines changed

apps/builder/app/dashboard/dashboard.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const projects = [
5050
latestBuildVirtual: null,
5151
marketplaceApprovalStatus: "UNLISTED" as const,
5252
tags: [],
53+
domainsVirtual: [],
5354
} as DashboardProject,
5455
];
5556

apps/builder/app/dashboard/projects/project-card.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,12 @@ const infoIconStyle = css({ flexShrink: 0 });
3737

3838
const PublishedLink = ({
3939
domain,
40-
publisherHost,
4140
tabIndex,
4241
}: {
4342
domain: string;
44-
publisherHost: string;
4543
tabIndex: number;
4644
}) => {
47-
const publishedOrigin = `https://${domain}.${publisherHost}`;
45+
const publishedOrigin = `https://${domain}`;
4846
return (
4947
<Link
5048
href={publishedOrigin}
@@ -123,12 +121,19 @@ export const ProjectCard = ({
123121
latestBuildVirtual,
124122
previewImageAsset,
125123
tags,
124+
domainsVirtual,
126125
},
127126
hasProPlan,
128127
publisherHost,
129128
projectsTags,
130129
...props
131130
}: ProjectCardProps) => {
131+
// Determine which domain to display: custom domain if available, otherwise wstd subdomain
132+
const customDomain = domainsVirtual?.find(
133+
(d: { domain: string; status: string; verified: boolean }) =>
134+
d.status === "ACTIVE" && d.verified
135+
)?.domain;
136+
const displayDomain = customDomain ?? `${domain}.${publisherHost}`;
132137
const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false);
133138
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
134139
const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);
@@ -258,11 +263,7 @@ export const ProjectCard = ({
258263
</Tooltip>
259264
</Flex>
260265
{isPublished ? (
261-
<PublishedLink
262-
publisherHost={publisherHost}
263-
domain={domain}
264-
tabIndex={-1}
265-
/>
266+
<PublishedLink domain={displayDomain} tabIndex={-1} />
266267
) : (
267268
<Text color="subtle">Not Published</Text>
268269
)}

packages/dashboard/src/db/projects.ts

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,88 @@ import {
44
type AppContext,
55
} from "@webstudio-is/trpc-interface/index.server";
66

7+
type DomainVirtual = {
8+
domain: string;
9+
status: string;
10+
verified: boolean;
11+
};
12+
13+
const fetchAndMapDomains = async <
14+
T extends {
15+
id: string | null;
16+
title: string | null;
17+
domain: string | null;
18+
isDeleted: boolean | null;
19+
createdAt: string | null;
20+
marketplaceApprovalStatus: string | null;
21+
},
22+
>(
23+
projects: T[],
24+
context: AppContext
25+
) => {
26+
const projectIds = projects
27+
.map((project) => project.id)
28+
.filter((id): id is string => id !== null);
29+
30+
type ProjectWithDomains = SetNonNullable<
31+
T,
32+
| "id"
33+
| "title"
34+
| "domain"
35+
| "isDeleted"
36+
| "createdAt"
37+
| "marketplaceApprovalStatus"
38+
> & {
39+
domainsVirtual: DomainVirtual[];
40+
};
41+
42+
if (projectIds.length === 0) {
43+
return projects.map((project) => ({
44+
...project,
45+
domainsVirtual: [],
46+
})) as ProjectWithDomains[];
47+
}
48+
49+
// Query ProjectDomain and Domain tables
50+
const domainsData = await context.postgrest.client
51+
.from("ProjectDomain")
52+
.select("projectId, Domain!inner(domain, status, txtRecord), txtRecord")
53+
.in("projectId", projectIds);
54+
55+
if (domainsData.error) {
56+
console.error("Error fetching domains:", domainsData.error);
57+
// Continue without domains rather than failing
58+
}
59+
60+
// Map domains to projects
61+
const domainsByProject = new Map<string, DomainVirtual[]>();
62+
if (domainsData.data) {
63+
for (const projectDomain of domainsData.data) {
64+
if (!domainsByProject.has(projectDomain.projectId)) {
65+
domainsByProject.set(projectDomain.projectId, []);
66+
}
67+
// Type assertion needed for joined data
68+
const domainData = projectDomain.Domain as unknown as {
69+
domain: string;
70+
status: string;
71+
txtRecord: string;
72+
};
73+
const verified = domainData.txtRecord === projectDomain.txtRecord;
74+
domainsByProject.get(projectDomain.projectId)?.push({
75+
domain: domainData.domain,
76+
status: domainData.status,
77+
verified,
78+
});
79+
}
80+
}
81+
82+
// Add domains to projects
83+
return projects.map((project) => ({
84+
...project,
85+
domainsVirtual: project.id ? domainsByProject.get(project.id) || [] : [],
86+
})) as ProjectWithDomains[];
87+
};
88+
789
export type DashboardProject = Awaited<ReturnType<typeof findMany>>[number];
890

991
export const findMany = async (userId: string, context: AppContext) => {
@@ -30,15 +112,7 @@ export const findMany = async (userId: string, context: AppContext) => {
30112
throw data.error;
31113
}
32114

33-
return data.data as SetNonNullable<
34-
(typeof data.data)[number],
35-
| "id"
36-
| "title"
37-
| "domain"
38-
| "isDeleted"
39-
| "createdAt"
40-
| "marketplaceApprovalStatus"
41-
>[];
115+
return await fetchAndMapDomains(data.data, context);
42116
};
43117

44118
export const findManyByIds = async (
@@ -58,13 +132,6 @@ export const findManyByIds = async (
58132
if (data.error) {
59133
throw data.error;
60134
}
61-
return data.data as SetNonNullable<
62-
(typeof data.data)[number],
63-
| "id"
64-
| "title"
65-
| "domain"
66-
| "isDeleted"
67-
| "createdAt"
68-
| "marketplaceApprovalStatus"
69-
>[];
135+
136+
return await fetchAndMapDomains(data.data, context);
70137
};

0 commit comments

Comments
 (0)