From 5a0ae89b2f43ee78f955840758bf95cb24fa8ec1 Mon Sep 17 00:00:00 2001 From: Andrej Date: Mon, 17 Feb 2025 20:52:07 +0100 Subject: [PATCH] feat: adding empty / error states --- .../src/project/controllers/create-project.ts | 9 ++- .../sidebar/sections/bottom-actions/index.tsx | 12 +++- .../projects/create-project-modal.tsx | 11 +++- .../sidebar/sections/projects/index.tsx | 26 ++++++++- .../web/src/components/kanban-board/index.tsx | 50 +++++++++++++++- .../src/components/project/empty-state.tsx | 57 ++++++++++++++++++ .../src/components/workspace/empty-state.tsx | 58 +++++++++++++++++++ apps/web/src/routes/dashboard.tsx | 11 +++- .../dashboard/workspace/$workspaceId.tsx | 20 ++++++- .../$workspaceId/project/$projectId.tsx | 33 ++++++++++- apps/web/vite.config.ts | 4 +- 11 files changed, 273 insertions(+), 18 deletions(-) create mode 100644 apps/web/src/components/project/empty-state.tsx create mode 100644 apps/web/src/components/workspace/empty-state.tsx diff --git a/apps/api/src/project/controllers/create-project.ts b/apps/api/src/project/controllers/create-project.ts index 088c2fd..b6f711b 100644 --- a/apps/api/src/project/controllers/create-project.ts +++ b/apps/api/src/project/controllers/create-project.ts @@ -2,10 +2,15 @@ import db from "../../database"; import { projectTable } from "../../database/schema"; import type { CreateProjectPayload } from "../db/queries"; -function createProject( +async function createProject( body: Pick, ) { - return db.insert(projectTable).values(body); + const [createdProject] = await db + .insert(projectTable) + .values(body) + .returning(); + + return createdProject; } export default createProject; diff --git a/apps/web/src/components/common/sidebar/sections/bottom-actions/index.tsx b/apps/web/src/components/common/sidebar/sections/bottom-actions/index.tsx index 648aa08..60c2f87 100644 --- a/apps/web/src/components/common/sidebar/sections/bottom-actions/index.tsx +++ b/apps/web/src/components/common/sidebar/sections/bottom-actions/index.tsx @@ -1,5 +1,7 @@ import { cn } from "@/lib/cn"; +import useProjectStore from "@/store/project"; import { useUserPreferencesStore } from "@/store/user-preferences"; +import useWorkspaceStore from "@/store/workspace"; import { useNavigate } from "@tanstack/react-router"; import { Settings } from "lucide-react"; import SignOutButton from "./sign-out-button"; @@ -7,12 +9,20 @@ import SignOutButton from "./sign-out-button"; function BottomActions() { const { isSidebarOpened } = useUserPreferencesStore(); const navigate = useNavigate(); + const { setProject } = useProjectStore(); + const { setWorkspace } = useWorkspaceStore(); + + const handleClickSettings = () => { + setProject(undefined); + setWorkspace(undefined); + navigate({ to: "/dashboard/settings/appearance" }); + }; return (
- {projects && - projects.length > 0 && + {projects && projects.length > 0 ? ( projects.map((project) => ( +
+ ) : null} TODO: Empty state.; + if (!project || !project.columns) { + return ( +
+
+
+
+
+
+ +
+
+ {[...Array(3)].map((_, i) => ( +
+
+
+
+
+ +
+ {[...Array(3)].map((_, j) => ( +
+
+
+
+
+
+ ))} +
+
+ ))} +
+
+
+ ); + } const activeTask = activeId ? project.columns @@ -95,7 +141,7 @@ function KanbanBoard() { sensors={sensors} >
-
+

{project?.name} diff --git a/apps/web/src/components/project/empty-state.tsx b/apps/web/src/components/project/empty-state.tsx new file mode 100644 index 0000000..a607c30 --- /dev/null +++ b/apps/web/src/components/project/empty-state.tsx @@ -0,0 +1,57 @@ +import { LayoutGrid, Plus } from "lucide-react"; +import { useState } from "react"; +import CreateProjectModal from "../common/sidebar/sections/projects/create-project-modal"; + +function EmptyProjectState() { + const [isCreateProjectOpen, setIsCreateProjectOpen] = useState(false); + + return ( +
+
+
+
+ +
+

+ Create your first project +

+

+ Get started by creating a project to organize your tasks and + collaborate with your team. +

+
+ +
+
+
+
+ +
+
+

+ New Project +

+

+ Create a project to organize your tasks +

+
+
+ +
+
+
+ setIsCreateProjectOpen(false)} + /> +
+ ); +} + +export default EmptyProjectState; diff --git a/apps/web/src/components/workspace/empty-state.tsx b/apps/web/src/components/workspace/empty-state.tsx new file mode 100644 index 0000000..c26f59b --- /dev/null +++ b/apps/web/src/components/workspace/empty-state.tsx @@ -0,0 +1,58 @@ +import { CreateWorkspaceModal } from "../common/sidebar/sections/workspaces/components/create-workspace-modal"; + +import { LayoutGrid, Plus } from "lucide-react"; +import { useState } from "react"; + +function EmptyWorkspaceState() { + const [isCreateWorkspaceOpen, setIsCreateWorkspaceOpen] = useState(false); + + return ( +
+
+
+
+ +
+

+ Create your first workspace +

+

+ Get started by creating a workspace to organize your projects and + collaborate with your team. +

+
+ +
+
+
+
+ +
+
+

+ New Workspace +

+

+ Create a workspace for your team +

+
+
+ +
+
+
+ setIsCreateWorkspaceOpen(false)} + /> +
+ ); +} + +export default EmptyWorkspaceState; diff --git a/apps/web/src/routes/dashboard.tsx b/apps/web/src/routes/dashboard.tsx index eb853f6..8a4d9dd 100644 --- a/apps/web/src/routes/dashboard.tsx +++ b/apps/web/src/routes/dashboard.tsx @@ -1,6 +1,7 @@ import { Sidebar } from "@/components/common/sidebar"; -import { Outlet, redirect } from "@tanstack/react-router"; -import { createFileRoute } from "@tanstack/react-router"; +import EmptyWorkspaceState from "@/components/workspace/empty-state"; +import useGetWorkspaces from "@/hooks/queries/workspace/use-get-workspaces"; +import { Outlet, createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/dashboard")({ component: DashboardIndexRouteComponent, @@ -14,11 +15,15 @@ export const Route = createFileRoute("/dashboard")({ }); function DashboardIndexRouteComponent() { + const { data } = useGetWorkspaces(); + + const isWorkspacesEmpty = data && data.length === 0; + return ( <>
- + {isWorkspacesEmpty ? : }
); diff --git a/apps/web/src/routes/dashboard/workspace/$workspaceId.tsx b/apps/web/src/routes/dashboard/workspace/$workspaceId.tsx index c7e437d..02d5647 100644 --- a/apps/web/src/routes/dashboard/workspace/$workspaceId.tsx +++ b/apps/web/src/routes/dashboard/workspace/$workspaceId.tsx @@ -1,6 +1,9 @@ +import EmptyProjectState from "@/components/project/empty-state"; +import useGetProjects from "@/hooks/queries/project/use-get-projects"; import useGetWorkspace from "@/hooks/queries/workspace/use-get-workspace"; import useWorkspaceStore from "@/store/workspace"; import { Outlet, createFileRoute } from "@tanstack/react-router"; +import { LayoutGrid } from "lucide-react"; import { useEffect } from "react"; export const Route = createFileRoute("/dashboard/workspace/$workspaceId")({ @@ -10,7 +13,8 @@ export const Route = createFileRoute("/dashboard/workspace/$workspaceId")({ function RouteComponent() { const { workspaceId } = Route.useParams(); const { setWorkspace } = useWorkspaceStore(); - const { data } = useGetWorkspace({ workspaceId }); + const { data, isLoading } = useGetWorkspace({ workspaceId }); + const { data: projects } = useGetProjects({ workspaceId }); useEffect(() => { if (data) { @@ -18,5 +22,17 @@ function RouteComponent() { } }, [data, setWorkspace]); - return ; + if (isLoading) { + return ( +
+
+ +
+
+ ); + } + + const isProjectsEmpty = projects && projects.length === 0; + + return <>{isProjectsEmpty ? : }; } diff --git a/apps/web/src/routes/dashboard/workspace/$workspaceId/project/$projectId.tsx b/apps/web/src/routes/dashboard/workspace/$workspaceId/project/$projectId.tsx index a11f91a..54bf3cb 100644 --- a/apps/web/src/routes/dashboard/workspace/$workspaceId/project/$projectId.tsx +++ b/apps/web/src/routes/dashboard/workspace/$workspaceId/project/$projectId.tsx @@ -1,7 +1,7 @@ import useGetProject from "@/hooks/queries/project/use-get-project"; import useProjectStore from "@/store/project"; -import { Outlet, createFileRoute } from "@tanstack/react-router"; -import { LayoutGrid } from "lucide-react"; +import { Link, Outlet, createFileRoute } from "@tanstack/react-router"; +import { AlertCircle, LayoutGrid } from "lucide-react"; import { useEffect } from "react"; export const Route = createFileRoute( @@ -12,7 +12,10 @@ export const Route = createFileRoute( function RouteComponent() { const { workspaceId, projectId } = Route.useParams(); - const { data, isFetching } = useGetProject({ id: projectId, workspaceId }); + const { data, isFetching, isError } = useGetProject({ + id: projectId, + workspaceId, + }); const { setProject } = useProjectStore(); useEffect(() => { @@ -31,5 +34,29 @@ function RouteComponent() { ); } + if (isError) { + return ( +
+
+ +
+

+ Project not found +

+

+ The project you're looking for doesn't exist or you don't have access + to it. +

+ + Return to workspace + +
+ ); + } + return ; } diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index f16255f..8d3d292 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -9,7 +9,9 @@ export default defineConfig({ plugins: [TanStackRouterVite(), react()], server: { host: true, - hmr: true, + hmr: { + host: "0.0.0.0", + }, port: 5173, }, resolve: {