feat: extend skeleton loaders
This commit is contained in:
@@ -15,6 +15,7 @@ import WorkspaceMenu from "~/components/WorkspaceMenu";
|
|||||||
|
|
||||||
interface SideNavigationProps {
|
interface SideNavigationProps {
|
||||||
user: UserType;
|
user: UserType;
|
||||||
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserType {
|
interface UserType {
|
||||||
@@ -22,7 +23,10 @@ interface UserType {
|
|||||||
image?: string | null | undefined;
|
image?: string | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SideNavigation({ user }: SideNavigationProps) {
|
export default function SideNavigation({
|
||||||
|
user,
|
||||||
|
isLoading,
|
||||||
|
}: SideNavigationProps) {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
@@ -65,6 +69,7 @@ export default function SideNavigation({ user }: SideNavigationProps) {
|
|||||||
<UserMenu
|
<UserMenu
|
||||||
email={user?.email ?? ""}
|
email={user?.email ?? ""}
|
||||||
imageUrl={user?.image ?? undefined}
|
imageUrl={user?.image ?? undefined}
|
||||||
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
</nav>
|
</nav>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -7,13 +7,18 @@ import createClient from "~/utils/supabase/client";
|
|||||||
interface UserMenuProps {
|
interface UserMenuProps {
|
||||||
imageUrl: string | undefined;
|
imageUrl: string | undefined;
|
||||||
email: string;
|
email: string;
|
||||||
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function classNames(...classes: string[]): string {
|
function classNames(...classes: string[]): string {
|
||||||
return classes.filter(Boolean).join(" ");
|
return classes.filter(Boolean).join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UserMenu({ imageUrl, email }: UserMenuProps) {
|
export default function UserMenu({
|
||||||
|
imageUrl,
|
||||||
|
email,
|
||||||
|
isLoading,
|
||||||
|
}: UserMenuProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { theme, switchTheme } = useTheme();
|
const { theme, switchTheme } = useTheme();
|
||||||
|
|
||||||
@@ -28,28 +33,35 @@ export default function UserMenu({ imageUrl, email }: UserMenuProps) {
|
|||||||
return (
|
return (
|
||||||
<Menu as="div" className="relative inline-block w-full text-left">
|
<Menu as="div" className="relative inline-block w-full text-left">
|
||||||
<div>
|
<div>
|
||||||
<Menu.Button className="flex w-full items-center rounded-md p-1.5 text-neutral-900 hover:bg-light-200 dark:text-dark-900 dark:hover:bg-dark-200 dark:hover:text-dark-1000">
|
{isLoading ? (
|
||||||
{imageUrl ? (
|
<div className="flex">
|
||||||
<Image
|
<div className="h-[30px] w-[30px] animate-pulse rounded-full bg-light-200 dark:bg-dark-200" />
|
||||||
src={imageUrl ?? ""}
|
<div className="mx-2 h-[30px] w-[175px] animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
|
||||||
className="h-8 w-8 rounded-full bg-gray-50"
|
</div>
|
||||||
width={30}
|
) : (
|
||||||
height={30}
|
<Menu.Button className="flex w-full items-center rounded-md p-1.5 text-neutral-900 hover:bg-light-200 dark:text-dark-900 dark:hover:bg-dark-200 dark:hover:text-dark-1000">
|
||||||
alt=""
|
{imageUrl ? (
|
||||||
/>
|
<Image
|
||||||
) : (
|
src={imageUrl ?? ""}
|
||||||
<span className="inline-block h-6 w-6 overflow-hidden rounded-full bg-light-400 dark:bg-dark-400">
|
className="h-8 w-8 rounded-full bg-gray-50"
|
||||||
<svg
|
width={30}
|
||||||
className="h-full w-full text-dark-700"
|
height={30}
|
||||||
fill="currentColor"
|
alt=""
|
||||||
viewBox="0 0 24 24"
|
/>
|
||||||
>
|
) : (
|
||||||
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
|
<span className="inline-block h-6 w-6 overflow-hidden rounded-full bg-light-400 dark:bg-dark-400">
|
||||||
</svg>
|
<svg
|
||||||
</span>
|
className="h-full w-full text-dark-700"
|
||||||
)}
|
fill="currentColor"
|
||||||
<span className="mx-2 truncate text-sm">{email}</span>
|
viewBox="0 0 24 24"
|
||||||
</Menu.Button>
|
>
|
||||||
|
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="mx-2 truncate text-sm">{email}</span>
|
||||||
|
</Menu.Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
|
|||||||
@@ -7,22 +7,30 @@ import { useWorkspace } from "~/providers/workspace";
|
|||||||
import { HiCheck } from "react-icons/hi2";
|
import { HiCheck } from "react-icons/hi2";
|
||||||
|
|
||||||
export default function WorkspaceMenu() {
|
export default function WorkspaceMenu() {
|
||||||
const { workspace, availableWorkspaces, switchWorkspace } = useWorkspace();
|
const { workspace, isLoading, availableWorkspaces, switchWorkspace } =
|
||||||
|
useWorkspace();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu as="div" className="relative inline-block w-full pb-3 text-left">
|
<Menu as="div" className="relative inline-block w-full pb-3 text-left">
|
||||||
<div>
|
<div>
|
||||||
<Menu.Button className="mb-1 flex w-full items-center rounded-[5px] p-1.5 hover:bg-light-200 dark:hover:bg-dark-200">
|
{isLoading ? (
|
||||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded-[5px] bg-indigo-700">
|
<div className="mb-1 flex p-1.5">
|
||||||
<span className="text-xs font-bold leading-none text-white">
|
<div className="h-6 w-6 animate-pulse rounded-[5px] bg-light-200 dark:bg-dark-200" />
|
||||||
{workspace?.name.charAt(0).toUpperCase()}
|
<div className="ml-2 h-6 w-[150px] animate-pulse rounded-[5px] bg-light-200 dark:bg-dark-200" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Menu.Button className="mb-1 flex w-full items-center rounded-[5px] p-1.5 hover:bg-light-200 dark:hover:bg-dark-200">
|
||||||
|
<span className="inline-flex h-6 w-6 items-center justify-center rounded-[5px] bg-indigo-700">
|
||||||
|
<span className="text-xs font-bold leading-none text-white">
|
||||||
|
{workspace?.name.charAt(0).toUpperCase()}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
<span className="ml-2 text-sm font-bold text-neutral-900 dark:text-dark-1000">
|
||||||
<span className="ml-2 text-sm font-bold text-neutral-900 dark:text-dark-1000">
|
{workspace?.name}
|
||||||
{workspace?.name}
|
</span>
|
||||||
</span>
|
</Menu.Button>
|
||||||
</Menu.Button>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import SideNavigation from "./SideNavigation";
|
|||||||
import FeedbackButton from "./FeedbackButton";
|
import FeedbackButton from "./FeedbackButton";
|
||||||
|
|
||||||
export default function Dashboard(props: { children: React.ReactNode }) {
|
export default function Dashboard(props: { children: React.ReactNode }) {
|
||||||
const { data } = api.auth.getUser.useQuery();
|
const { data, isLoading } = api.auth.getUser.useQuery();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col items-center bg-light-100 dark:bg-dark-50">
|
<div className="flex h-screen flex-col items-center bg-light-100 dark:bg-dark-50">
|
||||||
@@ -17,7 +17,7 @@ export default function Dashboard(props: { children: React.ReactNode }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex h-full w-full">
|
<div className="flex h-full w-full">
|
||||||
<SideNavigation user={{ email: data?.email }} />
|
<SideNavigation user={{ email: data?.email }} isLoading={isLoading} />
|
||||||
<div className="w-full overflow-hidden">{props.children}</div>
|
<div className="w-full overflow-hidden">{props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useRouter } from "next/navigation";
|
|||||||
|
|
||||||
interface WorkspaceContextProps {
|
interface WorkspaceContextProps {
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
|
isLoading: boolean;
|
||||||
switchWorkspace: (_workspace: Workspace) => void;
|
switchWorkspace: (_workspace: Workspace) => void;
|
||||||
availableWorkspaces: Workspace[];
|
availableWorkspaces: Workspace[];
|
||||||
}
|
}
|
||||||
@@ -41,7 +42,7 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
initialAvailableWorkspaces,
|
initialAvailableWorkspaces,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data } = api.workspace.all.useQuery();
|
const { data, isLoading } = api.workspace.all.useQuery();
|
||||||
|
|
||||||
const switchWorkspace = (_workspace: Workspace) => {
|
const switchWorkspace = (_workspace: Workspace) => {
|
||||||
localStorage.setItem("workspacePublicId", _workspace.publicId);
|
localStorage.setItem("workspacePublicId", _workspace.publicId);
|
||||||
@@ -97,7 +98,7 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceContext.Provider
|
<WorkspaceContext.Provider
|
||||||
value={{ workspace, availableWorkspaces, switchWorkspace }}
|
value={{ workspace, isLoading, availableWorkspaces, switchWorkspace }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</WorkspaceContext.Provider>
|
</WorkspaceContext.Provider>
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ interface LabelSelectorProps {
|
|||||||
selected: boolean;
|
selected: boolean;
|
||||||
colourCode: string;
|
colourCode: string;
|
||||||
}[];
|
}[];
|
||||||
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LabelSelector({
|
export default function LabelSelector({
|
||||||
cardPublicId,
|
cardPublicId,
|
||||||
labels,
|
labels,
|
||||||
|
isLoading,
|
||||||
}: LabelSelectorProps) {
|
}: LabelSelectorProps) {
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
@@ -47,104 +49,110 @@ export default function LabelSelector({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Menu
|
{isLoading ? (
|
||||||
as="div"
|
<div className="flex w-full">
|
||||||
className="relative flex w-full flex-wrap items-center text-left"
|
<div className="h-full w-[175px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
|
||||||
>
|
</div>
|
||||||
{selectedLabels.length ? (
|
) : (
|
||||||
<>
|
<Menu
|
||||||
{selectedLabels.map((label) => (
|
as="div"
|
||||||
<Menu.Button
|
className="relative flex w-full flex-wrap items-center text-left"
|
||||||
key={label.publicId}
|
>
|
||||||
className="my-1 mr-2 inline-flex w-fit items-center gap-x-1.5 rounded-full px-2 py-1 text-[12px] font-medium text-light-800 ring-1 ring-inset ring-light-600 dark:text-dark-1000 dark:ring-dark-800"
|
{selectedLabels.length ? (
|
||||||
>
|
<>
|
||||||
<svg
|
{selectedLabels.map((label) => (
|
||||||
fill={label.colourCode}
|
<Menu.Button
|
||||||
className="h-2 w-2"
|
key={label.publicId}
|
||||||
viewBox="0 0 6 6"
|
className="my-1 mr-2 inline-flex w-fit items-center gap-x-1.5 rounded-full px-2 py-1 text-[12px] font-medium text-light-800 ring-1 ring-inset ring-light-600 dark:text-dark-1000 dark:ring-dark-800"
|
||||||
aria-hidden="true"
|
|
||||||
>
|
>
|
||||||
<circle cx={3} cy={3} r={3} />
|
<svg
|
||||||
</svg>
|
fill={label.colourCode}
|
||||||
<div>{label.name}</div>
|
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>
|
||||||
|
</Menu.Button>
|
||||||
|
))}
|
||||||
|
<Menu.Button className="my-1 inline-flex w-fit items-center gap-x-1.5 rounded-full py-1 pl-2 pr-4 text-[12px] font-medium text-dark-800 ring-inset ring-dark-800 hover:bg-light-400 dark:hover:bg-dark-200">
|
||||||
|
<HiMiniPlus size={16} />
|
||||||
|
Add label
|
||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
))}
|
</>
|
||||||
<Menu.Button className="my-1 inline-flex w-fit items-center gap-x-1.5 rounded-full py-1 pl-2 pr-4 text-[12px] font-medium text-dark-800 ring-inset ring-dark-800 hover:bg-light-400 dark:hover:bg-dark-200">
|
) : (
|
||||||
<HiMiniPlus size={16} />
|
<Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
|
||||||
|
<HiMiniPlus size={22} className="pr-2" />
|
||||||
Add label
|
Add label
|
||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
</>
|
)}
|
||||||
) : (
|
|
||||||
<Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
|
|
||||||
<HiMiniPlus size={22} className="pr-2" />
|
|
||||||
Add label
|
|
||||||
</Menu.Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="transition ease-out duration-100"
|
enter="transition ease-out duration-100"
|
||||||
enterFrom="transform opacity-0 scale-95"
|
enterFrom="transform opacity-0 scale-95"
|
||||||
enterTo="transform opacity-100 scale-100"
|
enterTo="transform opacity-100 scale-100"
|
||||||
leave="transition ease-in duration-75"
|
leave="transition ease-in duration-75"
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
|
<Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<form onSubmit={formik.handleSubmit}>
|
<form onSubmit={formik.handleSubmit}>
|
||||||
{labels?.map((label) => (
|
{labels?.map((label) => (
|
||||||
<Menu.Item key={label.publicId}>
|
<Menu.Item key={label.publicId}>
|
||||||
{() => (
|
{() => (
|
||||||
<div
|
<div
|
||||||
key={label.publicId}
|
key={label.publicId}
|
||||||
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
|
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
|
||||||
onClick={async (e) => {
|
onClick={async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await formik.setFieldValue(
|
await formik.setFieldValue(
|
||||||
label.publicId,
|
label.publicId,
|
||||||
!formik.values[label.publicId],
|
!formik.values[label.publicId],
|
||||||
);
|
);
|
||||||
|
|
||||||
addOrRemoveLabel.mutate({
|
addOrRemoveLabel.mutate({
|
||||||
cardPublicId,
|
cardPublicId,
|
||||||
labelPublicId: label.publicId,
|
labelPublicId: label.publicId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await formik.submitForm();
|
await formik.submitForm();
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<input
|
|
||||||
id={label.publicId}
|
|
||||||
name={label.publicId}
|
|
||||||
type="checkbox"
|
|
||||||
className="h-[14px] w-[14px] rounded bg-transparent"
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
checked={formik.values[label.publicId]}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor={label.publicId}
|
|
||||||
className="ml-3 text-sm"
|
|
||||||
>
|
>
|
||||||
{label.name}
|
<input
|
||||||
</label>
|
id={label.publicId}
|
||||||
</div>
|
name={label.publicId}
|
||||||
)}
|
type="checkbox"
|
||||||
</Menu.Item>
|
className="h-[14px] w-[14px] rounded bg-transparent"
|
||||||
))}
|
onClick={(event) => event.stopPropagation()}
|
||||||
<button
|
onChange={formik.handleChange}
|
||||||
onClick={() => openModal("NEW_LABEL")}
|
checked={formik.values[label.publicId]}
|
||||||
className="flex w-full items-center rounded-[5px] p-1.5 px-2 text-sm hover:bg-light-200 dark:hover:bg-dark-300"
|
/>
|
||||||
>
|
<label
|
||||||
<HiMiniPlus size={22} className="pr-2" />
|
htmlFor={label.publicId}
|
||||||
Create new label
|
className="ml-3 text-sm"
|
||||||
</button>
|
>
|
||||||
</form>
|
{label.name}
|
||||||
</div>
|
</label>
|
||||||
</Menu.Items>
|
</div>
|
||||||
</Transition>
|
)}
|
||||||
</Menu>
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
onClick={() => openModal("NEW_LABEL")}
|
||||||
|
className="flex w-full items-center rounded-[5px] p-1.5 px-2 text-sm hover:bg-light-200 dark:hover:bg-dark-300"
|
||||||
|
>
|
||||||
|
<HiMiniPlus size={22} className="pr-2" />
|
||||||
|
Create new label
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ interface ListSelectorProps {
|
|||||||
name: string;
|
name: string;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
}[];
|
}[];
|
||||||
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ListSelector({
|
export default function ListSelector({
|
||||||
cardPublicId,
|
cardPublicId,
|
||||||
lists,
|
lists,
|
||||||
|
isLoading,
|
||||||
}: ListSelectorProps) {
|
}: ListSelectorProps) {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
@@ -42,66 +44,72 @@ export default function ListSelector({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Menu
|
{isLoading ? (
|
||||||
as="div"
|
<div className="flex w-full">
|
||||||
className="relative flex w-full flex-wrap items-center text-left"
|
<div className="h-full w-[150px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
|
||||||
>
|
</div>
|
||||||
<Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:border-light-300 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
|
) : (
|
||||||
{selectedList?.name}
|
<Menu
|
||||||
</Menu.Button>
|
as="div"
|
||||||
|
className="relative flex w-full flex-wrap items-center text-left"
|
||||||
<Transition
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition ease-out duration-100"
|
|
||||||
enterFrom="transform opacity-0 scale-95"
|
|
||||||
enterTo="transform opacity-100 scale-100"
|
|
||||||
leave="transition ease-in duration-75"
|
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
|
||||||
leaveTo="transform opacity-0 scale-95"
|
|
||||||
>
|
>
|
||||||
<Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
|
<Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:border-light-300 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
|
||||||
<div className="p-2">
|
{selectedList?.name}
|
||||||
<form onSubmit={formik.handleSubmit}>
|
</Menu.Button>
|
||||||
{lists?.map((list) => (
|
|
||||||
<Menu.Item key={list.publicId}>
|
|
||||||
<div
|
|
||||||
key={list.publicId}
|
|
||||||
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
|
|
||||||
onClick={async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
await formik.setFieldValue(
|
|
||||||
list.publicId,
|
|
||||||
!formik.values[list.publicId],
|
|
||||||
);
|
|
||||||
|
|
||||||
updateCardList.mutate({
|
<Transition
|
||||||
cardId: cardPublicId,
|
as={Fragment}
|
||||||
newListId: list.publicId,
|
enter="transition ease-out duration-100"
|
||||||
});
|
enterFrom="transform opacity-0 scale-95"
|
||||||
|
enterTo="transform opacity-100 scale-100"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
|
leaveTo="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
|
||||||
|
<div className="p-2">
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
{lists?.map((list) => (
|
||||||
|
<Menu.Item key={list.publicId}>
|
||||||
|
<div
|
||||||
|
key={list.publicId}
|
||||||
|
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await formik.setFieldValue(
|
||||||
|
list.publicId,
|
||||||
|
!formik.values[list.publicId],
|
||||||
|
);
|
||||||
|
|
||||||
await formik.submitForm();
|
updateCardList.mutate({
|
||||||
}}
|
cardId: cardPublicId,
|
||||||
>
|
newListId: list.publicId,
|
||||||
<input
|
});
|
||||||
id={list.publicId}
|
|
||||||
name={list.publicId}
|
await formik.submitForm();
|
||||||
type="checkbox"
|
}}
|
||||||
className="h-[14px] w-[14px] rounded bg-transparent"
|
>
|
||||||
onClick={(event) => event.stopPropagation()}
|
<input
|
||||||
onChange={formik.handleChange}
|
id={list.publicId}
|
||||||
checked={formik.values[list.publicId]}
|
name={list.publicId}
|
||||||
/>
|
type="checkbox"
|
||||||
<label htmlFor={list.publicId} className="ml-3 text-sm">
|
className="h-[14px] w-[14px] rounded bg-transparent"
|
||||||
{list.name}
|
onClick={(event) => event.stopPropagation()}
|
||||||
</label>
|
onChange={formik.handleChange}
|
||||||
</div>
|
checked={formik.values[list.publicId]}
|
||||||
</Menu.Item>
|
/>
|
||||||
))}
|
<label htmlFor={list.publicId} className="ml-3 text-sm">
|
||||||
</form>
|
{list.name}
|
||||||
</div>
|
</label>
|
||||||
</Menu.Items>
|
</div>
|
||||||
</Transition>
|
</Menu.Item>
|
||||||
</Menu>
|
))}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ interface MemberSelectorProps {
|
|||||||
};
|
};
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
}[];
|
}[];
|
||||||
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LabelSelector({
|
export default function LabelSelector({
|
||||||
cardPublicId,
|
cardPublicId,
|
||||||
members,
|
members,
|
||||||
|
isLoading,
|
||||||
}: MemberSelectorProps) {
|
}: MemberSelectorProps) {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
@@ -46,89 +48,98 @@ export default function LabelSelector({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Menu
|
{isLoading ? (
|
||||||
as="div"
|
<div className="flex w-full">
|
||||||
className="relative flex w-full flex-wrap items-center text-left"
|
<div className="h-full w-[125px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
|
||||||
>
|
</div>
|
||||||
<Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
|
) : (
|
||||||
{selectedMembers.length ? (
|
<Menu
|
||||||
<div className="isolate flex -space-x-1 overflow-hidden">
|
as="div"
|
||||||
{selectedMembers.map((member) => (
|
className="relative flex w-full flex-wrap items-center text-left"
|
||||||
<span
|
|
||||||
key={member.publicId}
|
|
||||||
className="relative z-30 inline-flex h-6 w-6 items-center justify-center rounded-full bg-gray-500 ring-1 ring-light-200 dark:ring-dark-100"
|
|
||||||
>
|
|
||||||
<span className="text-[10px] font-medium leading-none text-white">
|
|
||||||
{member.user?.name
|
|
||||||
? member.user?.name
|
|
||||||
.split(" ")
|
|
||||||
.map((namePart) => namePart.charAt(0).toUpperCase())
|
|
||||||
.join("")
|
|
||||||
: null}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<HiMiniPlus size={22} className="pr-2" />
|
|
||||||
{"Add member"}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Menu.Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition ease-out duration-100"
|
|
||||||
enterFrom="transform opacity-0 scale-95"
|
|
||||||
enterTo="transform opacity-100 scale-100"
|
|
||||||
leave="transition ease-in duration-75"
|
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
|
||||||
leaveTo="transform opacity-0 scale-95"
|
|
||||||
>
|
>
|
||||||
<Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
|
<Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
|
||||||
<div className="p-2">
|
{selectedMembers.length ? (
|
||||||
<form onSubmit={formik.handleSubmit}>
|
<div className="isolate flex -space-x-1 overflow-hidden">
|
||||||
{members?.map((member) => (
|
{selectedMembers.map((member) => (
|
||||||
<Menu.Item key={member.publicId}>
|
<span
|
||||||
<div
|
key={member.publicId}
|
||||||
key={member.publicId}
|
className="relative z-30 inline-flex h-6 w-6 items-center justify-center rounded-full bg-gray-500 ring-1 ring-light-200 dark:ring-dark-100"
|
||||||
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
|
>
|
||||||
onClick={async (e) => {
|
<span className="text-[10px] font-medium leading-none text-white">
|
||||||
e.preventDefault();
|
{member.user?.name
|
||||||
await formik.setFieldValue(
|
? member.user?.name
|
||||||
member.publicId,
|
.split(" ")
|
||||||
!formik.values[member.publicId],
|
.map((namePart) => namePart.charAt(0).toUpperCase())
|
||||||
);
|
.join("")
|
||||||
|
: null}
|
||||||
addOrRemoveMember.mutate({
|
</span>
|
||||||
cardPublicId,
|
</span>
|
||||||
workspaceMemberPublicId: member.publicId,
|
|
||||||
});
|
|
||||||
|
|
||||||
await formik.submitForm();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
id={member.publicId}
|
|
||||||
name={member.publicId}
|
|
||||||
type="checkbox"
|
|
||||||
className="h-[14px] w-[14px] rounded bg-transparent"
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
checked={formik.values[member.publicId]}
|
|
||||||
/>
|
|
||||||
<label htmlFor={member.publicId} className="ml-3 text-sm">
|
|
||||||
{member?.user?.name}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</Menu.Item>
|
|
||||||
))}
|
))}
|
||||||
</form>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
</Menu.Items>
|
<>
|
||||||
</Transition>
|
<HiMiniPlus size={22} className="pr-2" />
|
||||||
</Menu>
|
{"Add member"}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Menu.Button>
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-100"
|
||||||
|
enterFrom="transform opacity-0 scale-95"
|
||||||
|
enterTo="transform opacity-100 scale-100"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
|
leaveTo="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
|
||||||
|
<div className="p-2">
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
{members?.map((member) => (
|
||||||
|
<Menu.Item key={member.publicId}>
|
||||||
|
<div
|
||||||
|
key={member.publicId}
|
||||||
|
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await formik.setFieldValue(
|
||||||
|
member.publicId,
|
||||||
|
!formik.values[member.publicId],
|
||||||
|
);
|
||||||
|
|
||||||
|
addOrRemoveMember.mutate({
|
||||||
|
cardPublicId,
|
||||||
|
workspaceMemberPublicId: member.publicId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await formik.submitForm();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id={member.publicId}
|
||||||
|
name={member.publicId}
|
||||||
|
type="checkbox"
|
||||||
|
className="h-[14px] w-[14px] rounded bg-transparent"
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
checked={formik.values[member.publicId]}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={member.publicId}
|
||||||
|
className="ml-3 text-sm"
|
||||||
|
>
|
||||||
|
{member?.user?.name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,15 +152,27 @@ export default function CardPage() {
|
|||||||
<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 cardPublicId={cardId} lists={formattedLists} />
|
<ListSelector
|
||||||
|
cardPublicId={cardId}
|
||||||
|
lists={formattedLists}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4 flex w-full">
|
<div className="mb-4 flex w-full">
|
||||||
<p className="my-2 w-[100px] text-sm">Labels</p>
|
<p className="my-2 w-[100px] text-sm">Labels</p>
|
||||||
<LabelSelector cardPublicId={cardId} labels={formattedLabels} />
|
<LabelSelector
|
||||||
|
cardPublicId={cardId}
|
||||||
|
labels={formattedLabels}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full">
|
<div className="flex w-full">
|
||||||
<p className="my-2 w-[100px] text-sm">Members</p>
|
<p className="my-2 w-[100px] text-sm">Members</p>
|
||||||
<MemberSelector cardPublicId={cardId} members={formattedMembers} />
|
<MemberSelector
|
||||||
|
cardPublicId={cardId}
|
||||||
|
members={formattedMembers}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user