feat: dynamic page title

This commit is contained in:
Henry
2024-11-19 18:14:37 +00:00
parent 6bd5a68a76
commit d7074814ae
8 changed files with 523 additions and 468 deletions

View File

@@ -0,0 +1,9 @@
import Head from "next/head";
export const PageHead = ({ title }: { title: string }) => {
return (
<Head>
<title>{title}</title>
</Head>
);
};

View File

@@ -1,6 +1,8 @@
import { useState } from "react"; import { useState } from "react";
// import { useRouter } from "next/navigation"; // import { useRouter } from "next/navigation";
import { Auth } from "~/components/AuthForm"; import { Auth } from "~/components/AuthForm";
import { PageHead } from "~/components/PageHead";
// import { api } from "~/utils/api"; // import { api } from "~/utils/api";
export default function LoginPage() { export default function LoginPage() {
@@ -24,28 +26,31 @@ export default function LoginPage() {
// if (data?.id) router.push("/boards"); // if (data?.id) router.push("/boards");
return ( return (
<main className="h-screen bg-dark-50"> <>
<div className="flex h-full flex-col items-center justify-center"> <PageHead title="Login | kan.bn" />
<h1 className="mb-6 text-lg font-bold tracking-tight text-dark-1000"> <main className="h-screen bg-dark-50">
kan.bn <div className="flex h-full flex-col items-center justify-center">
</h1> <h1 className="mb-6 text-lg font-bold tracking-tight text-dark-1000">
<p className="mb-10 text-3xl text-dark-1000"> kan.bn
{isMagicLinkSent ? "Check your inbox" : "Welcome back"} </h1>
</p> <p className="mb-10 text-3xl text-dark-1000">
{isMagicLinkSent ? ( {isMagicLinkSent ? "Check your inbox" : "Welcome back"}
<div className="sm:mx-auto sm:w-full sm:max-w-sm"> </p>
<p className="text-md mt-2 text-center text-dark-1000"> {isMagicLinkSent ? (
{`Click on the link we've sent to ${magicLinkRecipient} to sign in.`}
</p>
</div>
) : (
<div className="w-full rounded-lg border border-dark-400 bg-dark-200 px-10 py-10 sm:max-w-md">
<div className="sm:mx-auto sm:w-full sm:max-w-sm"> <div className="sm:mx-auto sm:w-full sm:max-w-sm">
<Auth setIsMagicLinkSent={handleMagicLinkSent} /> <p className="text-md mt-2 text-center text-dark-1000">
{`Click on the link we've sent to ${magicLinkRecipient} to sign in.`}
</p>
</div> </div>
</div> ) : (
)} <div className="w-full rounded-lg border border-dark-400 bg-dark-200 px-10 py-10 sm:max-w-md">
</div> <div className="sm:mx-auto sm:w-full sm:max-w-sm">
</main> <Auth setIsMagicLinkSent={handleMagicLinkSent} />
</div>
</div>
)}
</div>
</main>
</>
); );
} }

View File

@@ -1,6 +1,7 @@
import { useState } from "react"; import { useState } from "react";
// import { useRouter } from "next/navigation"; // import { useRouter } from "next/navigation";
import { Auth } from "~/components/AuthForm"; import { Auth } from "~/components/AuthForm";
import { PageHead } from "~/components/PageHead";
// import { api } from "~/utils/api"; // import { api } from "~/utils/api";
export default function SignupPage() { export default function SignupPage() {
@@ -24,28 +25,31 @@ export default function SignupPage() {
// if (data?.id) router.push("/boards"); // if (data?.id) router.push("/boards");
return ( return (
<main className="h-screen bg-dark-50"> <>
<div className="flex h-full flex-col items-center justify-center"> <PageHead title="Signup | kan.bn" />
<h1 className="mb-6 text-lg font-bold tracking-tight text-dark-1000"> <main className="h-screen bg-dark-50">
kan.bn <div className="flex h-full flex-col items-center justify-center">
</h1> <h1 className="mb-6 text-lg font-bold tracking-tight text-dark-1000">
<p className="mb-10 text-3xl text-dark-1000"> kan.bn
{isMagicLinkSent ? "Check your inbox" : "Get started"} </h1>
</p> <p className="mb-10 text-3xl text-dark-1000">
{isMagicLinkSent ? ( {isMagicLinkSent ? "Check your inbox" : "Get started"}
<div className="sm:mx-auto sm:w-full sm:max-w-sm"> </p>
<p className="text-md mt-2 text-center text-dark-1000"> {isMagicLinkSent ? (
{`Click on the link we've sent to ${magicLinkRecipient} to sign in.`}
</p>
</div>
) : (
<div className="w-full rounded-lg border border-dark-400 bg-dark-200 px-10 py-10 sm:max-w-md">
<div className="sm:mx-auto sm:w-full sm:max-w-sm"> <div className="sm:mx-auto sm:w-full sm:max-w-sm">
<Auth setIsMagicLinkSent={handleMagicLinkSent} /> <p className="text-md mt-2 text-center text-dark-1000">
{`Click on the link we've sent to ${magicLinkRecipient} to sign in.`}
</p>
</div> </div>
</div> ) : (
)} <div className="w-full rounded-lg border border-dark-400 bg-dark-200 px-10 py-10 sm:max-w-md">
</div> <div className="sm:mx-auto sm:w-full sm:max-w-sm">
</main> <Auth setIsMagicLinkSent={handleMagicLinkSent} />
</div>
</div>
)}
</div>
</main>
</>
); );
} }

View File

@@ -15,7 +15,9 @@ import { api } from "~/utils/api";
import { formatToArray } from "~/utils/helpers"; import { formatToArray } from "~/utils/helpers";
import { useBoard } from "~/providers/board"; import { useBoard } from "~/providers/board";
import { useModal } from "~/providers/modal"; import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/providers/workspace";
import { PageHead } from "~/components/PageHead";
import Modal from "~/components/modal"; import Modal from "~/components/modal";
import PatternedBackground from "~/components/PatternedBackground"; import PatternedBackground from "~/components/PatternedBackground";
@@ -36,6 +38,7 @@ export default function BoardPage() {
const params = useParams(); const params = useParams();
const router = useRouter(); const router = useRouter();
const { boardData, setBoardData, updateCard, updateList } = useBoard(); const { boardData, setBoardData, updateCard, updateList } = useBoard();
const { workspace } = useWorkspace();
const { openModal, modalContentType } = useModal(); const { openModal, modalContentType } = useModal();
const [selectedPublicListId, setSelectedPublicListId] = const [selectedPublicListId, setSelectedPublicListId] =
useState<PublicListId>(""); useState<PublicListId>("");
@@ -135,181 +138,188 @@ export default function BoardPage() {
}; };
return ( return (
<div className="relative flex h-full flex-col"> <>
<PatternedBackground /> <PageHead
<div className="z-10 flex w-full justify-between p-8"> title={`${boardData?.name ?? "Board"} | ${workspace?.name ?? "Workspace"}`}
{isLoading ? ( />
<div className="flex space-x-2"> <div className="relative flex h-full flex-col">
<div className="h-[2.3rem] w-[150px] animate-pulse rounded-[5px] bg-light-200 dark:bg-dark-200" /> <PatternedBackground />
</div> <div className="z-10 flex w-full justify-between p-8">
) : ( {isLoading ? (
<form <div className="flex space-x-2">
onSubmit={handleSubmit(onSubmit)} <div className="h-[2.3rem] w-[150px] animate-pulse rounded-[5px] bg-light-200 dark:bg-dark-200" />
className="focus-visible:outline-none" </div>
> ) : (
<input <form
id="name" onSubmit={handleSubmit(onSubmit)}
type="text" className="focus-visible:outline-none"
{...register("name")}
onBlur={handleSubmit(onSubmit)}
className="block border-0 bg-transparent p-0 py-0 font-medium leading-[2.3rem] tracking-tight text-neutral-900 focus:ring-0 focus-visible:outline-none dark:text-dark-1000 sm:text-[1.2rem]"
/>
</form>
)}
<div className="flex items-center space-x-2">
<Filters />
<button
type="button"
className="mr-2 inline-flex items-center gap-x-1.5 rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:bg-dark-1000 dark:text-dark-50"
onClick={() => openNewListForm(boardId)}
>
<HiOutlinePlusSmall
className="-mr-0.5 h-5 w-5"
aria-hidden="true"
/>
New list
</button>
<BoardDropdown />
</div>
</div>
<div className="scrollbar-w-none z-0 flex-1 overflow-y-hidden overflow-x-scroll overscroll-contain scrollbar scrollbar-track-light-200 scrollbar-thumb-light-400 scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-h-[8px] dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-300">
{isLoading ? (
<div className="ml-[2rem] flex">
<div className="0 mr-5 h-[500px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
<div className="0 mr-5 h-[275px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
<div className="0 mr-5 h-[375px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
</div>
) : (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable
droppableId="all-lists"
direction="horizontal"
type="LIST"
> >
{(provided) => ( <input
<div id="name"
className="flex" type="text"
ref={provided.innerRef} {...register("name")}
{...provided.droppableProps} onBlur={handleSubmit(onSubmit)}
> className="block border-0 bg-transparent p-0 py-0 font-medium leading-[2.3rem] tracking-tight text-neutral-900 focus:ring-0 focus-visible:outline-none dark:text-dark-1000 sm:text-[1.2rem]"
<div className="min-w-[2rem]" /> />
{boardData?.lists?.map((list, index) => ( </form>
<List )}
index={index}
key={index} <div className="flex items-center space-x-2">
list={list} <Filters />
setSelectedPublicListId={(publicListId) => <button
setSelectedPublicListId(publicListId) type="button"
} className="mr-2 inline-flex items-center gap-x-1.5 rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:bg-dark-1000 dark:text-dark-50"
> onClick={() => openNewListForm(boardId)}
<Droppable droppableId={`${list.publicId}`} type="CARD"> >
{(provided) => ( <HiOutlinePlusSmall
<div className="-mr-0.5 h-5 w-5"
ref={provided.innerRef} aria-hidden="true"
{...provided.droppableProps} />
className="z-10 h-full max-h-[calc(100vh-265px)] min-h-[2rem] overflow-y-auto pr-1 scrollbar scrollbar-track-dark-100 scrollbar-thumb-dark-600 scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-w-[8px]" New list
> </button>
{list.cards?.map((card, index) => ( <BoardDropdown />
<Draggable </div>
key={card.publicId} </div>
draggableId={card.publicId}
index={index} <div className="scrollbar-w-none z-0 flex-1 overflow-y-hidden overflow-x-scroll overscroll-contain scrollbar scrollbar-track-light-200 scrollbar-thumb-light-400 scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-h-[8px] dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-300">
> {isLoading ? (
{(provided) => ( <div className="ml-[2rem] flex">
<Link <div className="0 mr-5 h-[500px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
onClick={(e) => { <div className="0 mr-5 h-[275px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
if ( <div className="0 mr-5 h-[375px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
</div>
) : (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable
droppableId="all-lists"
direction="horizontal"
type="LIST"
>
{(provided) => (
<div
className="flex"
ref={provided.innerRef}
{...provided.droppableProps}
>
<div className="min-w-[2rem]" />
{boardData?.lists?.map((list, index) => (
<List
index={index}
key={index}
list={list}
setSelectedPublicListId={(publicListId) =>
setSelectedPublicListId(publicListId)
}
>
<Droppable droppableId={`${list.publicId}`} type="CARD">
{(provided) => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
className="z-10 h-full max-h-[calc(100vh-265px)] min-h-[2rem] overflow-y-auto pr-1 scrollbar scrollbar-track-dark-100 scrollbar-thumb-dark-600 scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-w-[8px]"
>
{list.cards?.map((card, index) => (
<Draggable
key={card.publicId}
draggableId={card.publicId}
index={index}
>
{(provided) => (
<Link
onClick={(e) => {
if (
card.publicId.startsWith(
"PLACEHOLDER",
)
)
e.preventDefault();
}}
key={card.publicId}
href={`/cards/${card.publicId}`}
className={`mb-2 flex !cursor-pointer flex-col rounded-md border border-light-200 bg-light-50 px-3 py-2 text-sm text-neutral-900 dark:border-dark-200 dark:bg-dark-300 dark:text-dark-1000 dark:hover:bg-dark-400 ${
card.publicId.startsWith("PLACEHOLDER") card.publicId.startsWith("PLACEHOLDER")
) ? "pointer-events-none"
e.preventDefault(); : ""
}} }`}
key={card.publicId} ref={provided.innerRef}
href={`/cards/${card.publicId}`} {...provided.draggableProps}
className={`mb-2 flex !cursor-pointer flex-col rounded-md border border-light-200 bg-light-50 px-3 py-2 text-sm text-neutral-900 dark:border-dark-200 dark:bg-dark-300 dark:text-dark-1000 dark:hover:bg-dark-400 ${ {...provided.dragHandleProps}
card.publicId.startsWith("PLACEHOLDER") >
? "pointer-events-none" <div>{card.title}</div>
: "" {(card.labels?.length ?? 0) ||
}`} (card.members?.length ?? 0) ? (
ref={provided.innerRef} <div className="mt-2 flex justify-end space-x-1">
{...provided.draggableProps} {card.labels?.map((label) => (
{...provided.dragHandleProps}
>
<div>{card.title}</div>
{(card.labels?.length ?? 0) ||
(card.members?.length ?? 0) ? (
<div className="mt-2 flex justify-end space-x-1">
{card.labels?.map((label) => (
<span
key={label.publicId}
className="inline-flex w-fit items-center gap-x-1.5 rounded-full px-2 py-1 text-[10px] font-medium text-neutral-600 ring-1 ring-inset ring-light-600 dark:text-dark-1000 dark:ring-dark-800"
>
<svg
fill={
label.colourCode ?? undefined
}
className="h-2 w-2"
viewBox="0 0 6 6"
aria-hidden="true"
>
<circle cx={3} cy={3} r={3} />
</svg>
<div>{label.name}</div>
</span>
))}
<div className="isolate flex -space-x-1 overflow-hidden">
{card.members?.map((member) => (
<span <span
key={member.publicId} key={label.publicId}
className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-light-900 ring-2 ring-light-50 dark:bg-gray-500 dark:ring-dark-500" className="inline-flex w-fit items-center gap-x-1.5 rounded-full px-2 py-1 text-[10px] font-medium text-neutral-600 ring-1 ring-inset ring-light-600 dark:text-dark-1000 dark:ring-dark-800"
> >
<span className="text-[10px] font-medium leading-none text-white"> <svg
{member?.user?.name fill={
?.split(" ") label.colourCode ?? undefined
.map((namePart) => }
namePart className="h-2 w-2"
.charAt(0) viewBox="0 0 6 6"
.toUpperCase(), aria-hidden="true"
) >
.join("")} <circle cx={3} cy={3} r={3} />
</span> </svg>
<div>{label.name}</div>
</span> </span>
))} ))}
<div className="isolate flex -space-x-1 overflow-hidden">
{card.members?.map((member) => (
<span
key={member.publicId}
className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-light-900 ring-2 ring-light-50 dark:bg-gray-500 dark:ring-dark-500"
>
<span className="text-[10px] font-medium leading-none text-white">
{member?.user?.name
?.split(" ")
.map((namePart) =>
namePart
.charAt(0)
.toUpperCase(),
)
.join("")}
</span>
</span>
))}
</div>
</div> </div>
</div> ) : null}
) : null} </Link>
</Link> )}
)} </Draggable>
</Draggable> ))}
))} {provided.placeholder}
{provided.placeholder} </div>
</div> )}
)} </Droppable>
</Droppable> </List>
</List> ))}
))} <div className="min-w-[0.75rem]" />
<div className="min-w-[0.75rem]" /> {provided.placeholder}
{provided.placeholder} </div>
</div> )}
)} </Droppable>
</Droppable> </DragDropContext>
</DragDropContext> )}
)} </div>
<Modal modalSize={modalContentType === "NEW_CARD" ? "md" : "sm"}>
{modalContentType === "DELETE_BOARD" && <DeleteBoardConfirmation />}
{modalContentType === "DELETE_LIST" && (
<DeleteListConfirmation listPublicId={selectedPublicListId} />
)}
{modalContentType === "NEW_CARD" && (
<NewCardForm listPublicId={selectedPublicListId} />
)}
{modalContentType === "NEW_LIST" && (
<NewListForm boardPublicId={boardId} />
)}
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
</Modal>
</div> </div>
<Modal modalSize={modalContentType === "NEW_CARD" ? "md" : "sm"}> </>
{modalContentType === "DELETE_BOARD" && <DeleteBoardConfirmation />}
{modalContentType === "DELETE_LIST" && (
<DeleteListConfirmation listPublicId={selectedPublicListId} />
)}
{modalContentType === "NEW_CARD" && (
<NewCardForm listPublicId={selectedPublicListId} />
)}
{modalContentType === "NEW_LIST" && (
<NewListForm boardPublicId={boardId} />
)}
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
</Modal>
</div>
); );
} }

View File

@@ -2,57 +2,66 @@ import { HiArrowDownTray, HiOutlinePlusSmall } from "react-icons/hi2";
import { BoardsList } from "./components/BoardsList"; import { BoardsList } from "./components/BoardsList";
import { useModal } from "~/providers/modal"; import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/providers/workspace";
import Modal from "~/components/modal"; import Modal from "~/components/modal";
import { PageHead } from "~/components/PageHead";
import { ImportBoardsForm } from "./components/ImportBoardsForm"; import { ImportBoardsForm } from "./components/ImportBoardsForm";
import { NewBoardForm } from "./components/NewBoardForm"; import { NewBoardForm } from "./components/NewBoardForm";
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm"; import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
export default function BoardsPage() { export default function BoardsPage() {
const { openModal, modalContentType } = useModal(); const { openModal, modalContentType } = useModal();
const { workspace } = useWorkspace();
return ( return (
<div className="p-8"> <>
<div className="mb-8 flex w-full justify-between"> <PageHead title={`Boards | ${workspace?.name ?? "Workspace"}`} />
<h1 className="font-medium tracking-tight text-neutral-900 dark:text-dark-1000 sm:text-[1.2rem]"> <div className="p-8">
Boards <div className="mb-8 flex w-full justify-between">
</h1> <h1 className="font-medium tracking-tight text-neutral-900 dark:text-dark-1000 sm:text-[1.2rem]">
<div className="flex"> Boards
<button </h1>
type="button" <div className="flex">
className="bg-dark-3000 mr-2 flex items-center gap-x-1.5 rounded-md border-[1px] border-light-600 px-3 py-2 text-sm text-neutral-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:border-dark-600 dark:text-dark-1000" <button
onClick={() => openModal("IMPORT_BOARDS")} type="button"
> className="bg-dark-3000 mr-2 flex items-center gap-x-1.5 rounded-md border-[1px] border-light-600 px-3 py-2 text-sm text-neutral-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:border-dark-600 dark:text-dark-1000"
<div className="flex h-5 w-5 items-center"> onClick={() => openModal("IMPORT_BOARDS")}
<HiArrowDownTray className="-mr-0.5 h-4 w-4" aria-hidden="true" /> >
</div> <div className="flex h-5 w-5 items-center">
Import <HiArrowDownTray
</button> className="-mr-0.5 h-4 w-4"
<button aria-hidden="true"
type="button" />
className="flex items-center gap-x-1.5 rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:bg-dark-1000 dark:text-dark-50" </div>
onClick={() => openModal("NEW_BOARD")} Import
> </button>
<div className="h-5 w-5 items-center"> <button
<HiOutlinePlusSmall type="button"
className="-mr-0.5 h-5 w-5" className="flex items-center gap-x-1.5 rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:bg-dark-1000 dark:text-dark-50"
aria-hidden="true" onClick={() => openModal("NEW_BOARD")}
/> >
</div> <div className="h-5 w-5 items-center">
New <HiOutlinePlusSmall
</button> className="-mr-0.5 h-5 w-5"
aria-hidden="true"
/>
</div>
New
</button>
</div>
</div>
<Modal>
{modalContentType === "NEW_BOARD" && <NewBoardForm />}
{modalContentType === "IMPORT_BOARDS" && <ImportBoardsForm />}
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
</Modal>
<div className="flex flex-row">
<BoardsList />
</div> </div>
</div> </div>
</>
<Modal>
{modalContentType === "NEW_BOARD" && <NewBoardForm />}
{modalContentType === "IMPORT_BOARDS" && <ImportBoardsForm />}
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
</Modal>
<div className="flex flex-row">
<BoardsList />
</div>
</div>
); );
} }

View File

@@ -15,6 +15,7 @@ import { LabelForm } from "./components/LabelForm";
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm"; import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
import NewCommentForm from "./components/NewCommentForm"; import NewCommentForm from "./components/NewCommentForm";
import { PageHead } from "~/components/PageHead";
import Modal from "~/components/modal"; import Modal from "~/components/modal";
import { useModal } from "~/providers/modal"; import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup"; import { usePopup } from "~/providers/popup";
@@ -112,128 +113,133 @@ export default function CardPage() {
if (!cardId) return <></>; if (!cardId) return <></>;
return ( return (
<div className="flex h-full flex-1 flex-row"> <>
<div className="flex h-full w-full flex-col overflow-hidden"> <PageHead
<div className="h-full max-h-[calc(100vh-4rem)] overflow-y-auto p-8"> title={`${data?.title ?? "Card"} | ${board?.name ?? "Board"}`}
<div className="mb-8 flex w-full items-center justify-between"> />
{isLoading ? ( <div className="flex h-full flex-1 flex-row">
<div className="flex space-x-2"> <div className="flex h-full w-full flex-col overflow-hidden">
<div className="h-[2.3rem] w-[150px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" /> <div className="h-full max-h-[calc(100vh-4rem)] overflow-y-auto p-8">
<div className="h-[2.3rem] w-[300px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" /> <div className="mb-8 flex w-full items-center justify-between">
</div> {isLoading ? (
) : ( <div className="flex space-x-2">
<> <div className="h-[2.3rem] w-[150px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
<Link <div className="h-[2.3rem] w-[300px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
className="whitespace-nowrap font-medium leading-[2.3rem] tracking-tight text-light-900 dark:text-dark-900 sm:text-[1.2rem]"
href={`/boards/${board?.publicId}`}
>
{board?.name}
</Link>
<IoChevronForwardSharp
size={18}
className="mx-2 text-light-900 dark:text-dark-900"
/>
<form
onSubmit={handleSubmit(onSubmit)}
className="w-full space-y-6"
>
<div>
<input
type="text"
id="title"
{...register("title")}
onBlur={handleSubmit(onSubmit)}
className="block w-full border-0 bg-transparent p-0 py-0 font-medium tracking-tight text-neutral-900 focus:ring-0 dark:text-dark-1000 sm:text-[1.2rem]"
/>
</div>
</form>
<div className="flex">
<Dropdown />
</div> </div>
</> ) : (
)} <>
</div> <Link
<div className="mb-10 flex w-full max-w-2xl justify-between"> className="whitespace-nowrap font-medium leading-[2.3rem] tracking-tight text-light-900 dark:text-dark-900 sm:text-[1.2rem]"
<form href={`/boards/${board?.publicId}`}
onSubmit={handleSubmit(onSubmit)} >
className="w-full space-y-6" {board?.name}
> </Link>
<div className="mt-2"> <IoChevronForwardSharp
<ContentEditable size={18}
placeholder="Add description..." className="mx-2 text-light-900 dark:text-dark-900"
html={watch("description")} />
disabled={false} <form
onChange={(e) => setValue("description", e.target.value)} onSubmit={handleSubmit(onSubmit)}
onBlur={handleSubmit(onSubmit)} className="w-full space-y-6"
className="block w-full border-0 bg-transparent py-1.5 text-light-900 focus-visible:outline-none dark:text-dark-1000 sm:text-sm sm:leading-6" >
<div>
<input
type="text"
id="title"
{...register("title")}
onBlur={handleSubmit(onSubmit)}
className="block w-full border-0 bg-transparent p-0 py-0 font-medium tracking-tight text-neutral-900 focus:ring-0 dark:text-dark-1000 sm:text-[1.2rem]"
/>
</div>
</form>
<div className="flex">
<Dropdown />
</div>
</>
)}
</div>
<div className="mb-10 flex w-full max-w-2xl justify-between">
<form
onSubmit={handleSubmit(onSubmit)}
className="w-full space-y-6"
>
<div className="mt-2">
<ContentEditable
placeholder="Add description..."
html={watch("description")}
disabled={false}
onChange={(e) => setValue("description", e.target.value)}
onBlur={handleSubmit(onSubmit)}
className="block w-full border-0 bg-transparent py-1.5 text-light-900 focus-visible:outline-none dark:text-dark-1000 sm:text-sm sm:leading-6"
/>
</div>
</form>
</div>
<div className="border-t-[1px] border-light-600 pt-12 dark:border-dark-600">
<h2 className="text-md pb-4 font-medium text-light-900 dark:text-dark-1000">
Activity
</h2>
<div>
<ActivityList
activities={activities ?? []}
isLoading={isLoading}
/> />
</div> </div>
</form> <div className="mt-6">
</div> <NewCommentForm cardPublicId={cardId} />
<div className="border-t-[1px] border-light-600 pt-12 dark:border-dark-600"> </div>
<h2 className="text-md pb-4 font-medium text-light-900 dark:text-dark-1000">
Activity
</h2>
<div>
<ActivityList
activities={activities ?? []}
isLoading={isLoading}
/>
</div>
<div className="mt-6">
<NewCommentForm cardPublicId={cardId} />
</div> </div>
</div> </div>
</div> </div>
</div> <div className="min-w-[325px] border-l-[1px] border-light-600 bg-light-200 p-8 text-light-900 dark:border-dark-400 dark:bg-dark-100 dark:text-dark-900">
<div className="min-w-[325px] border-l-[1px] border-light-600 bg-light-200 p-8 text-light-900 dark:border-dark-400 dark:bg-dark-100 dark:text-dark-900"> <div className="mb-4 flex w-full">
<div className="mb-4 flex w-full"> <p className="my-2 w-[100px] text-sm">List</p>
<p className="my-2 w-[100px] text-sm">List</p> <ListSelector
<ListSelector cardPublicId={cardId}
cardPublicId={cardId} lists={formattedLists}
lists={formattedLists} isLoading={isLoading}
isLoading={isLoading} />
/> </div>
<div className="mb-4 flex w-full">
<p className="my-2 w-[100px] text-sm">Labels</p>
<LabelSelector
cardPublicId={cardId}
labels={formattedLabels}
isLoading={isLoading}
/>
</div>
<div className="flex w-full">
<p className="my-2 w-[100px] text-sm">Members</p>
<MemberSelector
cardPublicId={cardId}
members={formattedMembers}
isLoading={isLoading}
/>
</div>
</div> </div>
<div className="mb-4 flex w-full">
<p className="my-2 w-[100px] text-sm">Labels</p>
<LabelSelector
cardPublicId={cardId}
labels={formattedLabels}
isLoading={isLoading}
/>
</div>
<div className="flex w-full">
<p className="my-2 w-[100px] text-sm">Members</p>
<MemberSelector
cardPublicId={cardId}
members={formattedMembers}
isLoading={isLoading}
/>
</div>
</div>
<Modal> <Modal>
{modalContentType === "NEW_LABEL" && ( {modalContentType === "NEW_LABEL" && (
<LabelForm cardPublicId={cardId} /> <LabelForm cardPublicId={cardId} />
)} )}
{modalContentType === "EDIT_LABEL" && ( {modalContentType === "EDIT_LABEL" && (
<LabelForm cardPublicId={cardId} isEdit /> <LabelForm cardPublicId={cardId} isEdit />
)} )}
{modalContentType === "DELETE_LABEL" && ( {modalContentType === "DELETE_LABEL" && (
<DeleteLabelConfirmation <DeleteLabelConfirmation
cardPublicId={cardId} cardPublicId={cardId}
labelPublicId={entityId} labelPublicId={entityId}
/> />
)} )}
{modalContentType === "DELETE_CARD" && ( {modalContentType === "DELETE_CARD" && (
<DeleteCardConfirmation <DeleteCardConfirmation
boardPublicId={boardId ?? ""} boardPublicId={boardId ?? ""}
cardPublicId={cardId} cardPublicId={cardId}
/> />
)} )}
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />} {modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
</Modal> </Modal>
</div> </div>
</>
); );
} }

View File

@@ -3,6 +3,8 @@ import { twMerge } from "tailwind-merge";
import { useModal } from "~/providers/modal"; import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/providers/workspace"; import { useWorkspace } from "~/providers/workspace";
import { PageHead } from "~/components/PageHead";
import Modal from "~/components/modal"; import Modal from "~/components/modal";
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm"; import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
@@ -136,82 +138,85 @@ export default function MembersPage() {
}; };
return ( return (
<div className="px-28 py-12"> <>
<div className="mb-8 flex w-full justify-between"> <PageHead title={`Members | ${workspace?.name ?? "Workspace"}`} />
<h1 className="font-medium tracking-tight text-neutral-900 dark:text-dark-1000 sm:text-[1.2rem]"> <div className="px-28 py-12">
Members <div className="mb-8 flex w-full justify-between">
</h1> <h1 className="font-medium tracking-tight text-neutral-900 dark:text-dark-1000 sm:text-[1.2rem]">
<div className="flex"> Members
<button </h1>
type="button" <div className="flex">
className="flex items-center gap-x-1.5 rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:bg-dark-1000 dark:text-dark-50" <button
onClick={() => openModal("INVITE_MEMBER")} type="button"
> className="flex items-center gap-x-1.5 rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:bg-dark-1000 dark:text-dark-50"
<div className="h-5 w-5 items-center"> onClick={() => openModal("INVITE_MEMBER")}
<HiOutlinePlusSmall >
className="-mr-0.5 h-5 w-5" <div className="h-5 w-5 items-center">
aria-hidden="true" <HiOutlinePlusSmall
/> className="-mr-0.5 h-5 w-5"
</div> aria-hidden="true"
Invite />
</button> </div>
Invite
</button>
</div>
</div> </div>
</div>
<div className="mt-8 flow-root"> <div className="mt-8 flow-root">
<div className="-mx-4 -my-2 overflow-x-visible sm:-mx-6 lg:-mx-8"> <div className="-mx-4 -my-2 overflow-x-visible sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
<div className="h-full shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg"> <div className="h-full shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
<table className="min-w-full divide-y divide-light-600 dark:divide-dark-600"> <table className="min-w-full divide-y divide-light-600 dark:divide-dark-600">
<thead className="rounded-t-lg bg-light-300 dark:bg-dark-200"> <thead className="rounded-t-lg bg-light-300 dark:bg-dark-200">
<tr> <tr>
<th <th
scope="col" scope="col"
className="w-[65%] rounded-tl-lg py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-light-900 dark:text-dark-900 sm:pl-6" className="w-[65%] rounded-tl-lg py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-light-900 dark:text-dark-900 sm:pl-6"
> >
User User
</th> </th>
<th <th
scope="col" scope="col"
className="w-[35%] rounded-tr-lg px-3 py-3.5 text-left text-sm font-semibold text-light-900 dark:text-dark-900" className="w-[35%] rounded-tr-lg px-3 py-3.5 text-left text-sm font-semibold text-light-900 dark:text-dark-900"
> >
Role Role
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-light-600 bg-light-50 dark:divide-dark-600 dark:bg-dark-100"> <tbody className="divide-y divide-light-600 bg-light-50 dark:divide-dark-600 dark:bg-dark-100">
{!isLoading && {!isLoading &&
data?.members.map((member, index) => ( data?.members.map((member, index) => (
<TableRow <TableRow
key={member.publicId} key={member.publicId}
memberPublicId={member.publicId} memberPublicId={member.publicId}
memberName={member?.user?.name} memberName={member?.user?.name}
memberEmail={member?.user?.email} memberEmail={member?.user?.email}
memberRole={member.role} memberRole={member.role}
memberStatus={member.status} memberStatus={member.status}
isLastRow={index === data.members.length - 1} isLastRow={index === data.members.length - 1}
/> />
))} ))}
{isLoading && ( {isLoading && (
<> <>
<TableRow showSkeleton /> <TableRow showSkeleton />
<TableRow showSkeleton /> <TableRow showSkeleton />
<TableRow showSkeleton isLastRow /> <TableRow showSkeleton isLastRow />
</> </>
)} )}
</tbody> </tbody>
</table> </table>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<Modal> <Modal>
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />} {modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
{modalContentType === "INVITE_MEMBER" && <InviteMemberForm />} {modalContentType === "INVITE_MEMBER" && <InviteMemberForm />}
{modalContentType === "REMOVE_MEMBER" && <DeleteMemberConfirmation />} {modalContentType === "REMOVE_MEMBER" && <DeleteMemberConfirmation />}
</Modal> </Modal>
</div> </div>
</>
); );
} }

View File

@@ -1,6 +1,7 @@
import Modal from "~/components/modal"; import Modal from "~/components/modal";
import Button from "~/components/Button"; import Button from "~/components/Button";
import { PageHead } from "~/components/PageHead";
import { DeleteWorkspaceConfirmation } from "./components/DeleteWorkspaceConfirmation"; import { DeleteWorkspaceConfirmation } from "./components/DeleteWorkspaceConfirmation";
import UpdateWorkspaceNameForm from "./components/UpdateWorkspaceNameForm"; import UpdateWorkspaceNameForm from "./components/UpdateWorkspaceNameForm";
@@ -12,41 +13,47 @@ export default function SettingsPage() {
const { workspace } = useWorkspace(); const { workspace } = useWorkspace();
return ( return (
<div className="px-28 py-12"> <>
<div className="mb-8 flex w-full justify-between"> <PageHead title={`Settings | ${workspace?.name ?? "Workspace"}`} />
<h1 className="font-medium tracking-tight text-neutral-900 dark:text-dark-1000 sm:text-[1.2rem]"> <div className="px-28 py-12">
Settings <div className="mb-8 flex w-full justify-between">
</h1> <h1 className="font-medium tracking-tight text-neutral-900 dark:text-dark-1000 sm:text-[1.2rem]">
</div> Settings
</h1>
</div>
<div className="mb-8 border-t border-light-300 dark:border-dark-300"> <div className="mb-8 border-t border-light-300 dark:border-dark-300">
<h2 className="mb-4 mt-8 text-[14px] text-neutral-900 dark:text-dark-1000"> <h2 className="mb-4 mt-8 text-[14px] text-neutral-900 dark:text-dark-1000">
Workspace name Workspace name
</h2> </h2>
<UpdateWorkspaceNameForm <UpdateWorkspaceNameForm
workspacePublicId={workspace?.publicId} workspacePublicId={workspace?.publicId}
workspaceName={workspace?.name} workspaceName={workspace?.name}
/> />
</div> </div>
<div className="border-t border-light-300 dark:border-dark-300"> <div className="border-t border-light-300 dark:border-dark-300">
<h2 className="mt-8 text-[14px] text-neutral-900 dark:text-dark-1000"> <h2 className="mt-8 text-[14px] text-neutral-900 dark:text-dark-1000">
Delete workspace Delete workspace
</h2> </h2>
<p className="mb-8 mt-2 text-sm text-neutral-500 dark:text-dark-900"> <p className="mb-8 mt-2 text-sm text-neutral-500 dark:text-dark-900">
Once you delete your workspace, there is no going back. Please be Once you delete your workspace, there is no going back. Please be
certain. certain.
</p> </p>
<Button variant="primary" onClick={() => openModal("DELETE_WORKSPACE")}> <Button
Delete workspace variant="primary"
</Button> onClick={() => openModal("DELETE_WORKSPACE")}
</div> >
Delete workspace
</Button>
</div>
<Modal> <Modal>
{modalContentType === "DELETE_WORKSPACE" && ( {modalContentType === "DELETE_WORKSPACE" && (
<DeleteWorkspaceConfirmation /> <DeleteWorkspaceConfirmation />
)} )}
</Modal> </Modal>
</div> </div>
</>
); );
} }