Skip to content

Commit b7356e0

Browse files
authored
feat: List view and sorting of projects in dashboard (#5467)
## 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 21bce61 commit b7356e0

File tree

18 files changed

+2773
-706
lines changed

18 files changed

+2773
-706
lines changed

apps/builder/app/dashboard/dashboard.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ export const Dashboard = () => {
174174
as="aside"
175175
align="stretch"
176176
direction="column"
177+
shrink={false}
177178
css={{
178179
width: theme.sizes.sidebarWidth,
179180
borderRight: `1px solid ${theme.colors.borderMain}`,

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

Lines changed: 31 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import { useEffect, useState } from "react";
22
import {
3-
DropdownMenu,
4-
DropdownMenuTrigger,
5-
DropdownMenuContent,
6-
DropdownMenuItem,
7-
IconButton,
83
css,
94
Flex,
105
Text,
@@ -15,7 +10,7 @@ import {
1510
Link,
1611
Box,
1712
} from "@webstudio-is/design-system";
18-
import { InfoCircleIcon, EllipsesIcon } from "@webstudio-is/icons";
13+
import { InfoCircleIcon, UploadIcon } from "@webstudio-is/icons";
1914
import type { DashboardProject } from "@webstudio-is/dashboard";
2015
import { builderUrl } from "~/shared/router-utils";
2116
import {
@@ -32,6 +27,8 @@ import { Spinner } from "../shared/spinner";
3227
import { Card, CardContent, CardFooter } from "../shared/card";
3328
import type { User } from "~/shared/db/user.server";
3429
import { TagsDialog } from "./tags";
30+
import { ProjectMenu } from "./project-menu";
31+
import { formatDate } from "./utils";
3532

3633
const infoIconStyle = css({ flexShrink: 0 });
3734

@@ -58,52 +55,6 @@ const PublishedLink = ({
5855
);
5956
};
6057

61-
const Menu = ({
62-
tabIndex,
63-
onDelete,
64-
onRename,
65-
onDuplicate,
66-
onShare,
67-
onUpdateTags,
68-
}: {
69-
tabIndex: number;
70-
onDelete: () => void;
71-
onRename: () => void;
72-
onDuplicate: () => void;
73-
onShare: () => void;
74-
onUpdateTags: () => void;
75-
}) => {
76-
const [isOpen, setIsOpen] = useState(false);
77-
return (
78-
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
79-
<DropdownMenuTrigger asChild>
80-
<IconButton
81-
aria-label="Menu Button"
82-
tabIndex={tabIndex}
83-
css={{ alignSelf: "center" }}
84-
>
85-
<EllipsesIcon width={15} height={15} />
86-
</IconButton>
87-
</DropdownMenuTrigger>
88-
<DropdownMenuContent align="end" css={{ width: theme.spacing[24] }}>
89-
<DropdownMenuItem onSelect={onDuplicate}>Duplicate</DropdownMenuItem>
90-
<DropdownMenuItem onSelect={onRename}>Rename</DropdownMenuItem>
91-
<DropdownMenuItem onSelect={onShare}>Share</DropdownMenuItem>
92-
<DropdownMenuItem onSelect={onUpdateTags}>Tags</DropdownMenuItem>
93-
<DropdownMenuItem onSelect={onDelete}>Delete</DropdownMenuItem>
94-
</DropdownMenuContent>
95-
</DropdownMenu>
96-
);
97-
};
98-
99-
const formatDate = (date: string) => {
100-
return new Date(date).toLocaleDateString("en-US", {
101-
year: "numeric",
102-
month: "short",
103-
day: "numeric",
104-
});
105-
};
106-
10758
type ProjectCardProps = {
10859
project: DashboardProject;
10960
hasProPlan: boolean;
@@ -196,6 +147,24 @@ export const ProjectCard = ({
196147
opacity: 0,
197148
}}
198149
/>
150+
{isPublished && (
151+
<Box
152+
css={{
153+
position: "absolute",
154+
top: theme.spacing[5],
155+
left: theme.spacing[5],
156+
zIndex: 1,
157+
backgroundColor: "oklch(0 0 0 / 0.3)",
158+
borderRadius: theme.borderRadius[3],
159+
padding: theme.spacing[2],
160+
display: "flex",
161+
alignItems: "center",
162+
justifyContent: "center",
163+
}}
164+
>
165+
<UploadIcon color={rawTheme.colors.foregroundContrastMain} />
166+
</Box>
167+
)}
199168
<Flex
200169
wrap="wrap"
201170
gap={1}
@@ -245,13 +214,19 @@ export const ProjectCard = ({
245214
variant="wrapped"
246215
content={
247216
<Text variant="small">
248-
Created on {formatDate(createdAt)}
249-
{latestBuildVirtual?.publishStatus === "PUBLISHED" && (
217+
Created: {formatDate(createdAt)}
218+
{latestBuildVirtual?.updatedAt && (
250219
<>
251220
<br />
252-
Published on {formatDate(latestBuildVirtual.createdAt)}
221+
Last modified: {formatDate(latestBuildVirtual.updatedAt)}
253222
</>
254223
)}
224+
<br />
225+
{latestBuildVirtual?.publishStatus === "PUBLISHED" ? (
226+
<>Published: {formatDate(latestBuildVirtual.createdAt)}</>
227+
) : (
228+
<>Not published</>
229+
)}
255230
</Text>
256231
}
257232
>
@@ -268,8 +243,7 @@ export const ProjectCard = ({
268243
<Text color="subtle">Not Published</Text>
269244
)}
270245
</Flex>
271-
<Menu
272-
tabIndex={-1}
246+
<ProjectMenu
273247
onDelete={() => setIsDeleteDialogOpen(true)}
274248
onRename={() => setIsRenameDialogOpen(true)}
275249
onShare={() => setIsShareDialogOpen(true)}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useState } from "react";
2+
import {
3+
DropdownMenu,
4+
DropdownMenuTrigger,
5+
DropdownMenuContent,
6+
DropdownMenuItem,
7+
IconButton,
8+
theme,
9+
} from "@webstudio-is/design-system";
10+
import { EllipsesIcon } from "@webstudio-is/icons";
11+
12+
type ProjectMenuProps = {
13+
onDelete: () => void;
14+
onRename: () => void;
15+
onDuplicate: () => void;
16+
onShare: () => void;
17+
onUpdateTags: () => void;
18+
};
19+
20+
export const ProjectMenu = ({
21+
onDelete,
22+
onRename,
23+
onDuplicate,
24+
onShare,
25+
onUpdateTags,
26+
}: ProjectMenuProps) => {
27+
const [isOpen, setIsOpen] = useState(false);
28+
return (
29+
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
30+
<DropdownMenuTrigger asChild>
31+
<IconButton
32+
aria-label="Menu Button"
33+
tabIndex={-1}
34+
css={{ alignSelf: "center", position: "relative", zIndex: 1 }}
35+
>
36+
<EllipsesIcon width={15} height={15} />
37+
</IconButton>
38+
</DropdownMenuTrigger>
39+
<DropdownMenuContent align="end" css={{ width: theme.spacing[24] }}>
40+
<DropdownMenuItem onSelect={onDuplicate}>Duplicate</DropdownMenuItem>
41+
<DropdownMenuItem onSelect={onRename}>Rename</DropdownMenuItem>
42+
<DropdownMenuItem onSelect={onShare}>Share</DropdownMenuItem>
43+
<DropdownMenuItem onSelect={onUpdateTags}>Tags</DropdownMenuItem>
44+
<DropdownMenuItem onSelect={onDelete}>Delete</DropdownMenuItem>
45+
</DropdownMenuContent>
46+
</DropdownMenu>
47+
);
48+
};

0 commit comments

Comments
 (0)