From 4a81b988b43ba0b5afb81d4f3eba3561ab3cbf1d Mon Sep 17 00:00:00 2001 From: Saheb Giri <47132373+iamsahebgiri@users.noreply.github.com> Date: Thu, 23 Mar 2023 16:12:14 +0530 Subject: [PATCH] feat: implemented new pages design with bare minimum functionality (#503) * chore: add page types and page api service * chore: add create, list, update and delete on pages * chore: add create, delete and patch page blocks * feat: add and remove pages to favorite * fix: made neccessary changes - used tailwind for hover events - add error toast alert - used partial for patch request * fix: replace absolute positiong with a flex box * fix: design list page view to match with ui * feat: add top large textarea for page title and description * refactor: add page label with types * feat: add pages grid layout * feat: add tabs and masonry layout * fix: build errors --------- Co-authored-by: Aaryan Khandelwal --- apps/app/components/pages/page-grid-item.tsx | 172 ++++++++++++++++++ apps/app/components/pages/page-label.tsx | 23 +++ apps/app/components/pages/pages-grid.tsx | 58 ++++++ apps/app/components/pages/pages-list.tsx | 22 +-- apps/app/components/pages/pages-masonry.tsx | 82 +++++++++ .../pages/single-page-list-item.tsx | 17 +- .../projects/[projectId]/pages/index.tsx | 118 +++++++++++- 7 files changed, 455 insertions(+), 37 deletions(-) create mode 100644 apps/app/components/pages/page-grid-item.tsx create mode 100644 apps/app/components/pages/page-label.tsx create mode 100644 apps/app/components/pages/pages-grid.tsx create mode 100644 apps/app/components/pages/pages-masonry.tsx diff --git a/apps/app/components/pages/page-grid-item.tsx b/apps/app/components/pages/page-grid-item.tsx new file mode 100644 index 000000000..5f8b7b90d --- /dev/null +++ b/apps/app/components/pages/page-grid-item.tsx @@ -0,0 +1,172 @@ +import React from "react"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { mutate } from "swr"; + +// services +import pagesService from "services/pages.service"; +// ui +import { CustomMenu } from "components/ui"; +// icons +import { PencilIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline"; +// helpers +import { truncateText } from "helpers/string.helper"; +// hooks +import useToast from "hooks/use-toast"; +// types +import { IPage } from "types"; +// fetch keys +import { PAGE_LIST } from "constants/fetch-keys"; +import Label from "./page-label"; + +type TSingleStatProps = { + page: IPage; + handleEditPage: () => void; + handleDeletePage: () => void; +}; + +export const SinglePageGridItem: React.FC = (props) => { + const { page, handleEditPage, handleDeletePage } = props; + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { setToastAlert } = useToast(); + + const handleAddToFavorites = () => { + if (!workspaceSlug && !projectId && !page) return; + + pagesService + .addPageToFavorites(workspaceSlug as string, projectId as string, { + page: page.id, + }) + .then(() => { + mutate( + PAGE_LIST(projectId as string), + (prevData) => + (prevData ?? []).map((m) => ({ + ...m, + is_favorite: m.id === page.id ? true : m.is_favorite, + })), + false + ); + setToastAlert({ + type: "success", + title: "Success!", + message: "Successfully added the page to favorites.", + }); + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Couldn't add the page to favorites. Please try again.", + }); + }); + }; + + const handleRemoveFromFavorites = () => { + if (!workspaceSlug || !page) return; + + pagesService + .removePageFromFavorites(workspaceSlug as string, projectId as string, page.id) + .then(() => { + mutate( + PAGE_LIST(projectId as string), + (prevData) => + (prevData ?? []).map((m) => ({ + ...m, + is_favorite: m.id === page.id ? false : m.is_favorite, + })), + false + ); + setToastAlert({ + type: "success", + title: "Success!", + message: "Successfully removed the page from favorites.", + }); + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Couldn't remove the page from favorites. Please try again.", + }); + }); + }; + + return ( +
+
  • +
    +
    +
    + + +

    {truncateText(page.name, 75)}

    +
    + + + + +
    + +
    +
    +

    + {new Date(page.updated_at).toLocaleTimeString()} +

    + {page.is_favorite ? ( + + ) : ( + + )} + + + + + Edit Page + + + + + + Delete Page + + + +
    +
    +
    +
    +

    + Lorem ipsum dolor sit amet consectetur adipisicing elit. Reiciendis atque aliquam + saepe sapiente illo ratione delectus dolorem repellat, id autem, molestiae neque + quaerat ipsum perspiciatis pariatur? Unde consectetur quibusdam ut. +

    +

    + Quisquam quas expedita cupiditate ipsum cumque fugit at, optio quia ea? Id doloribus + assumenda ad magni laborum aut, aspernatur nemo similique, suscipit dolores porro + necessitatibus, inventore ab aliquid molestias. Aspernatur. +

    +

    + Beatae obcaecati minus temporibus sunt, quo nulla, tenetur nisi sit maiores aspernatur + numquam facilis asperiores eos rerum, ad dolorem quos laboriosam dicta eaque! Pariatur + magni eos, architecto itaque esse minus. +

    +

    + Dolorum saepe impedit officiis odit! Porro aliquid dolorum corporis impedit eaque + iusto, illo hic neque quia vero aperiam? Nemo aliquam, hic incidunt mollitia totam + asperiores sunt nam inventore voluptatibus eum? +

    +
    +
    +
    +
  • +
    + ); +}; diff --git a/apps/app/components/pages/page-label.tsx b/apps/app/components/pages/page-label.tsx new file mode 100644 index 000000000..38ffd0496 --- /dev/null +++ b/apps/app/components/pages/page-label.tsx @@ -0,0 +1,23 @@ +import React from "react"; + +type TLabelProps = { + variant: "red" | "blue" | string; + children?: React.ReactNode; +}; + +const Label: React.FC = (props) => { + let color = "bg-green-100 text-green-800"; + + if (props.variant === "red") { + color = "bg-red-100 text-red-800"; + } else if (props.variant === "blue") { + color = "bg-blue-100 text-blue-800"; + } + return ( +

    + {props.children} +

    + ); +}; + +export default Label; diff --git a/apps/app/components/pages/pages-grid.tsx b/apps/app/components/pages/pages-grid.tsx new file mode 100644 index 000000000..0895331b1 --- /dev/null +++ b/apps/app/components/pages/pages-grid.tsx @@ -0,0 +1,58 @@ +import { useState } from "react"; +// components +import { DeletePageModal } from "components/pages"; +import { Loader } from "components/ui"; +import { SinglePageGridItem } from "components/pages/page-grid-item"; +// types +import { IPage } from "types"; + +export const PagesGrid: React.FC = ({ pages, setCreateUpdatePageModal, setSelectedPage }) => { + const [pageDeleteModal, setPageDeleteModal] = useState(false); + const [selectedPageForDelete, setSelectedPageForDelete] = useState(); + + const handleDeletePage = (page: IPage) => { + setSelectedPageForDelete({ ...page, actionType: "delete" }); + setPageDeleteModal(true); + }; + + const handleEditPage = (page: IPage) => { + setSelectedPage({ ...page, actionType: "edit" }); + setCreateUpdatePageModal(true); + }; + + return ( + <> + + {pages ? ( + pages.length > 0 ? ( +
    +
      + {pages.map((page: any) => ( + handleDeletePage(page)} + handleEditPage={() => handleEditPage(page)} + /> + ))} +
    +
    + ) : ( + "No Pages found" + ) + ) : ( + + + + )} + + ); +}; diff --git a/apps/app/components/pages/pages-list.tsx b/apps/app/components/pages/pages-list.tsx index 8bfc2b069..d595b4b4c 100644 --- a/apps/app/components/pages/pages-list.tsx +++ b/apps/app/components/pages/pages-list.tsx @@ -42,18 +42,16 @@ export const PagesList: React.FC = ({ /> {pages ? ( pages.length > 0 ? ( -
    -
      - {pages.map((page) => ( - handleDeletePage(page)} - handleEditPage={() => handleEditPage(page)} - /> - ))} -
    -
    +
      + {pages.map((page) => ( + handleDeletePage(page)} + handleEditPage={() => handleEditPage(page)} + /> + ))} +
    ) : ( "No Pages found" ) diff --git a/apps/app/components/pages/pages-masonry.tsx b/apps/app/components/pages/pages-masonry.tsx new file mode 100644 index 000000000..10a0a726f --- /dev/null +++ b/apps/app/components/pages/pages-masonry.tsx @@ -0,0 +1,82 @@ +import React from "react"; + +// ui +import { CustomMenu } from "components/ui"; +// icons +import { PencilIcon, StarIcon, SwatchIcon, TrashIcon } from "@heroicons/react/24/outline"; + +const MasonryItem: React.FC = (props) => ( +
    +

    Personal Diary

    +

    {props.children}

    +
    +
    +
    + + {false ? ( + + ) : ( + + )} + + {}}> + + + Edit Page + + + {}}> + + + Delete Page + + + +
    +

    9:41 PM

    +
    +
    +
    +); + +export default function PagesMasonry() { + return ( +
    + + Lorem, ipsum dolor sit amet consectetur adipisicing elit. Impedit porro mollitia iure, + reiciendis quo tempora rem debitis velit quas doloremque. Dicta velit voluptas, blanditiis + excepturi vitae eum corporis totam eius? + + + Lorem, ipsum dolor sit amet consectetur adipisicing elit. Impedit porro mollitia iure, + reiciendis quo tempora rem debitis velit quas doloremque. Dicta velit voluptas, blanditiis + excepturi vitae eum corporis totam eius? Lorem, ipsum dolor sit amet consectetur adipisicing + elit. Impedit porro mollitia iure, reiciendis quo tempora rem debitis velit quas doloremque. + Dicta velit voluptas, blanditiis excepturi vitae eum corporis totam eius? + + + Lorem, ipsum dolor sit amet consectetur adipisicing elit. Impedit porro mollitia iure, + reiciendis quo tempora rem debitis velit quas doloremque. Dicta velit voluptas, blanditiis + excepturi vitae eum corporis totam eius? + + + Lorem, ipsum dolor sit amet consectetur adipisicing elit. Impedit porro mollitia iure, + reiciendis quo tempora rem debitis velit quas doloremque. Dicta velit voluptas, blanditiis + excepturi vitae eum corporis totam eius? Lorem, ipsum dolor sit amet consectetur adipisicing + elit. Impedit porro mollitia iure, reiciendis quo tempora rem debitis velit quas doloremque. + Dicta velit voluptas, blanditiis excepturi vitae eum corporis totam eius? + +
    + ); +} diff --git a/apps/app/components/pages/single-page-list-item.tsx b/apps/app/components/pages/single-page-list-item.tsx index 93fecd66b..af0e73b10 100644 --- a/apps/app/components/pages/single-page-list-item.tsx +++ b/apps/app/components/pages/single-page-list-item.tsx @@ -7,6 +7,7 @@ import { mutate } from "swr"; import pagesService from "services/pages.service"; // ui import { CustomMenu } from "components/ui"; +import Label from "./page-label"; // icons import { PencilIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline"; // helpers @@ -24,20 +25,6 @@ type TSingleStatProps = { handleDeletePage: () => void; }; -const Label: React.FC = (props) => { - let color = "bg-green-100 text-green-800"; - if (props.variant === "red") { - color = "bg-red-100 text-red-800"; - } else if (props.variant === "blue") { - color = "bg-blue-100 text-blue-800"; - } - return ( -

    - {props.children} -

    - ); -}; - export const SinglePageListItem: React.FC = (props) => { const { page, handleEditPage, handleDeletePage } = props; @@ -111,7 +98,7 @@ export const SinglePageListItem: React.FC = (props) => { return (
  • -
    +
    diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx index e8d6c74de..7a8e7466b 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx @@ -24,10 +24,29 @@ import { HeaderButton } from "components/ui"; import { CreateUpdatePageModal } from "components/pages/create-update-page-modal"; import { PagesList } from "components/pages/pages-list"; import { IPage } from "types"; +import PagesMasonry from "components/pages/pages-masonry"; +import { Tab } from "@headlessui/react"; +import { ListBulletIcon, RectangleGroupIcon, Squares2X2Icon } from "@heroicons/react/20/solid"; +import { PagesGrid } from "components/pages/pages-grid"; + +const TabPill: React.FC = (props) => ( + + `rounded-full border px-5 py-1.5 text-sm outline-none ${ + selected + ? "border-theme bg-theme text-white" + : "border-gray-300 bg-white hover:bg-hover-gray" + }` + } + > + {props.children} + +); const ProjectPages: NextPage = () => { const [isCreateUpdatePageModalOpen, setIsCreateUpdatePageModalOpen] = useState(false); const [selectedPage, setSelectedPage] = useState(); + const [viewType, setViewType] = useState("list"); const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -82,17 +101,96 @@ const ProjectPages: NextPage = () => { handleClose={() => setIsCreateUpdatePageModalOpen(false)} data={selectedPage} /> -
    -

    Pages

    -

    - Note down all the important and minor details in the way you want to. -

    +
    +
    + + + +