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