fix: layout height and overflow (#1004)
* fix: kanban height issue * dev: Layout fixes * dev: layout changes * fix: layout overflow settings and fixed header * style: filters padding fixed * fix: hide filters if none are applied --------- Co-authored-by: gurusainath <gurusainath007@gmail.com>
This commit is contained in:
parent
443878994a
commit
a1de3f581f
41 changed files with 1080 additions and 1035 deletions
|
|
@ -8,6 +8,7 @@ import useSWR from "swr";
|
|||
import workspaceService from "services/workspace.service";
|
||||
// layouts
|
||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||
import { SettingsHeader } from "components/workspace";
|
||||
// ui
|
||||
import { SecondaryButton } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
|
|
@ -38,33 +39,36 @@ const BillingSettings: NextPage = () => {
|
|||
</Breadcrumbs>
|
||||
}
|
||||
>
|
||||
<section className="space-y-8">
|
||||
<div>
|
||||
<h3 className="text-3xl font-bold leading-6">Billing & Plans</h3>
|
||||
<p className="mt-4 text-sm text-brand-secondary">[Free launch preview] plan Pro</p>
|
||||
</div>
|
||||
<div className="space-y-8 md:w-2/3">
|
||||
<div className="px-24 py-8">
|
||||
<SettingsHeader />
|
||||
<section className="space-y-8">
|
||||
<div>
|
||||
<div className="w-80 rounded-md border border-brand-base bg-brand-base p-4 text-center">
|
||||
<h4 className="text-md mb-1 leading-6">Payment due</h4>
|
||||
<h2 className="text-3xl font-extrabold">--</h2>
|
||||
<h3 className="text-3xl font-bold leading-6">Billing & Plans</h3>
|
||||
<p className="mt-4 text-sm text-brand-secondary">[Free launch preview] plan Pro</p>
|
||||
</div>
|
||||
<div className="space-y-8 md:w-2/3">
|
||||
<div>
|
||||
<div className="w-80 rounded-md border border-brand-base bg-brand-base p-4 text-center">
|
||||
<h4 className="text-md mb-1 leading-6">Payment due</h4>
|
||||
<h2 className="text-3xl font-extrabold">--</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-md mb-1 leading-6">Current plan</h4>
|
||||
<p className="mb-3 text-sm text-brand-secondary">
|
||||
You are currently using the free plan
|
||||
</p>
|
||||
<a href="https://plane.so/pricing" target="_blank" rel="noreferrer">
|
||||
<SecondaryButton outline>View Plans and Upgrade</SecondaryButton>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-md mb-1 leading-6">Billing history</h4>
|
||||
<p className="mb-3 text-sm text-brand-secondary">There are no invoices to display</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-md mb-1 leading-6">Current plan</h4>
|
||||
<p className="mb-3 text-sm text-brand-secondary">
|
||||
You are currently using the free plan
|
||||
</p>
|
||||
<a href="https://plane.so/pricing" target="_blank" rel="noreferrer">
|
||||
<SecondaryButton outline>View Plans and Upgrade</SecondaryButton>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-md mb-1 leading-6">Billing history</h4>
|
||||
<p className="mb-3 text-sm text-brand-secondary">There are no invoices to display</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</WorkspaceAuthorizationLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useRouter } from "next/router";
|
|||
|
||||
// layouts
|
||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||
import { SettingsHeader } from "components/workspace";
|
||||
// components
|
||||
import IntegrationGuide from "components/integration/guide";
|
||||
// ui
|
||||
|
|
@ -22,7 +23,10 @@ const ImportExport: NextPage = () => {
|
|||
</Breadcrumbs>
|
||||
}
|
||||
>
|
||||
<IntegrationGuide />
|
||||
<div className="px-24 py-8">
|
||||
<SettingsHeader />
|
||||
<IntegrationGuide />
|
||||
</div>
|
||||
</WorkspaceAuthorizationLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ import fileService from "services/file.service";
|
|||
import useToast from "hooks/use-toast";
|
||||
// layouts
|
||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||
import SettingsNavbar from "layouts/settings-navbar";
|
||||
// components
|
||||
import { ImageUploadModal } from "components/core";
|
||||
import { DeleteWorkspaceModal } from "components/workspace";
|
||||
import { DeleteWorkspaceModal, SettingsHeader } from "components/workspace";
|
||||
// ui
|
||||
import { Spinner, Input, CustomSelect, SecondaryButton, DangerButton } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
|
|
@ -172,163 +173,167 @@ const WorkspaceSettings: NextPage = () => {
|
|||
}}
|
||||
data={activeWorkspace ?? null}
|
||||
/>
|
||||
{activeWorkspace ? (
|
||||
<div className="space-y-8 sm:space-y-12">
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Logo</h4>
|
||||
<p className="text-sm text-brand-secondary">
|
||||
Max file size is 5MB. Supported file types are .jpg and .png.
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<button type="button" onClick={() => setIsImageUploadModalOpen(true)}>
|
||||
{watch("logo") && watch("logo") !== null && watch("logo") !== "" ? (
|
||||
<div className="relative mx-auto flex h-12 w-12">
|
||||
<Image
|
||||
src={watch("logo")!}
|
||||
alt="Workspace Logo"
|
||||
objectFit="cover"
|
||||
layout="fill"
|
||||
className="rounded-md"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative flex h-12 w-12 items-center justify-center rounded bg-gray-700 p-4 uppercase text-white">
|
||||
{activeWorkspace?.name?.charAt(0) ?? "N"}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<div className="flex gap-4">
|
||||
<SecondaryButton
|
||||
onClick={() => {
|
||||
setIsImageUploadModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{isImageUploading ? "Uploading..." : "Upload"}
|
||||
</SecondaryButton>
|
||||
{activeWorkspace.logo && activeWorkspace.logo !== "" && (
|
||||
<DangerButton onClick={() => handleDelete(activeWorkspace.logo)}>
|
||||
{isImageRemoving ? "Removing..." : "Remove"}
|
||||
</DangerButton>
|
||||
)}
|
||||
<div className="px-24 py-8">
|
||||
<SettingsHeader />
|
||||
{activeWorkspace ? (
|
||||
<div className="space-y-8 sm:space-y-12">
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Logo</h4>
|
||||
<p className="text-sm text-brand-secondary">
|
||||
Max file size is 5MB. Supported file types are .jpg and .png.
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<button type="button" onClick={() => setIsImageUploadModalOpen(true)}>
|
||||
{watch("logo") && watch("logo") !== null && watch("logo") !== "" ? (
|
||||
<div className="relative mx-auto flex h-12 w-12">
|
||||
<Image
|
||||
src={watch("logo")!}
|
||||
alt="Workspace Logo"
|
||||
objectFit="cover"
|
||||
layout="fill"
|
||||
className="rounded-md"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative flex h-12 w-12 items-center justify-center rounded bg-gray-700 p-4 uppercase text-white">
|
||||
{activeWorkspace?.name?.charAt(0) ?? "N"}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<div className="flex gap-4">
|
||||
<SecondaryButton
|
||||
onClick={() => {
|
||||
setIsImageUploadModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{isImageUploading ? "Uploading..." : "Upload"}
|
||||
</SecondaryButton>
|
||||
{activeWorkspace.logo && activeWorkspace.logo !== "" && (
|
||||
<DangerButton onClick={() => handleDelete(activeWorkspace.logo)}>
|
||||
{isImageRemoving ? "Removing..." : "Remove"}
|
||||
</DangerButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">URL</h4>
|
||||
<p className="text-sm text-brand-secondary">Your workspace URL.</p>
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">URL</h4>
|
||||
<p className="text-sm text-brand-secondary">Your workspace URL.</p>
|
||||
</div>
|
||||
<div className="col-span-12 flex items-center gap-2 sm:col-span-6">
|
||||
<Input
|
||||
id="url"
|
||||
name="url"
|
||||
autoComplete="off"
|
||||
register={register}
|
||||
error={errors.name}
|
||||
className="w-full"
|
||||
value={`${
|
||||
typeof window !== "undefined" &&
|
||||
window.location.origin.replace("http://", "").replace("https://", "")
|
||||
}/${activeWorkspace.slug}`}
|
||||
disabled
|
||||
/>
|
||||
<SecondaryButton
|
||||
className="h-min"
|
||||
onClick={() =>
|
||||
copyTextToClipboard(
|
||||
`${typeof window !== "undefined" && window.location.origin}/${
|
||||
activeWorkspace.slug
|
||||
}`
|
||||
).then(() => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Link Copied!",
|
||||
message: "Workspace link copied to clipboard.",
|
||||
});
|
||||
})
|
||||
}
|
||||
outline
|
||||
>
|
||||
<LinkIcon className="h-[18px] w-[18px]" />
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-12 flex items-center gap-2 sm:col-span-6">
|
||||
<Input
|
||||
id="url"
|
||||
name="url"
|
||||
autoComplete="off"
|
||||
register={register}
|
||||
error={errors.name}
|
||||
className="w-full"
|
||||
value={`${
|
||||
typeof window !== "undefined" &&
|
||||
window.location.origin.replace("http://", "").replace("https://", "")
|
||||
}/${activeWorkspace.slug}`}
|
||||
disabled
|
||||
/>
|
||||
<SecondaryButton
|
||||
className="h-min"
|
||||
onClick={() =>
|
||||
copyTextToClipboard(
|
||||
`${typeof window !== "undefined" && window.location.origin}/${
|
||||
activeWorkspace.slug
|
||||
}`
|
||||
).then(() => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Link Copied!",
|
||||
message: "Workspace link copied to clipboard.",
|
||||
});
|
||||
})
|
||||
}
|
||||
outline
|
||||
>
|
||||
<LinkIcon className="h-[18px] w-[18px]" />
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Name</h4>
|
||||
<p className="text-sm text-brand-secondary">Give a name to your workspace.</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="Name"
|
||||
autoComplete="off"
|
||||
register={register}
|
||||
error={errors.name}
|
||||
validations={{
|
||||
required: "Name is required",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Company Size</h4>
|
||||
<p className="text-sm text-brand-secondary">How big is your company?</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<Controller
|
||||
name="company_size"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CustomSelect
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
label={value ? value.toString() : "Select company size"}
|
||||
input
|
||||
>
|
||||
{COMPANY_SIZE?.map((item) => (
|
||||
<CustomSelect.Option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:text-right">
|
||||
<SecondaryButton onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
|
||||
{isSubmitting ? "Updating..." : "Update Workspace"}
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Name</h4>
|
||||
<p className="text-sm text-brand-secondary">Give a name to your workspace.</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="Name"
|
||||
autoComplete="off"
|
||||
register={register}
|
||||
error={errors.name}
|
||||
validations={{
|
||||
required: "Name is required",
|
||||
}}
|
||||
/>
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Danger Zone</h4>
|
||||
<p className="text-sm text-brand-secondary">
|
||||
The danger zone of the workspace delete page is a critical area that requires
|
||||
careful consideration and attention. When deleting a workspace, all of the data
|
||||
and resources within that workspace will be permanently removed and cannot be
|
||||
recovered.
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<DangerButton onClick={() => setIsOpen(true)} outline>
|
||||
Delete the workspace
|
||||
</DangerButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Company Size</h4>
|
||||
<p className="text-sm text-brand-secondary">How big is your company?</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<Controller
|
||||
name="company_size"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CustomSelect
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
label={value ? value.toString() : "Select company size"}
|
||||
input
|
||||
>
|
||||
{COMPANY_SIZE?.map((item) => (
|
||||
<CustomSelect.Option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
|
||||
<Spinner />
|
||||
</div>
|
||||
<div className="sm:text-right">
|
||||
<SecondaryButton onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
|
||||
{isSubmitting ? "Updating..." : "Update Workspace"}
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold">Danger Zone</h4>
|
||||
<p className="text-sm text-brand-secondary">
|
||||
The danger zone of the workspace delete page is a critical area that requires
|
||||
careful consideration and attention. When deleting a workspace, all of the data and
|
||||
resources within that workspace will be permanently removed and cannot be recovered.
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<DangerButton onClick={() => setIsOpen(true)} outline>
|
||||
Delete the workspace
|
||||
</DangerButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</WorkspaceAuthorizationLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import workspaceService from "services/workspace.service";
|
|||
import IntegrationService from "services/integration";
|
||||
// layouts
|
||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||
import { SettingsHeader } from "components/workspace";
|
||||
// components
|
||||
import { SingleIntegrationCard } from "components/integration";
|
||||
// ui
|
||||
|
|
@ -46,31 +47,34 @@ const WorkspaceIntegrations: NextPage = () => {
|
|||
</Breadcrumbs>
|
||||
}
|
||||
>
|
||||
<section className="space-y-8">
|
||||
<div className="flex flex-col items-start gap-3">
|
||||
<h3 className="text-2xl font-semibold">Integrations</h3>
|
||||
<div className="flex items-center gap-3 rounded-[10px] border border-brand-accent/75 bg-brand-accent/5 p-4 text-sm text-brand-base">
|
||||
<ExclamationIcon height={24} width={24} className="fill-current text-brand-base" />
|
||||
<p className="leading-5">
|
||||
Integrations and importers are only available on the cloud version. We plan to
|
||||
open-source our SDKs in the near future so that the community can request or
|
||||
contribute integrations as needed.
|
||||
</p>
|
||||
<div className="px-24 py-8">
|
||||
<SettingsHeader />
|
||||
<section className="space-y-8">
|
||||
<div className="flex flex-col items-start gap-3">
|
||||
<h3 className="text-2xl font-semibold">Integrations</h3>
|
||||
<div className="flex items-center gap-3 rounded-[10px] border border-brand-accent/75 bg-brand-accent/5 p-4 text-sm text-brand-base">
|
||||
<ExclamationIcon height={24} width={24} className="fill-current text-brand-base" />
|
||||
<p className="leading-5">
|
||||
Integrations and importers are only available on the cloud version. We plan to
|
||||
open-source our SDKs in the near future so that the community can request or
|
||||
contribute integrations as needed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-5">
|
||||
{appIntegrations ? (
|
||||
appIntegrations.map((integration) => (
|
||||
<SingleIntegrationCard key={integration.id} integration={integration} />
|
||||
))
|
||||
) : (
|
||||
<Loader className="space-y-5">
|
||||
<Loader.Item height="60px" />
|
||||
<Loader.Item height="60px" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
<div className="space-y-5">
|
||||
{appIntegrations ? (
|
||||
appIntegrations.map((integration) => (
|
||||
<SingleIntegrationCard key={integration.id} integration={integration} />
|
||||
))
|
||||
) : (
|
||||
<Loader className="space-y-5">
|
||||
<Loader.Item height="60px" />
|
||||
<Loader.Item height="60px" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</WorkspaceAuthorizationLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import useToast from "hooks/use-toast";
|
|||
import workspaceService from "services/workspace.service";
|
||||
// layouts
|
||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||
import { SettingsHeader } from "components/workspace";
|
||||
// components
|
||||
import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove";
|
||||
import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal";
|
||||
|
|
@ -137,117 +138,120 @@ const MembersSettings: NextPage = () => {
|
|||
</Breadcrumbs>
|
||||
}
|
||||
>
|
||||
<section className="space-y-8">
|
||||
<div className="flex items-end justify-between gap-4">
|
||||
<h3 className="text-2xl font-semibold">Members</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 text-brand-accent outline-none"
|
||||
onClick={() => setInviteModal(true)}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Member
|
||||
</button>
|
||||
</div>
|
||||
{!workspaceMembers || !workspaceInvitations ? (
|
||||
<Loader className="space-y-5">
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="divide-y divide-brand-base rounded-[10px] border border-brand-base bg-brand-base px-6">
|
||||
{members.length > 0
|
||||
? members.map((member) => (
|
||||
<div key={member.id} className="flex items-center justify-between py-6">
|
||||
<div className="flex items-center gap-x-8 gap-y-2">
|
||||
<div className="relative flex h-10 w-10 items-center justify-center rounded-lg bg-gray-700 p-4 capitalize text-white">
|
||||
{member.avatar && member.avatar !== "" ? (
|
||||
<Image
|
||||
src={member.avatar}
|
||||
alt={member.first_name}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
) : member.first_name !== "" ? (
|
||||
member.first_name.charAt(0)
|
||||
) : (
|
||||
member.email.charAt(0)
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm">
|
||||
{member.first_name} {member.last_name}
|
||||
</h4>
|
||||
<p className="text-xs text-brand-secondary">{member.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
{!member?.status && (
|
||||
<div className="mr-2 flex items-center justify-center rounded-full bg-yellow-500/20 px-2 py-1 text-center text-xs text-yellow-500">
|
||||
<p>Pending</p>
|
||||
</div>
|
||||
)}
|
||||
<CustomSelect
|
||||
label={ROLE[member.role as keyof typeof ROLE]}
|
||||
value={member.role}
|
||||
onChange={(value: any) => {
|
||||
workspaceService
|
||||
.updateWorkspaceMember(activeWorkspace?.slug as string, member.id, {
|
||||
role: value,
|
||||
})
|
||||
.then(() => {
|
||||
mutateMembers(
|
||||
(prevData) =>
|
||||
prevData?.map((m) =>
|
||||
m.id === member.id ? { ...m, role: value } : m
|
||||
),
|
||||
false
|
||||
);
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Member role updated successfully.",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
title: "Error",
|
||||
type: "error",
|
||||
message: "An error occurred while updating member role.",
|
||||
});
|
||||
});
|
||||
}}
|
||||
position="right"
|
||||
>
|
||||
{Object.keys(ROLE).map((key) => (
|
||||
<CustomSelect.Option key={key} value={key}>
|
||||
<>{ROLE[parseInt(key) as keyof typeof ROLE]}</>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
<CustomMenu ellipsis>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
if (member.member) {
|
||||
setSelectedRemoveMember(member.id);
|
||||
} else {
|
||||
setSelectedInviteRemoveMember(member.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove member
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
<div className="px-24 py-8">
|
||||
<SettingsHeader />
|
||||
<section className="space-y-8">
|
||||
<div className="flex items-end justify-between gap-4">
|
||||
<h3 className="text-2xl font-semibold">Members</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 text-brand-accent outline-none"
|
||||
onClick={() => setInviteModal(true)}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Member
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
{!workspaceMembers || !workspaceInvitations ? (
|
||||
<Loader className="space-y-5">
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="divide-y divide-brand-base rounded-[10px] border border-brand-base bg-brand-base px-6">
|
||||
{members.length > 0
|
||||
? members.map((member) => (
|
||||
<div key={member.id} className="flex items-center justify-between py-6">
|
||||
<div className="flex items-center gap-x-8 gap-y-2">
|
||||
<div className="relative flex h-10 w-10 items-center justify-center rounded-lg bg-gray-700 p-4 capitalize text-white">
|
||||
{member.avatar && member.avatar !== "" ? (
|
||||
<Image
|
||||
src={member.avatar}
|
||||
alt={member.first_name}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
) : member.first_name !== "" ? (
|
||||
member.first_name.charAt(0)
|
||||
) : (
|
||||
member.email.charAt(0)
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm">
|
||||
{member.first_name} {member.last_name}
|
||||
</h4>
|
||||
<p className="text-xs text-brand-secondary">{member.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
{!member?.status && (
|
||||
<div className="mr-2 flex items-center justify-center rounded-full bg-yellow-500/20 px-2 py-1 text-center text-xs text-yellow-500">
|
||||
<p>Pending</p>
|
||||
</div>
|
||||
)}
|
||||
<CustomSelect
|
||||
label={ROLE[member.role as keyof typeof ROLE]}
|
||||
value={member.role}
|
||||
onChange={(value: any) => {
|
||||
workspaceService
|
||||
.updateWorkspaceMember(activeWorkspace?.slug as string, member.id, {
|
||||
role: value,
|
||||
})
|
||||
.then(() => {
|
||||
mutateMembers(
|
||||
(prevData) =>
|
||||
prevData?.map((m) =>
|
||||
m.id === member.id ? { ...m, role: value } : m
|
||||
),
|
||||
false
|
||||
);
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Member role updated successfully.",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
title: "Error",
|
||||
type: "error",
|
||||
message: "An error occurred while updating member role.",
|
||||
});
|
||||
});
|
||||
}}
|
||||
position="right"
|
||||
>
|
||||
{Object.keys(ROLE).map((key) => (
|
||||
<CustomSelect.Option key={key} value={key}>
|
||||
<>{ROLE[parseInt(key) as keyof typeof ROLE]}</>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
<CustomMenu ellipsis>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
if (member.member) {
|
||||
setSelectedRemoveMember(member.id);
|
||||
} else {
|
||||
setSelectedInviteRemoveMember(member.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove member
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</WorkspaceAuthorizationLayout>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue