Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Dashboards search #4781

Merged
merged 32 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
aff95b1
search wip
kof Jan 23, 2025
33b639f
wip search
kof Jan 24, 2025
2043256
empty state
kof Jan 25, 2025
57090cb
make project items tabbable
kof Jan 25, 2025
095b2bf
Merge branch 'main' into dash-search
kof Jan 25, 2025
e8d816f
simplify
kof Jan 25, 2025
c23cdfe
remove old logic
kof Jan 25, 2025
ac25b4d
search rewrite
kof Jan 26, 2025
a264a77
enable view transition
kof Jan 27, 2025
a9a7463
shouldRevalidate
kof Jan 27, 2025
074f732
stories
kof Jan 27, 2025
d7c8b5a
test secret login via keyboard enter
kof Jan 27, 2025
c1ad082
move background from body, because it sometimes doesn't get rerender …
kof Jan 27, 2025
8aea518
Merge branch 'main' into dash-search
kof Jan 27, 2025
c9c1844
use form in login
kof Jan 27, 2025
335511b
fix csrf
kof Jan 27, 2025
a3283d8
simplify
kof Jan 27, 2025
9c39091
Revert "simplify"
kof Jan 27, 2025
898b4a2
fix revalidation issue
kof Jan 27, 2025
0ef1f72
avoid redirect, because it can get stuck easily
kof Jan 28, 2025
15cd718
lint
kof Jan 28, 2025
67a4d8f
experimentally delete viewTransition
kof Jan 28, 2025
7888422
Merge branch 'main' into dash-search
kof Jan 28, 2025
0d65fe9
test with empty cards
kof Jan 28, 2025
53556ab
Revert "test with empty cards"
kof Jan 28, 2025
2624025
Revert "experimentally delete viewTransition"
kof Jan 28, 2025
125b5f4
Reapply "experimentally delete viewTransition"
kof Jan 28, 2025
1efb06e
fix search for new users
kof Jan 28, 2025
3cafba3
don't show projects/templates nav when no projects and we are in
kof Jan 28, 2025
437ad33
rename cancel to abort
kof Jan 28, 2025
fc66eea
Merge branch 'main' into dash-search
kof Jan 28, 2025
5465a84
stories
kof Jan 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 79 additions & 58 deletions apps/builder/app/dashboard/dashboard.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { StoryFn } from "@storybook/react";
import type { JSX } from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { Dashboard } from "./dashboard";
import { createMemoryRouter, RouterProvider } from "react-router-dom";
import { Dashboard, DashboardSetup } from "./dashboard";
import type { UserPlanFeatures } from "~/shared/db/user-plan-features.server";
import type { DashboardProject } from "@webstudio-is/dashboard";

Expand All @@ -20,14 +20,10 @@ const user = {
provider: "github",
};

const createRouter = (element: JSX.Element) =>
createBrowserRouter([
{
path: "*",
element,
loader: () => null,
},
]);
const createRouter = (element: JSX.Element, path: string, current?: string) =>
createMemoryRouter([{ path, element }], {
initialEntries: [current ?? path],
});

const userPlanFeatures: UserPlanFeatures = {
hasProPlan: false,
Expand All @@ -38,60 +34,85 @@ const userPlanFeatures: UserPlanFeatures = {
maxDomainsAllowedPerUser: 1,
};

export const WithProjects: StoryFn<typeof Dashboard> = () => {
const projects = [
{
id: "0",
createdAt: new Date().toString(),
title: "My Project",
domain: "domain.com",
userId: "",
isDeleted: false,
isPublished: false,
latestBuild: null,
previewImageAsset: null,
previewImageAssetId: "",
latestBuildVirtual: null,
marketplaceApprovalStatus: "UNLISTED" as const,
} as DashboardProject,
];
const projects = [
{
id: "0",
createdAt: new Date().toString(),
title: "My Project",
domain: "domain.com",
userId: "",
isDeleted: false,
isPublished: false,
latestBuild: null,
previewImageAsset: null,
previewImageAssetId: "",
latestBuildVirtual: null,
marketplaceApprovalStatus: "UNLISTED" as const,
} as DashboardProject,
];

const data = {
user,
templates: projects,
userPlanFeatures,
publisherHost: "https://wstd.work",
projects,
};

export const Welcome: StoryFn<typeof Dashboard> = () => {
const router = createRouter(
<>
<DashboardSetup data={{ ...data, projects: [] }} />
<Dashboard />
</>,
"/dashboard/templates"
);
return <RouterProvider router={router} />;
};

export const Projects: StoryFn<typeof Dashboard> = () => {
const router = createRouter(
<Dashboard
user={user}
welcome={false}
projects={projects}
userPlanFeatures={userPlanFeatures}
publisherHost={"https://wstd.work"}
/>
<>
<DashboardSetup data={data} />
<Dashboard />
</>,
"/dashboard"
);
return <RouterProvider router={router} />;
};

export const WithTemplates: StoryFn<typeof Dashboard> = () => {
const templates = [
{
id: "0",
createdAt: new Date().toString(),
title: "My Project",
domain: "domain.com",
userId: "",
isDeleted: false,
isPublished: false,
latestBuild: null,
previewImageAsset: null,
previewImageAssetId: "",
latestBuildVirtual: null,
marketplaceApprovalStatus: "UNLISTED" as const,
} as DashboardProject,
];
export const Templates: StoryFn<typeof Dashboard> = () => {
const router = createRouter(
<>
<DashboardSetup data={data} />
<Dashboard />
</>,
"/dashboard/templates"
);
return <RouterProvider router={router} />;
};

export const Search: StoryFn<typeof Dashboard> = () => {
const router = createRouter(
<>
<DashboardSetup data={data} />
<Dashboard />
</>,
"/dashboard/search",
"/dashboard/search?q=my"
);

return <RouterProvider router={router} />;
};

export const SearchNothingFound: StoryFn<typeof Dashboard> = () => {
const router = createRouter(
<Dashboard
user={user}
templates={templates}
welcome
userPlanFeatures={userPlanFeatures}
publisherHost={"https://wstd.work"}
/>
<>
<DashboardSetup data={data} />
<Dashboard />
</>,
"/dashboard/search",
"/dashboard/search?q=notfound"
);
return <RouterProvider router={router} />;
};
123 changes: 76 additions & 47 deletions apps/builder/app/dashboard/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ import {
globalCss,
theme,
} from "@webstudio-is/design-system";
import type { DashboardProject } from "@webstudio-is/dashboard";
import { BodyIcon, ExtensionIcon } from "@webstudio-is/icons";
import type { User } from "~/shared/db/user.server";
import type { UserPlanFeatures } from "~/shared/db/user-plan-features.server";
import { NavLink, useLocation, useRevalidator } from "@remix-run/react";
import { atom } from "nanostores";
import { useStore } from "@nanostores/react";
import { CloneProjectDialog } from "~/shared/clone-project";
import { dashboardPath, templatesPath } from "~/shared/router-utils";
import { dashboardPath } from "~/shared/router-utils";
import { CollapsibleSection } from "~/builder/shared/collapsible-section";
import { ProfileMenu } from "./profile-menu";
import { Projects } from "./projects/projects";
import { Templates } from "./templates/templates";
import { Header } from "./shared/layout";
import { help } from "~/shared/help";
import { SearchResults } from "./search/search-results";
import type { DashboardData } from "./shared/types";
import { Search } from "./search/search-field";

const globalStyles = globalCss({
body: {
Expand All @@ -33,7 +35,7 @@ const globalStyles = globalCss({
const CloneProject = ({
projectToClone,
}: {
projectToClone: DashboardProps["projectToClone"];
projectToClone: DashboardData["projectToClone"];
}) => {
const location = useLocation();
const [isOpen, setIsOpen] = useState(projectToClone !== undefined);
Expand All @@ -51,20 +53,22 @@ const CloneProject = ({
window.history.replaceState(currentState, "", location.pathname);
}, [location.search, location.pathname]);

return projectToClone !== undefined ? (
<CloneProjectDialog
isOpen={isOpen}
onOpenChange={setIsOpen}
project={{
id: projectToClone.id,
title: projectToClone.title,
}}
authToken={projectToClone.authToken}
onCreate={() => {
revalidate();
}}
/>
) : undefined;
if (projectToClone !== undefined) {
return (
<CloneProjectDialog
isOpen={isOpen}
onOpenChange={setIsOpen}
project={{
id: projectToClone.id,
title: projectToClone.title,
}}
authToken={projectToClone.authToken}
onCreate={() => {
revalidate();
}}
/>
);
}
};

const sidebarLinkStyle = css({
Expand Down Expand Up @@ -99,7 +103,7 @@ const NavigationItems = ({
<List style={{ padding: 0, margin: 0 }}>
{items.map((item, index) => {
return (
<ListItem asChild index={index}>
<ListItem asChild index={index} key={index}>
<NavLink
to={item.to}
end
Expand All @@ -118,30 +122,42 @@ const NavigationItems = ({
);
};

type DashboardProps = {
user: User;
projects?: Array<DashboardProject>;
templates?: Array<DashboardProject>;
welcome: boolean;
userPlanFeatures: UserPlanFeatures;
publisherHost: string;
projectToClone?: {
authToken: string;
id: string;
title: string;
};
};
const $data = atom<DashboardData | undefined>();

export const Dashboard = ({
user,
projects,
templates,
welcome,
userPlanFeatures,
publisherHost,
projectToClone,
}: DashboardProps) => {
export const DashboardSetup = ({ data }: { data: DashboardData }) => {
$data.set(data);
globalStyles();
return undefined;
};

const getView = (pathname: string) => {
if (pathname === dashboardPath("templates")) {
return "templates";
}
if (pathname === dashboardPath("search")) {
return "search";
}
return "projects";
};

export const Dashboard = () => {
const data = useStore($data);
const location = useLocation();

if (data === undefined) {
return;
}

const {
user,
userPlanFeatures,
publisherHost,
projectToClone,
projects,
templates,
} = data;
const view = getView(location.pathname);
const welcome = view === "templates" && projects.length === 0;

return (
<TooltipProvider>
Expand All @@ -160,26 +176,36 @@ export const Dashboard = ({
<Header variant="aside">
<ProfileMenu user={user} userPlanFeatures={userPlanFeatures} />
</Header>
<Flex
direction="column"
gap="3"
css={{
paddingInline: theme.spacing[7],
paddingBottom: theme.spacing[7],
}}
>
<Search />
</Flex>
<nav>
<CollapsibleSection label="Workspace" fullWidth>
<NavigationItems
items={
welcome
? [
{
to: templatesPath(),
to: dashboardPath("templates"),
prefix: <ExtensionIcon />,
children: "Welcome",
},
]
: [
{
to: dashboardPath(),
to: dashboardPath("projects"),
prefix: <BodyIcon />,
children: "Projects",
},
{
to: templatesPath(),
to: dashboardPath("templates"),
prefix: <ExtensionIcon />,
children: "Starter templates",
},
Expand All @@ -199,14 +225,17 @@ export const Dashboard = ({
</CollapsibleSection>
</nav>
</Flex>
{projects && (
{view === "projects" && (
<Projects
projects={projects}
hasProPlan={userPlanFeatures.hasProPlan}
publisherHost={publisherHost}
/>
)}
{templates && <Templates templates={templates} welcome={welcome} />}
{view === "templates" && (
<Templates projects={templates} welcome={welcome} />
)}
{view === "search" && <SearchResults {...data} />}
</Flex>
<CloneProject projectToClone={projectToClone} />
<Toaster />
Expand Down
11 changes: 0 additions & 11 deletions apps/builder/app/dashboard/projects/empty-state.tsx

This file was deleted.

Loading
Loading