UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: редизайн окон настроек workspace и profile
This commit is contained in:
parent
290b00d251
commit
85bd24c45b
|
|
@ -113,7 +113,7 @@ const WorkspaceMembersSettingsPage = observer(function WorkspaceMembersSettingsP
|
|||
"opacity-60": !canPerformWorkspaceMemberActions,
|
||||
})}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4 pb-3.5">
|
||||
<div className="flex items-center justify-between gap-4 pb-4">
|
||||
<h4 className="flex items-center gap-2.5 text-h3-medium">
|
||||
{t("workspace_settings.settings.members.title")}
|
||||
{workspaceMemberIds && workspaceMemberIds.length > 0 && (
|
||||
|
|
@ -121,7 +121,7 @@ const WorkspaceMembersSettingsPage = observer(function WorkspaceMembersSettingsP
|
|||
)}
|
||||
</h4>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1.5 rounded-md border border-subtle bg-surface-1 px-2.5 py-1.5">
|
||||
<div className="nodedc-settings-field flex min-h-[2.75rem] items-center gap-1.5 px-3.5">
|
||||
<SearchIcon className="h-3.5 w-3.5 text-placeholder" />
|
||||
<input
|
||||
className="w-full max-w-[234px] border-none bg-transparent text-body-xs-regular outline-none placeholder:text-placeholder"
|
||||
|
|
@ -139,7 +139,12 @@ const WorkspaceMembersSettingsPage = observer(function WorkspaceMembersSettingsP
|
|||
/>
|
||||
<MembersActivityButton workspaceSlug={workspaceSlug} />
|
||||
{canPerformWorkspaceAdminActions && (
|
||||
<Button variant="primary" size="lg" onClick={() => setInviteModal(true)}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
className="nodedc-settings-primary-button min-w-[11rem]"
|
||||
onClick={() => setInviteModal(true)}
|
||||
>
|
||||
{t("workspace_settings.settings.members.add_member")}
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
|||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
// components
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { SettingsHeading } from "@/components/settings/heading";
|
||||
import { WebhookSettingsLoader } from "@/components/ui/loader/settings/web-hook";
|
||||
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
|
||||
import { WebhooksList, CreateWebhookModal } from "@/components/web-hooks";
|
||||
import { WebhooksEmptyState } from "@/components/web-hooks/empty-state";
|
||||
// hooks
|
||||
import { useWebhook } from "@/hooks/store/use-webhook";
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
|
|
@ -78,7 +78,12 @@ function WebhooksListPage({ params }: Route.ComponentProps) {
|
|||
title={t("workspace_settings.settings.webhooks.title")}
|
||||
description={t("workspace_settings.settings.webhooks.description")}
|
||||
control={
|
||||
<Button variant="primary" size="lg" onClick={() => setShowCreateWebhookModal(true)}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
className="nodedc-settings-primary-button min-w-[11rem]"
|
||||
onClick={() => setShowCreateWebhookModal(true)}
|
||||
>
|
||||
{t("workspace_settings.settings.webhooks.add_webhook")}
|
||||
</Button>
|
||||
}
|
||||
|
|
@ -90,21 +95,9 @@ function WebhooksListPage({ params }: Route.ComponentProps) {
|
|||
) : (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<EmptyStateCompact
|
||||
assetKey="webhook"
|
||||
title={t("settings_empty_state.webhooks.title")}
|
||||
description={t("settings_empty_state.webhooks.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("settings_empty_state.webhooks.cta_primary"),
|
||||
onClick: () => {
|
||||
setShowCreateWebhookModal(true);
|
||||
},
|
||||
},
|
||||
]}
|
||||
align="start"
|
||||
rootClassName="py-20"
|
||||
/>
|
||||
<div className="py-20">
|
||||
<WebhooksEmptyState onClick={() => setShowCreateWebhookModal(true)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ export const DeleteWorkspaceSection = observer(function DeleteWorkspaceSection(p
|
|||
description={t("workspace_settings.settings.general.delete_workspace_description")}
|
||||
control={
|
||||
<Button
|
||||
variant="error-outline"
|
||||
variant="ghost"
|
||||
className="nodedc-modal-danger-button min-w-[10rem]"
|
||||
onClick={() => setDeleteWorkspaceModal(true)}
|
||||
data-ph-element={WORKSPACE_TRACKER_ELEMENTS.DELETE_WORKSPACE_BUTTON}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -30,31 +30,31 @@ export function ApiTokenListItem(props: Props) {
|
|||
return (
|
||||
<>
|
||||
<DeleteApiTokenModal isOpen={deleteModalOpen} onClose={() => setDeleteModalOpen(false)} tokenId={token.id} />
|
||||
<div className="group relative flex flex-col justify-center border-b border-subtle py-3">
|
||||
<div className="nodedc-settings-card group relative flex flex-col justify-center px-5 py-4">
|
||||
<Tooltip tooltipContent="Delete token" isMobile={isMobile}>
|
||||
<button
|
||||
onClick={() => setDeleteModalOpen(true)}
|
||||
className="absolute right-4 hidden place-items-center group-hover:grid"
|
||||
className="nodedc-settings-chip absolute top-4 right-4 hidden min-h-[2.25rem] place-items-center !px-2.5 group-hover:grid"
|
||||
data-ph-element={PROFILE_SETTINGS_TRACKER_ELEMENTS.LIST_ITEM_DELETE_ICON}
|
||||
>
|
||||
<XCircle className="h-4 w-4 text-danger-primary" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<div className="flex w-4/5 items-center">
|
||||
<h5 className="truncate text-13 font-medium">{token.label}</h5>
|
||||
<h5 className="truncate text-13 font-medium text-primary">{token.label}</h5>
|
||||
<span
|
||||
className={`${
|
||||
token.is_active ? "bg-success-subtle text-success-primary" : "bg-layer-1 text-placeholder"
|
||||
} ml-2 flex h-4 max-h-fit items-center rounded-xs px-2 text-11 font-medium`}
|
||||
token.is_active ? "bg-success-subtle text-success-primary" : "bg-white/6 text-placeholder"
|
||||
} ml-2 flex min-h-[1.35rem] max-h-fit items-center rounded-full px-2.5 text-[11px] font-medium`}
|
||||
>
|
||||
{token.is_active ? "Active" : "Expired"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 flex w-full flex-col justify-center">
|
||||
{token.description.trim() !== "" && (
|
||||
<p className="mb-1 max-w-[70%] text-13 break-words">{token.description}</p>
|
||||
<p className="mb-1 max-w-[70%] text-13 break-words text-secondary">{token.description}</p>
|
||||
)}
|
||||
<p className="mb-1 text-11 leading-6 text-placeholder">
|
||||
<p className="mb-1 text-11 leading-6 text-tertiary">
|
||||
{token.is_active
|
||||
? token.expired_at
|
||||
? `Expires ${renderFormattedDate(token.expired_at)} at ${renderFormattedTime(token.expired_at)}`
|
||||
|
|
|
|||
|
|
@ -21,9 +21,10 @@ import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
|||
// import { Tooltip } from "@plane/propel/tooltip";
|
||||
// import { EIssuesStoreType } from "@plane/types";
|
||||
import type { TWorkItemFilterExpression } from "@plane/types";
|
||||
import { CustomSelect, SearchSelectionDropdown } from "@plane/ui";
|
||||
import { SearchSelectionDropdown } from "@plane/ui";
|
||||
// import { WorkspaceLevelWorkItemFiltersHOC } from "@/components/work-item-filters/filters-hoc/workspace-level";
|
||||
// import { WorkItemFiltersRow } from "@/components/work-item-filters/filters-row";
|
||||
import { SelectionDropdown } from "@/components/common/selection-dropdown";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useUser, useUserPermissions } from "@/hooks/store/user";
|
||||
import { ProjectExportService } from "@/services/project/project-export.service";
|
||||
|
|
@ -171,6 +172,8 @@ export const ExportForm = observer(function ExportForm(props: Props) {
|
|||
.join(", ")
|
||||
: "All projects"
|
||||
}
|
||||
className="!rounded-[1.25rem]"
|
||||
buttonClassName="nodedc-settings-select !border-0 !px-4 !py-3 text-13 font-medium"
|
||||
optionsClassName="max-w-48 sm:max-w-[532px]"
|
||||
placement="bottom-end"
|
||||
multiple
|
||||
|
|
@ -189,26 +192,30 @@ export const ExportForm = observer(function ExportForm(props: Props) {
|
|||
name="provider"
|
||||
disabled={!isMember && (!hasProjects || !canPerformAnyCreateAction)}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CustomSelect
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
label={t(value.i18n_title)}
|
||||
optionsClassName="max-w-48 sm:max-w-[532px]"
|
||||
<SelectionDropdown
|
||||
menuButton={t(value.i18n_title)}
|
||||
menuButtonWrapperClassName="nodedc-settings-select px-4 py-3 text-13 font-medium"
|
||||
dropdownContentClassName="max-w-48 sm:max-w-[532px]"
|
||||
placement="bottom-end"
|
||||
buttonClassName="py-2 text-13"
|
||||
>
|
||||
{EXPORTERS_LIST.map((service) => (
|
||||
<CustomSelect.Option key={service.provider} className="flex items-center gap-2" value={service}>
|
||||
<span className="truncate">{t(service.i18n_title)}</span>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
options={EXPORTERS_LIST.map((service) => ({
|
||||
key: service.provider,
|
||||
title: <span className="truncate">{t(service.i18n_title)}</span>,
|
||||
isChecked: value.provider === service.provider,
|
||||
onClick: () => onChange(service),
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<div className="px-4 py-3">
|
||||
<Button variant="primary" size="lg" type="submit" loading={exportLoading}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
type="submit"
|
||||
loading={exportLoading}
|
||||
className="nodedc-settings-primary-button min-w-[9.75rem]"
|
||||
>
|
||||
{exportLoading ? `${t("workspace_settings.settings.exports.exporting")}...` : t("export")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export const PrevExports = observer(function PrevExports(props: Props) {
|
|||
<div className="flex items-center justify-between border-b border-subtle pb-3.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-h6-medium text-primary">{t("workspace_settings.settings.exports.previous_exports")}</h3>
|
||||
<Button variant="tertiary" className="shrink-0" onClick={handleRefresh}>
|
||||
<Button variant="ghost" size="lg" className="nodedc-settings-chip shrink-0" onClick={handleRefresh}>
|
||||
<RefreshCw className={`h-3 w-3 ${refreshing ? "animate-spin" : ""}`} />
|
||||
{refreshing ? t("refreshing") : t("refresh_status")}
|
||||
</Button>
|
||||
|
|
@ -76,8 +76,9 @@ export const PrevExports = observer(function PrevExports(props: Props) {
|
|||
{!!exporterServices?.results?.length && (
|
||||
<div className="flex items-center gap-2 text-11">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
className="nodedc-settings-chip"
|
||||
disabled={!exporterServices?.prev_page_results}
|
||||
onClick={() => exporterServices?.prev_page_results && setCursor(exporterServices?.prev_cursor)}
|
||||
prependIcon={<MoveLeft />}
|
||||
|
|
@ -85,8 +86,9 @@ export const PrevExports = observer(function PrevExports(props: Props) {
|
|||
{t("prev")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
className="nodedc-settings-chip"
|
||||
disabled={!exporterServices?.next_page_results}
|
||||
onClick={() => exporterServices?.next_page_results && setCursor(exporterServices?.next_cursor)}
|
||||
appendIcon={<MoveRight />}
|
||||
|
|
@ -100,7 +102,7 @@ export const PrevExports = observer(function PrevExports(props: Props) {
|
|||
{exporterServices && exporterServices?.results ? (
|
||||
exporterServices?.results?.length > 0 ? (
|
||||
<div>
|
||||
<div className="divide-y divide-subtle-1">
|
||||
<div className="nodedc-settings-card overflow-hidden px-1 py-1">
|
||||
<Table
|
||||
columns={columns}
|
||||
data={exporterServices?.results ?? []}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export const StartOfWeekPreference = observer(function StartOfWeekPreference(pro
|
|||
<SelectionDropdown
|
||||
placement="bottom-end"
|
||||
menuButton={getStartOfWeekLabel(userProfile.start_of_the_week)}
|
||||
menuButtonWrapperClassName="flex w-full items-center justify-between rounded-full border border-subtle-1 px-3 py-2 text-13"
|
||||
menuButtonWrapperClassName="nodedc-settings-select flex w-full items-center justify-between px-4 py-3 text-13 font-medium"
|
||||
options={START_OF_THE_WEEK_OPTIONS.map((day) => ({
|
||||
key: `${day.value}`,
|
||||
title: day.label,
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export const ProjectMemberList = observer(function ProjectMemberList(props: TPro
|
|||
projectId={projectId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-4 overflow-x-hidden border-b border-subtle py-2">
|
||||
<div className="flex items-center justify-between gap-4 overflow-x-hidden pb-4">
|
||||
<div className="text-14 font-semibold">{t("common.members")}</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="nodedc-settings-field flex min-h-[2.75rem] items-center justify-start gap-1.5 px-3">
|
||||
|
|
@ -116,7 +116,7 @@ export const ProjectMemberList = observer(function ProjectMemberList(props: TPro
|
|||
{!projectMemberIds ? (
|
||||
<MembersSettingsLoader />
|
||||
) : (
|
||||
<div className="divide-y divide-subtle overflow-scroll">
|
||||
<div className="nodedc-settings-card overflow-scroll px-1 py-1">
|
||||
{searchedProjectMembers.length !== 0 && (
|
||||
<ProjectMemberListItem
|
||||
memberDetails={memberDetails ?? []}
|
||||
|
|
|
|||
|
|
@ -61,11 +61,11 @@ export const ActivityProfileSettingsList = observer(function ProfileActivityList
|
|||
return (
|
||||
<>
|
||||
{userProfileActivity ? (
|
||||
<ul>
|
||||
<ul className="space-y-2">
|
||||
{userProfileActivity.results.map((activityItem: any) => {
|
||||
if (activityItem.field === "comment")
|
||||
return (
|
||||
<div key={activityItem.id} className="mt-2">
|
||||
<div key={activityItem.id} className="nodedc-settings-card px-5 py-4">
|
||||
<div className="relative flex items-start space-x-3">
|
||||
<div className="relative px-1">
|
||||
{activityItem.field ? (
|
||||
|
|
@ -90,12 +90,12 @@ export const ActivityProfileSettingsList = observer(function ProfileActivityList
|
|||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div>
|
||||
<div className="text-11">
|
||||
<div className="text-11 text-primary">
|
||||
{activityItem.actor_detail.is_bot
|
||||
? activityItem.actor_detail.first_name + " Bot"
|
||||
: activityItem.actor_detail.display_name}
|
||||
</div>
|
||||
<p className="mt-0.5 text-11 text-secondary">
|
||||
<p className="mt-0.5 text-11 text-tertiary">
|
||||
Commented {calculateTimeAgo(activityItem.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -122,7 +122,7 @@ export const ActivityProfileSettingsList = observer(function ProfileActivityList
|
|||
if ("field" in activityItem && activityItem.field !== "updated_by")
|
||||
return (
|
||||
<li key={activityItem.id}>
|
||||
<div className="relative pb-1">
|
||||
<div className="nodedc-settings-card relative px-5 py-4">
|
||||
<div className="relative flex items-start space-x-2">
|
||||
<>
|
||||
<div>
|
||||
|
|
@ -153,7 +153,7 @@ export const ActivityProfileSettingsList = observer(function ProfileActivityList
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 border-b border-subtle py-4">
|
||||
<div className="min-w-0 flex-1 py-2">
|
||||
<div className="text-caption-md-regular break-words text-secondary">
|
||||
{activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? (
|
||||
<span className="text-gray font-medium">NODE.DC</span>
|
||||
|
|
|
|||
|
|
@ -82,10 +82,16 @@ export const ActivityProfileSettings = observer(function ActivityProfileSettings
|
|||
title={t("account_settings.activity.heading")}
|
||||
description={t("account_settings.activity.description")}
|
||||
/>
|
||||
<div className="mt-7 w-full">{activityPages}</div>
|
||||
<div className="mt-7 w-full space-y-2">{activityPages}</div>
|
||||
{isLoadMoreVisible && (
|
||||
<div className="mt-4 flex w-full items-center justify-center">
|
||||
<Button variant="ghost" onClick={handleLoadMore} appendIcon={<ChevronDown />}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
className="nodedc-settings-primary-button min-w-[10.5rem]"
|
||||
onClick={handleLoadMore}
|
||||
appendIcon={<ChevronDown />}
|
||||
>
|
||||
{t("load_more")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -41,20 +41,23 @@ export const APITokensProfileSettings = observer(function APITokensProfileSettin
|
|||
title={t("account_settings.api_tokens.heading")}
|
||||
description={t("account_settings.api_tokens.description")}
|
||||
control={
|
||||
<Button variant="primary" size="lg" onClick={() => setIsCreateTokenModalOpen(true)}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
className="nodedc-settings-primary-button min-w-[11rem]"
|
||||
onClick={() => setIsCreateTokenModalOpen(true)}
|
||||
>
|
||||
{t("workspace_settings.settings.api_tokens.add_token")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<div className="mt-7">
|
||||
{tokens.length > 0 ? (
|
||||
<>
|
||||
<div>
|
||||
{tokens.map((token) => (
|
||||
<ApiTokenListItem key={token.id} token={token} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
<div className="space-y-2">
|
||||
{tokens.map((token) => (
|
||||
<ApiTokenListItem key={token.id} token={token} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyStateCompact
|
||||
assetKey="token"
|
||||
|
|
|
|||
|
|
@ -211,63 +211,61 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
/>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="w-full">
|
||||
<div className="flex w-full flex-col gap-7">
|
||||
<div className="relative h-44 w-full">
|
||||
<CoverImage
|
||||
src={userCover}
|
||||
className="h-44 w-full rounded-lg"
|
||||
alt={currentUser?.first_name ?? "Cover image"}
|
||||
/>
|
||||
<div className="absolute -bottom-6 left-6 flex items-end justify-between">
|
||||
<div className="flex gap-3">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-surface-2">
|
||||
<button type="button" onClick={() => setIsImageUploadModalOpen(true)}>
|
||||
{!userAvatar || userAvatar === "" ? (
|
||||
<div className="h-16 w-16 rounded-md bg-layer-1 p-2">
|
||||
<CircleUserRound className="h-full w-full text-secondary" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative h-16 w-16 overflow-hidden">
|
||||
<img
|
||||
src={getFileURL(userAvatar)}
|
||||
className="absolute top-0 left-0 h-full w-full rounded-lg object-cover"
|
||||
onClick={() => setIsImageUploadModalOpen(true)}
|
||||
alt={currentUser?.display_name}
|
||||
role="button"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<div className="nodedc-settings-card overflow-hidden">
|
||||
<div className="relative h-44 w-full overflow-hidden rounded-[1.35rem]">
|
||||
<CoverImage
|
||||
src={userCover}
|
||||
className="h-44 w-full rounded-[1.35rem]"
|
||||
alt={currentUser?.first_name ?? "Cover image"}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-black/10 via-transparent to-black/45" />
|
||||
<div className="absolute right-4 bottom-4 flex">
|
||||
<Controller
|
||||
control={control}
|
||||
name="cover_image_url"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ImagePickerPopover
|
||||
label={t("change_cover")}
|
||||
control={control}
|
||||
onChange={(imageUrl) => onChange(imageUrl)}
|
||||
value={value}
|
||||
isProfileCover
|
||||
buttonClassName="nodedc-overlay-button min-w-[10.5rem]"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mt-7 flex items-end justify-between gap-4 px-6 pb-6">
|
||||
<div className="flex items-end gap-4">
|
||||
<button type="button" onClick={() => setIsImageUploadModalOpen(true)} className="shrink-0">
|
||||
{!userAvatar || userAvatar === "" ? (
|
||||
<div className="grid h-[5.5rem] w-[5.5rem] place-items-center rounded-[1.35rem] bg-white/8 p-3 backdrop-blur-xl">
|
||||
<CircleUserRound className="h-full w-full text-primary" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative h-[5.5rem] w-[5.5rem] overflow-hidden rounded-[1.35rem] bg-white/8">
|
||||
<img
|
||||
src={getFileURL(userAvatar)}
|
||||
className="absolute top-0 left-0 h-full w-full rounded-[1.35rem] object-cover"
|
||||
onClick={() => setIsImageUploadModalOpen(true)}
|
||||
alt={currentUser?.display_name}
|
||||
role="button"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<div className="flex flex-col gap-1 pb-1">
|
||||
<div className="text-h4-semibold leading-6 text-primary">{`${watch("first_name")} ${watch("last_name")}`}</div>
|
||||
<span className="text-body-sm-regular text-tertiary">{watch("email")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute right-3 bottom-3 flex">
|
||||
<Controller
|
||||
control={control}
|
||||
name="cover_image_url"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ImagePickerPopover
|
||||
label={t("change_cover")}
|
||||
control={control}
|
||||
onChange={(imageUrl) => onChange(imageUrl)}
|
||||
value={value}
|
||||
isProfileCover
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="item-center mt-6 flex justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="item-center flex text-16 font-medium text-secondary">
|
||||
<span>{`${watch("first_name")} ${watch("last_name")}`}</span>
|
||||
</div>
|
||||
<span className="text-13 tracking-tight text-tertiary">{watch("email")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-13 font-medium text-secondary">
|
||||
<div className="nodedc-settings-card flex flex-col gap-6 px-5 py-5">
|
||||
<div className="grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2 xl:grid-cols-3">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<h4 className="text-13 font-medium text-tertiary">
|
||||
{t("first_name")}
|
||||
<span className="text-danger-primary">*</span>
|
||||
</h4>
|
||||
|
|
@ -288,7 +286,7 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
ref={ref}
|
||||
hasError={Boolean(errors.first_name)}
|
||||
placeholder={t("profile_general.first_name_placeholder")}
|
||||
className={`w-full rounded-md ${errors.first_name ? "border-danger-strong" : ""}`}
|
||||
className={`nodedc-settings-input w-full ${errors.first_name ? "border-danger-strong" : ""}`}
|
||||
maxLength={50}
|
||||
autoComplete="on"
|
||||
/>
|
||||
|
|
@ -296,8 +294,8 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
/>
|
||||
{errors.first_name && <span className="text-11 text-danger-primary">{errors.first_name.message}</span>}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-13 font-medium text-secondary">{t("last_name")}</h4>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<h4 className="text-13 font-medium text-tertiary">{t("last_name")}</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
name="last_name"
|
||||
|
|
@ -314,7 +312,7 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
ref={ref}
|
||||
hasError={Boolean(errors.last_name)}
|
||||
placeholder={t("profile_general.last_name_placeholder")}
|
||||
className="w-full rounded-md"
|
||||
className="nodedc-settings-input w-full"
|
||||
maxLength={50}
|
||||
autoComplete="on"
|
||||
/>
|
||||
|
|
@ -322,8 +320,8 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
/>
|
||||
{errors.last_name && <span className="text-11 text-danger-primary">{errors.last_name.message}</span>}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-13 font-medium text-secondary">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<h4 className="text-13 font-medium text-tertiary">
|
||||
{t("display_name")}
|
||||
<span className="text-danger-primary">*</span>
|
||||
</h4>
|
||||
|
|
@ -344,7 +342,7 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
ref={ref}
|
||||
hasError={Boolean(errors?.display_name)}
|
||||
placeholder={t("profile_general.display_name_placeholder")}
|
||||
className={`w-full ${errors?.display_name ? "border-danger-strong" : ""}`}
|
||||
className={`nodedc-settings-input w-full ${errors?.display_name ? "border-danger-strong" : ""}`}
|
||||
maxLength={50}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -353,8 +351,8 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
<span className="text-11 text-danger-primary">{errors?.display_name?.message}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-13 font-medium text-secondary">
|
||||
<div className="flex flex-col gap-1.5 xl:col-span-2">
|
||||
<h4 className="text-13 font-medium text-tertiary">
|
||||
{t("auth.common.email.label")}
|
||||
<span className="text-danger-primary">*</span>
|
||||
</h4>
|
||||
|
|
@ -373,7 +371,7 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
ref={ref}
|
||||
hasError={Boolean(errors.email)}
|
||||
placeholder={t("profile_general.email_placeholder")}
|
||||
className={`w-full cursor-not-allowed rounded-md !bg-surface-2 ${
|
||||
className={`nodedc-settings-input w-full cursor-not-allowed !bg-white/4 ${
|
||||
errors.email ? "border-danger-strong" : ""
|
||||
}`}
|
||||
autoComplete="on"
|
||||
|
|
@ -384,7 +382,7 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
{isSMTPConfigured && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn w-fit text-11 text-secondary underline"
|
||||
className="nodedc-settings-chip flex w-fit items-center gap-2 px-3.5 py-1.5 text-12 font-medium text-primary"
|
||||
onClick={() => setIsChangeEmailModalOpen(true)}
|
||||
>
|
||||
{t("account_settings.profile.change_email_modal.title")}
|
||||
|
|
@ -393,8 +391,15 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button variant="primary" type="submit" loading={isLoading}>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<div />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
type="submit"
|
||||
loading={isLoading}
|
||||
className="nodedc-settings-save-button min-w-[12rem]"
|
||||
>
|
||||
{isLoading ? t("saving") : t("save_changes")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -405,7 +410,11 @@ export const GeneralProfileSettingsForm = observer(function GeneralProfileSettin
|
|||
title={t("deactivate_account")}
|
||||
description={t("deactivate_account_description")}
|
||||
control={
|
||||
<Button variant="error-outline" onClick={() => setDeactivateAccountModal(true)}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="nodedc-modal-danger-button min-w-[11rem]"
|
||||
onClick={() => setDeactivateAccountModal(true)}
|
||||
>
|
||||
{t("deactivate_account")}
|
||||
</Button>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export const NotificationsProfileSettingsForm = observer(function NotificationsP
|
|||
}, [reset, data]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<SettingsControlItem
|
||||
title={t("property_changes")}
|
||||
description={t("property_changes_description")}
|
||||
|
|
@ -100,7 +100,7 @@ export const NotificationsProfileSettingsForm = observer(function NotificationsP
|
|||
/>
|
||||
}
|
||||
/>
|
||||
<div className="border-l-3 border-subtle-1 pl-3">
|
||||
<div className="ml-4 border-l border-white/8 pl-4">
|
||||
<SettingsControlItem
|
||||
title={t("issue_completed")}
|
||||
description={t("issue_completed_description")}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export const NotificationsProfileSettings = observer(function NotificationsProfi
|
|||
title={t("account_settings.notifications.heading")}
|
||||
description={t("account_settings.notifications.description")}
|
||||
/>
|
||||
<div className="mt-7">
|
||||
<div className="mt-7 flex flex-col gap-3">
|
||||
<NotificationsProfileSettingsForm data={data} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,14 @@ export const ProfileSettingsLanguageAndTimezonePreferencesList = observer(
|
|||
<SettingsControlItem
|
||||
title={t("timezone")}
|
||||
description={t("timezone_setting")}
|
||||
control={<TimezoneSelect value={user?.user_timezone || "Asia/Kolkata"} onChange={handleTimezoneChange} />}
|
||||
control={
|
||||
<TimezoneSelect
|
||||
value={user?.user_timezone || "Asia/Kolkata"}
|
||||
onChange={handleTimezoneChange}
|
||||
buttonClassName="nodedc-settings-select !border-0 !px-4 !py-3 text-13 font-medium"
|
||||
className="!rounded-[1.25rem]"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsControlItem
|
||||
title={t("language")}
|
||||
|
|
@ -87,7 +94,7 @@ export const ProfileSettingsLanguageAndTimezonePreferencesList = observer(
|
|||
onClick: () => handleLanguageChange(item.value),
|
||||
}))}
|
||||
menuButton={profile?.language ? getLanguageLabel(profile?.language) : "Select a language"}
|
||||
menuButtonWrapperClassName="rounded-md border border-subtle-1 px-3 py-2 text-13"
|
||||
menuButtonWrapperClassName="nodedc-settings-select px-4 py-3 text-13 font-medium"
|
||||
placement="bottom-end"
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export const PreferencesProfileSettings = observer(function PreferencesProfileSe
|
|||
description={t("account_settings.preferences.description")}
|
||||
/>
|
||||
<div className="mt-7 flex w-full flex-col gap-6">
|
||||
<section>
|
||||
<section className="flex flex-col gap-3">
|
||||
<ProfileSettingsDefaultPreferencesList />
|
||||
</section>
|
||||
<section className="flex flex-col gap-y-3">
|
||||
|
|
|
|||
|
|
@ -132,10 +132,10 @@ export const SecurityProfileSettings = observer(function SecurityProfileSettings
|
|||
<div className="size-full">
|
||||
<ProfileSettingsHeading title={t("auth.common.password.change_password.label.default")} />
|
||||
<form onSubmit={handleSubmit(handleChangePassword)} className="mt-7 flex flex-col gap-8">
|
||||
<div className="flex flex-col gap-y-7">
|
||||
<div className="nodedc-settings-card flex flex-col gap-y-7 px-5 py-5">
|
||||
{oldPasswordRequired && (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<h4 className="text-13">{t("auth.common.password.current_password.label")}</h4>
|
||||
<h4 className="text-13 font-medium text-tertiary">{t("auth.common.password.current_password.label")}</h4>
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -150,7 +150,7 @@ export const SecurityProfileSettings = observer(function SecurityProfileSettings
|
|||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={t("old_password")}
|
||||
className="w-full"
|
||||
className="nodedc-settings-input w-full"
|
||||
hasError={Boolean(errors.old_password)}
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
|
|
@ -175,7 +175,7 @@ export const SecurityProfileSettings = observer(function SecurityProfileSettings
|
|||
)}
|
||||
<div className="grid gap-x-4 gap-y-7 sm:grid-cols-2">
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<h4 className="text-13">{t("auth.common.password.new_password.label")}</h4>
|
||||
<h4 className="text-13 font-medium text-tertiary">{t("auth.common.password.new_password.label")}</h4>
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -190,7 +190,7 @@ export const SecurityProfileSettings = observer(function SecurityProfileSettings
|
|||
value={value}
|
||||
placeholder={t("auth.common.password.new_password.placeholder")}
|
||||
onChange={onChange}
|
||||
className="w-full"
|
||||
className="nodedc-settings-input w-full"
|
||||
hasError={Boolean(errors.new_password)}
|
||||
onFocus={() => setIsPasswordInputFocused(true)}
|
||||
onBlur={() => setIsPasswordInputFocused(false)}
|
||||
|
|
@ -221,7 +221,7 @@ export const SecurityProfileSettings = observer(function SecurityProfileSettings
|
|||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<h4 className="text-13">{t("auth.common.password.confirm_password.label")}</h4>
|
||||
<h4 className="text-13 font-medium text-tertiary">{t("auth.common.password.confirm_password.label")}</h4>
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -236,7 +236,7 @@ export const SecurityProfileSettings = observer(function SecurityProfileSettings
|
|||
placeholder={t("auth.common.password.confirm_password.placeholder")}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="w-full"
|
||||
className="nodedc-settings-input w-full"
|
||||
hasError={Boolean(errors.confirm_password)}
|
||||
onFocus={() => setIsRetryPasswordInputFocused(true)}
|
||||
onBlur={() => setIsRetryPasswordInputFocused(false)}
|
||||
|
|
@ -262,7 +262,14 @@ export const SecurityProfileSettings = observer(function SecurityProfileSettings
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button variant="primary" size="xl" type="submit" loading={isSubmitting} disabled={isButtonDisabled}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
type="submit"
|
||||
loading={isSubmitting}
|
||||
disabled={isButtonDisabled}
|
||||
className="nodedc-settings-save-button min-w-[12rem]"
|
||||
>
|
||||
{isSubmitting
|
||||
? `${t("auth.common.password.change_password.label.submitting")}`
|
||||
: t("auth.common.password.change_password.label.default")}
|
||||
|
|
|
|||
|
|
@ -39,19 +39,25 @@ export const ProfileSettingsModal = observer(function ProfileSettingsModal() {
|
|||
handleClose={handleClose}
|
||||
position={EModalPosition.CENTER}
|
||||
width={EModalWidth.VIXL}
|
||||
className="h-175"
|
||||
className="nodedc-glass-modal h-175 overflow-hidden rounded-[1.85rem] border-0 shadow-none"
|
||||
>
|
||||
<div className="@container relative size-full">
|
||||
<div className="@container relative size-full overflow-hidden rounded-[1.85rem]">
|
||||
<div className="flex size-full">
|
||||
<ProfileSettingsSidebarRoot
|
||||
activeTab={activeTab}
|
||||
className="w-[250px] rounded-l-xl"
|
||||
className="w-[280px] rounded-l-[1.85rem]"
|
||||
updateActiveTab={(tab) => toggleProfileSettingsModal({ activeTab: tab })}
|
||||
/>
|
||||
<ProfileSettingsContent activeTab={activeTab} className="flex-1 rounded-r-xl" />
|
||||
<ProfileSettingsContent activeTab={activeTab} className="flex-1 rounded-r-[1.85rem]" />
|
||||
</div>
|
||||
<div className="absolute top-3.5 right-3.5">
|
||||
<IconButton size="base" variant="tertiary" icon={X} onClick={handleClose} />
|
||||
<IconButton
|
||||
size="base"
|
||||
variant="ghost"
|
||||
icon={X}
|
||||
onClick={handleClose}
|
||||
className="nodedc-overlay-button !h-11 !w-11 !min-h-11 !rounded-[1.1rem] !px-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ModalCore>
|
||||
|
|
|
|||
|
|
@ -105,7 +105,12 @@ export function CreateWebhookModal(props: ICreateWebhookModal) {
|
|||
});
|
||||
|
||||
return (
|
||||
<ModalCore isOpen={isOpen} position={EModalPosition.TOP} width={EModalWidth.XXL} className="p-4 pb-0">
|
||||
<ModalCore
|
||||
isOpen={isOpen}
|
||||
position={EModalPosition.TOP}
|
||||
width={EModalWidth.XXL}
|
||||
className="nodedc-glass-modal rounded-[1.75rem] p-4 pb-0"
|
||||
>
|
||||
{!generatedWebhook ? (
|
||||
<WebhookForm onSubmit={handleCreateWebhook} handleClose={handleClose} />
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -17,17 +17,15 @@ type Props = {
|
|||
export function WebhooksEmptyState(props: Props) {
|
||||
const { onClick } = props;
|
||||
return (
|
||||
<div
|
||||
className={`mx-auto flex w-full items-center justify-center rounded-xs border border-subtle bg-surface-2 px-16 py-10 lg:w-3/4`}
|
||||
>
|
||||
<div className="flex w-full flex-col items-center text-center">
|
||||
<img src={EmptyWebhook} className="w-52 object-cover sm:w-60" alt="empty" />
|
||||
<h6 className="mt-6 mb-3 text-18 font-semibold sm:mt-8">No webhooks</h6>
|
||||
<p className="mb-7 text-tertiary sm:mb-8">Create webhooks to receive real-time updates and automate actions</p>
|
||||
<Button className="flex items-center gap-1.5" onClick={onClick}>
|
||||
<div className="mx-auto flex w-full max-w-[34rem] flex-col items-center text-center">
|
||||
<img src={EmptyWebhook} className="w-40 object-cover opacity-90 sm:w-48" alt="empty" />
|
||||
<h6 className="mt-6 text-2xl font-semibold text-primary">No webhooks</h6>
|
||||
<p className="mt-3 max-w-[28rem] text-body-sm-regular text-tertiary">
|
||||
Create webhooks to receive real-time updates and automate actions.
|
||||
</p>
|
||||
<Button variant="ghost" size="lg" className="nodedc-settings-primary-button mt-7 min-w-[11rem]" onClick={onClick}>
|
||||
Add webhook
|
||||
</Button>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export const WebhookForm = observer(function WebhookForm(props: Props) {
|
|||
return (
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||
<div className="space-y-5">
|
||||
<div className="text-18 font-medium text-secondary">
|
||||
<div className="text-18 font-medium text-primary">
|
||||
{data
|
||||
? t("workspace_settings.settings.webhooks.modal.details")
|
||||
: t("workspace_settings.settings.webhooks.modal.title")}
|
||||
|
|
@ -99,9 +99,11 @@ export const WebhookForm = observer(function WebhookForm(props: Props) {
|
|||
<div className="space-y-5 pt-0">
|
||||
<WebhookSecretKey data={data} />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
type="submit"
|
||||
loading={isSubmitting}
|
||||
className="nodedc-settings-save-button min-w-[11rem]"
|
||||
data-ph-element={WORKSPACE_SETTINGS_TRACKER_ELEMENTS.WEBHOOK_UPDATE_BUTTON}
|
||||
>
|
||||
{isSubmitting ? t("updating") : t("update")}
|
||||
|
|
@ -109,11 +111,17 @@ export const WebhookForm = observer(function WebhookForm(props: Props) {
|
|||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-end gap-2 border-t-[0.5px] border-subtle px-5 py-4">
|
||||
<Button variant="secondary" size="lg" onClick={handleClose}>
|
||||
<Button variant="secondary" size="lg" className="nodedc-modal-secondary-button" onClick={handleClose}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
{!webhookSecretKey && (
|
||||
<Button type="submit" variant="primary" size="lg" loading={isSubmitting} className="capitalize">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
loading={isSubmitting}
|
||||
className="nodedc-modal-primary-button min-w-[9.5rem] capitalize"
|
||||
>
|
||||
{isSubmitting ? t("common.creating") : t("common.create")}
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@ export function WebhooksListItem(props: IWebhookListItem) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-subtle bg-layer-2 px-4 py-3">
|
||||
<div className="nodedc-settings-card px-4 py-3">
|
||||
<Link
|
||||
href={`/${workspaceSlug}/settings/webhooks/${webhook?.id}`}
|
||||
className="flex items-center justify-between gap-4"
|
||||
>
|
||||
<h5 className="truncate text-body-sm-medium">{webhook.url}</h5>
|
||||
<h5 className="truncate text-body-sm-medium text-primary">{webhook.url}</h5>
|
||||
<div className="shrink-0">
|
||||
<ToggleSwitch value={webhook.is_active} onChange={handleToggle} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const WebhooksList = observer(function WebhooksList() {
|
|||
const { webhooks } = useWebhook();
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col gap-y-2 overflow-y-auto rounded-lg border border-subtle bg-layer-1 p-3">
|
||||
<div className="nodedc-settings-card flex size-full flex-col gap-y-3 overflow-y-auto p-3">
|
||||
{Object.values(webhooks ?? {}).map((webhook) => (
|
||||
<WebhooksListItem key={webhook.id} webhook={webhook} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -122,17 +122,17 @@ export const WorkspaceInvitationsListItem = observer(function WorkspaceInvitatio
|
|||
}}
|
||||
onSubmit={handleRemoveInvitation}
|
||||
/>
|
||||
<div className="group flex h-full w-full items-center justify-between px-3 py-4 hover:bg-layer-transparent-hover">
|
||||
<div className="group flex h-full w-full items-center justify-between rounded-[1rem] px-4 py-4 transition-colors hover:bg-white/4">
|
||||
<div className="flex items-center gap-x-4 gap-y-2">
|
||||
<span className="relative flex h-10 w-10 items-center justify-center rounded-sm bg-layer-3 p-4 text-tertiary capitalize">
|
||||
<span className="relative flex h-10 w-10 items-center justify-center rounded-[0.95rem] bg-white/6 p-4 text-tertiary capitalize">
|
||||
{(invitationDetails.email ?? "?")[0]}
|
||||
</span>
|
||||
<div>
|
||||
<h4 className="cursor-default text-body-xs-regular">{invitationDetails.email}</h4>
|
||||
<h4 className="cursor-default text-body-xs-regular text-primary">{invitationDetails.email}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-11">
|
||||
<div className="flex items-center justify-center rounded-sm bg-label-yellow-bg-strong/20 px-2.5 py-1 text-center text-caption-sm-medium text-label-yellow-text">
|
||||
<div className="flex items-center justify-center rounded-full bg-label-yellow-bg-strong/20 px-2.5 py-1 text-center text-caption-sm-medium text-label-yellow-text">
|
||||
<p>{t("common.pending")}</p>
|
||||
</div>
|
||||
<SelectionDropdown
|
||||
|
|
@ -167,7 +167,7 @@ export const WorkspaceInvitationsListItem = observer(function WorkspaceInvitatio
|
|||
},
|
||||
}))}
|
||||
menuButton={
|
||||
<div className="item-center flex gap-1 rounded-sm px-2 py-0.5">
|
||||
<div className="nodedc-settings-chip item-center flex gap-1 px-3 py-1">
|
||||
<span
|
||||
className={`flex items-center rounded-sm text-caption-sm-medium ${
|
||||
hasRoleChangeAccess ? "" : "text-placeholder"
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ export const WorkspaceMembersListItem = observer(function WorkspaceMembersListIt
|
|||
if (isEmpty(columns)) return <MembersLayoutLoader />;
|
||||
|
||||
return (
|
||||
<div className="grid border-t border-subtle">
|
||||
<div className="grid overflow-hidden rounded-[1.2rem]">
|
||||
{removeMemberModal && (
|
||||
<ConfirmWorkspaceMemberRemove
|
||||
isOpen={removeMemberModal.member.id.length > 0}
|
||||
|
|
@ -109,10 +109,10 @@ export const WorkspaceMembersListItem = observer(function WorkspaceMembersListIt
|
|||
(memberDetails?.filter((member): member is IWorkspaceMember => member !== null) ?? []) as unknown as RowData[]
|
||||
}
|
||||
keyExtractor={(rowData) => rowData?.member.id ?? ""}
|
||||
tHeadClassName="border-b border-subtle"
|
||||
tHeadClassName="border-b border-white/6"
|
||||
thClassName="text-left font-medium divide-x-0 text-placeholder"
|
||||
tBodyClassName="divide-y-0"
|
||||
tBodyTrClassName="divide-x-0 p-4 h-10 text-secondary"
|
||||
tBodyTrClassName="divide-x-0 h-11 px-4 text-secondary"
|
||||
tHeadTrClassName="divide-x-0"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export const WorkspaceMembersList = observer(function WorkspaceMembersList(props
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="divide-y-[0.5px] divide-subtle overflow-scroll">
|
||||
<div className="nodedc-settings-card overflow-hidden px-1 py-1">
|
||||
{searchedMemberIds?.length !== 0 && <WorkspaceMembersListItem memberDetails={memberDetails ?? []} />}
|
||||
{searchedInvitationsIds?.length === 0 && searchedMemberIds?.length === 0 && (
|
||||
<h4 className="mt-16 text-center text-body-xs-regular text-placeholder">{t("no_matching_members")}</h4>
|
||||
|
|
@ -85,7 +85,7 @@ export const WorkspaceMembersList = observer(function WorkspaceMembersList(props
|
|||
buttonClassName="w-full"
|
||||
className=""
|
||||
title={
|
||||
<div className="flex w-full items-center justify-between pt-4">
|
||||
<div className="flex w-full items-center justify-between pt-5">
|
||||
<div className="flex">
|
||||
<h4 className="pt-2 pb-2 text-h5-medium">{t("workspace_settings.settings.members.pending_invites")}</h4>
|
||||
{searchedInvitationsIds && (
|
||||
|
|
@ -97,7 +97,7 @@ export const WorkspaceMembersList = observer(function WorkspaceMembersList(props
|
|||
}
|
||||
>
|
||||
<Disclosure.Panel>
|
||||
<div className="ml-auto items-center gap-1.5 rounded-md bg-surface-1 py-1.5">
|
||||
<div className="nodedc-settings-card ml-auto items-center gap-1.5 py-2">
|
||||
{searchedInvitationsIds?.map((invitationId) => (
|
||||
<WorkspaceInvitationsListItem key={invitationId} invitationId={invitationId} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -147,27 +147,27 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
|
|||
)}
|
||||
/>
|
||||
<div className={cn("flex w-full flex-col gap-y-7", { "opacity-60": !isAdmin })}>
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="nodedc-settings-card flex items-center gap-5 px-5 py-5">
|
||||
<div className="flex shrink-0 flex-col gap-1">
|
||||
<button type="button" onClick={() => setIsImageUploadModalOpen(true)} disabled={!isAdmin}>
|
||||
{workspaceLogo && workspaceLogo !== "" ? (
|
||||
<div className="relative flex size-14">
|
||||
<img
|
||||
src={getFileURL(workspaceLogo)}
|
||||
className="absolute top-0 left-0 size-full rounded-md object-cover"
|
||||
className="absolute top-0 left-0 size-full rounded-[1rem] object-cover"
|
||||
alt="Workspace Logo"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative grid size-14 place-items-center rounded-md bg-accent-primary text-24 text-on-color uppercase">
|
||||
<div className="relative grid size-14 place-items-center rounded-[1rem] bg-accent-primary text-24 text-on-color uppercase">
|
||||
{currentWorkspace?.name?.charAt(0) ?? "N"}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="mb:-my-5 text-h5-semibold leading-6">{watch("name")}</div>
|
||||
<button type="button" onClick={handleCopyUrl} className="text-left text-body-xs-regular tracking-tight">{`${
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-h5-semibold leading-6 text-primary">{watch("name")}</div>
|
||||
<button type="button" onClick={handleCopyUrl} className="text-left text-body-xs-regular tracking-tight text-tertiary">{`${
|
||||
typeof window !== "undefined" && window.location.origin.replace("http://", "").replace("https://", "")
|
||||
}/${currentWorkspace.slug}`}</button>
|
||||
{isAdmin && (
|
||||
|
|
@ -188,9 +188,9 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-7">
|
||||
<div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-10 xl:grid-cols-2 2xl:grid-cols-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="nodedc-settings-card flex flex-col gap-7 px-5 py-5">
|
||||
<div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-8 xl:grid-cols-2 2xl:grid-cols-3">
|
||||
<div className="flex flex-col gap-2.5">
|
||||
<h4 className="text-body-sm-medium text-tertiary">{t("workspace_settings.settings.general.name")}</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -208,14 +208,14 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
|
|||
ref={ref}
|
||||
hasError={Boolean(errors.name)}
|
||||
placeholder={t("workspace_settings.settings.general.name")}
|
||||
className="w-full rounded-md"
|
||||
className="nodedc-settings-input w-full"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.name && <p className="text-caption-sm-regular text-danger-primary">{errors.name.message}</p>}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2.5">
|
||||
<h4 className="text-body-sm-medium text-tertiary">
|
||||
{t("workspace_settings.settings.general.company_size")}
|
||||
</h4>
|
||||
|
|
@ -234,13 +234,13 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
|
|||
ORGANIZATION_SIZE.find((c) => c === value) ??
|
||||
t("workspace_settings.settings.general.errors.company_size.select_a_range")
|
||||
}
|
||||
menuButtonWrapperClassName="rounded-md border border-subtle bg-layer-2 px-3 py-2 text-13 shadow-none"
|
||||
menuButtonWrapperClassName="nodedc-settings-select px-4 py-3 text-13 font-medium"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2.5">
|
||||
<h4 className="text-body-sm-medium text-tertiary">{t("workspace_settings.settings.general.url")}</h4>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -257,13 +257,13 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
|
|||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.url)}
|
||||
className="w-full cursor-not-allowed rounded-md !bg-layer-1"
|
||||
className="nodedc-settings-input w-full cursor-not-allowed !bg-white/4"
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2.5">
|
||||
<h4 className="text-body-sm-medium text-tertiary">
|
||||
{t("workspace_settings.settings.general.workspace_timezone")}
|
||||
</h4>
|
||||
|
|
@ -280,10 +280,11 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
|
|||
</div>
|
||||
</div>
|
||||
{isAdmin && (
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
className="nodedc-settings-save-button min-w-[13rem]"
|
||||
onClick={(e) => {
|
||||
void handleSubmit(onSubmit)(e);
|
||||
}}
|
||||
|
|
|
|||
Loading…
Reference in New Issue