UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: единый модальный режим настроек воркспейса
This commit is contained in:
@@ -9,15 +9,18 @@ import { useParams } from "next/navigation";
|
||||
// Plane imports
|
||||
import type { IWebhook } from "@plane/types";
|
||||
import { ToggleSwitch } from "@plane/ui";
|
||||
// components
|
||||
import { openWorkspaceWebhookSettingsModal } from "@/components/workspace/settings/workspace-settings-modal.utils";
|
||||
// hooks
|
||||
import { useWebhook } from "@/hooks/store/use-webhook";
|
||||
|
||||
interface IWebhookListItem {
|
||||
useModalLink?: boolean;
|
||||
webhook: IWebhook;
|
||||
}
|
||||
|
||||
export function WebhooksListItem(props: IWebhookListItem) {
|
||||
const { webhook } = props;
|
||||
const { useModalLink = false, webhook } = props;
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
@@ -28,17 +31,33 @@ export function WebhooksListItem(props: IWebhookListItem) {
|
||||
await updateWebhook(workspaceSlug.toString(), webhook.id, { is_active: !webhook.is_active });
|
||||
};
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<h5 className="truncate text-body-sm-medium text-primary">{webhook.url}</h5>
|
||||
<div className="shrink-0" onClick={(event) => event.stopPropagation()}>
|
||||
<ToggleSwitch value={webhook.is_active} onChange={handleToggle} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<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 text-primary">{webhook.url}</h5>
|
||||
<div className="shrink-0">
|
||||
<ToggleSwitch value={webhook.is_active} onChange={handleToggle} />
|
||||
</div>
|
||||
</Link>
|
||||
{useModalLink && webhook.id ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openWorkspaceWebhookSettingsModal(webhook.id, true)}
|
||||
className="flex w-full items-center justify-between gap-4 text-left"
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
) : (
|
||||
<Link
|
||||
href={`/${workspaceSlug}/settings/webhooks/${webhook?.id}`}
|
||||
className="flex items-center justify-between gap-4"
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,14 +10,18 @@ import { useWebhook } from "@/hooks/store/use-webhook";
|
||||
// components
|
||||
import { WebhooksListItem } from "./webhooks-list-item";
|
||||
|
||||
export const WebhooksList = observer(function WebhooksList() {
|
||||
type TWebhooksListProps = {
|
||||
useModalLinks?: boolean;
|
||||
};
|
||||
|
||||
export const WebhooksList = observer(function WebhooksList({ useModalLinks = false }: TWebhooksListProps) {
|
||||
// store hooks
|
||||
const { webhooks } = useWebhook();
|
||||
|
||||
return (
|
||||
<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} />
|
||||
<WebhooksListItem key={webhook.id} webhook={webhook} useModalLink={useModalLinks} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
|
||||
import { ExportGuide } from "@/components/exporter/guide";
|
||||
import { SettingsHeading } from "@/components/settings/heading";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
|
||||
export function WorkspaceExportsSettingsContent() {
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { t } = useTranslation();
|
||||
const canPerformWorkspaceMemberActions = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
|
||||
if (workspaceUserInfo && !canPerformWorkspaceMemberActions) {
|
||||
return <NotAuthorizedView section="settings" className="h-auto" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("flex w-full flex-col gap-y-6", {
|
||||
"opacity-60": !canPerformWorkspaceMemberActions,
|
||||
})}
|
||||
>
|
||||
<SettingsHeading
|
||||
title={t("workspace_settings.settings.exports.heading")}
|
||||
description={t("workspace_settings.settings.exports.description")}
|
||||
/>
|
||||
<ExportGuide />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { SearchIcon } from "@plane/propel/icons";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { IWorkspaceBulkInviteFormData } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
|
||||
import { CountChip } from "@/components/common/count-chip";
|
||||
import { MemberListFiltersDropdown } from "@/components/project/dropdowns/filters/member-list";
|
||||
import { WorkspaceMembersList } from "@/components/workspace/settings/members-list";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
// plane web components
|
||||
import { BillingActionsButton } from "@/plane-web/components/workspace/billing/billing-actions-button";
|
||||
import { MembersActivityButton, SendWorkspaceInvitationModal } from "@/plane-web/components/workspace/members";
|
||||
|
||||
type TWorkspaceMembersSettingsContentProps = {
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export function WorkspaceMembersSettingsContent({ workspaceSlug }: TWorkspaceMembersSettingsContentProps) {
|
||||
const [inviteModal, setInviteModal] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const {
|
||||
workspace: { workspaceMemberIds, inviteMembersToWorkspace, filtersStore },
|
||||
} = useMember();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
const canPerformWorkspaceMemberActions = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
const appliedRoleFilters = filtersStore.filters?.roles || [];
|
||||
|
||||
const handleWorkspaceInvite = async (data: IWorkspaceBulkInviteFormData) => {
|
||||
try {
|
||||
await inviteMembersToWorkspace(workspaceSlug, data);
|
||||
setInviteModal(false);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: t("workspace_settings.settings.members.invitations_sent_successfully"),
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? (error as Error & { error?: string }).error : undefined;
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: `${message ?? t("something_went_wrong_please_try_again")}`,
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRoleFilterUpdate = (role: string) => {
|
||||
const currentRoles = filtersStore.filters?.roles || [];
|
||||
const updatedRoles = currentRoles.includes(role) ? currentRoles.filter((r) => r !== role) : [...currentRoles, role];
|
||||
|
||||
filtersStore.updateFilters({
|
||||
roles: updatedRoles.length > 0 ? updatedRoles : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
if (workspaceUserInfo && !canPerformWorkspaceMemberActions) {
|
||||
return <NotAuthorizedView section="settings" className="h-auto" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SendWorkspaceInvitationModal
|
||||
isOpen={inviteModal}
|
||||
onClose={() => setInviteModal(false)}
|
||||
onSubmit={handleWorkspaceInvite}
|
||||
/>
|
||||
<section
|
||||
className={cn("size-full", {
|
||||
"opacity-60": !canPerformWorkspaceMemberActions,
|
||||
})}
|
||||
>
|
||||
<div className="flex flex-wrap 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 && (
|
||||
<CountChip count={workspaceMemberIds.length} className="m-auto h-5" />
|
||||
)}
|
||||
</h4>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<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"
|
||||
placeholder={`${t("search")}...`}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<MemberListFiltersDropdown
|
||||
appliedFilters={appliedRoleFilters}
|
||||
handleUpdate={handleRoleFilterUpdate}
|
||||
memberType="workspace"
|
||||
/>
|
||||
<MembersActivityButton workspaceSlug={workspaceSlug} />
|
||||
{canPerformWorkspaceAdminActions && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
className="nodedc-settings-primary-button min-w-[11rem]"
|
||||
onClick={() => setInviteModal(true)}
|
||||
>
|
||||
{t("workspace_settings.settings.members.add_member")}
|
||||
</Button>
|
||||
)}
|
||||
<BillingActionsButton canPerformWorkspaceAdminActions={canPerformWorkspaceAdminActions} />
|
||||
</div>
|
||||
</div>
|
||||
<WorkspaceMembersList searchQuery={searchQuery} isAdmin={canPerformWorkspaceAdminActions} />
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { IWebhook } from "@plane/types";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
import { SettingsHeading } from "@/components/settings/heading";
|
||||
import { WebhookSettingsLoader } from "@/components/ui/loader/settings/web-hook";
|
||||
import { CreateWebhookModal, DeleteWebhookModal, WebhookDeleteSection, WebhookForm, WebhooksList } from "@/components/web-hooks";
|
||||
import { WebhooksEmptyState } from "@/components/web-hooks/empty-state";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useWebhook } from "@/hooks/store/use-webhook";
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
|
||||
type TWorkspaceWebhooksSettingsContentProps = {
|
||||
selectedWebhookId?: string;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export function WorkspaceWebhooksSettingsContent({
|
||||
selectedWebhookId,
|
||||
workspaceSlug,
|
||||
}: TWorkspaceWebhooksSettingsContentProps) {
|
||||
const [showCreateWebhookModal, setShowCreateWebhookModal] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { fetchWebhooks, webhooks, clearSecretKey, webhookSecretKey, createWebhook } = useWebhook();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
|
||||
useSWR(
|
||||
canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null,
|
||||
canPerformWorkspaceAdminActions ? () => fetchWebhooks(workspaceSlug) : null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showCreateWebhookModal && webhookSecretKey) clearSecretKey();
|
||||
}, [showCreateWebhookModal, webhookSecretKey, clearSecretKey]);
|
||||
|
||||
if (workspaceUserInfo && !canPerformWorkspaceAdminActions) {
|
||||
return <NotAuthorizedView section="settings" className="h-auto" />;
|
||||
}
|
||||
|
||||
if (selectedWebhookId) {
|
||||
return <WorkspaceWebhookDetailsSettingsContent webhookId={selectedWebhookId} workspaceSlug={workspaceSlug} />;
|
||||
}
|
||||
|
||||
if (!webhooks) return <WebhookSettingsLoader />;
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<CreateWebhookModal
|
||||
createWebhook={createWebhook}
|
||||
clearSecretKey={clearSecretKey}
|
||||
currentWorkspace={currentWorkspace}
|
||||
isOpen={showCreateWebhookModal}
|
||||
onClose={() => setShowCreateWebhookModal(false)}
|
||||
/>
|
||||
<SettingsHeading
|
||||
title={t("workspace_settings.settings.webhooks.title")}
|
||||
description={t("workspace_settings.settings.webhooks.description")}
|
||||
control={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
className="nodedc-settings-primary-button min-w-[11rem]"
|
||||
onClick={() => setShowCreateWebhookModal(true)}
|
||||
>
|
||||
{t("workspace_settings.settings.webhooks.add_webhook")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
{Object.keys(webhooks).length > 0 ? (
|
||||
<div className="mt-4">
|
||||
<WebhooksList useModalLinks />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="py-20">
|
||||
<WebhooksEmptyState onClick={() => setShowCreateWebhookModal(true)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type TWorkspaceWebhookDetailsSettingsContentProps = {
|
||||
webhookId: string;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
function WorkspaceWebhookDetailsSettingsContent({ webhookId, workspaceSlug }: TWorkspaceWebhookDetailsSettingsContentProps) {
|
||||
const [deleteWebhookModal, setDeleteWebhookModal] = useState(false);
|
||||
const { currentWebhook, fetchWebhookById, updateWebhook } = useWebhook();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
|
||||
useSWR(
|
||||
isAdmin ? `WEBHOOK_DETAILS_${workspaceSlug}_${webhookId}` : null,
|
||||
isAdmin ? () => fetchWebhookById(workspaceSlug, webhookId) : null
|
||||
);
|
||||
|
||||
const handleUpdateWebhook = async (formData: IWebhook) => {
|
||||
if (!formData || !formData.id) return;
|
||||
|
||||
const payload = {
|
||||
url: formData.url,
|
||||
is_active: formData.is_active,
|
||||
project: formData.project,
|
||||
cycle: formData.cycle,
|
||||
module: formData.module,
|
||||
issue: formData.issue,
|
||||
issue_comment: formData.issue_comment,
|
||||
};
|
||||
|
||||
try {
|
||||
await updateWebhook(workspaceSlug, formData.id, payload);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Webhook updated successfully.",
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: error?.error ?? "Something went wrong. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (!isAdmin) {
|
||||
return (
|
||||
<div className="mt-10 flex h-full w-full justify-center p-4">
|
||||
<p className="text-13 text-tertiary">You are not authorized to access this page.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentWebhook)
|
||||
return (
|
||||
<div className="grid h-full w-full place-items-center p-4">
|
||||
<LogoSpinner />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteWebhookModal isOpen={deleteWebhookModal} onClose={() => setDeleteWebhookModal(false)} />
|
||||
<div className="w-full space-y-8 overflow-y-auto">
|
||||
<WebhookForm onSubmit={handleUpdateWebhook} data={currentWebhook} />
|
||||
{currentWebhook && <WebhookDeleteSection openDeleteModal={() => setDeleteWebhookModal(true)} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { X } from "lucide-react";
|
||||
import {
|
||||
EUserPermissionsLevel,
|
||||
GROUPED_WORKSPACE_SETTINGS,
|
||||
WORKSPACE_SETTINGS,
|
||||
WORKSPACE_SETTINGS_CATEGORIES,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
@@ -23,7 +24,10 @@ import { SettingsSidebarItem } from "@/components/settings/sidebar/item";
|
||||
import { WORKSPACE_SETTINGS_ICONS } from "@/components/settings/workspace/sidebar/item-icon";
|
||||
import { WorkspaceSettingsSidebarHeader } from "@/components/settings/workspace/sidebar/header";
|
||||
import { AIVoiceTaskerSettingsContent } from "@/components/workspace/settings/ai-voice-tasker-settings";
|
||||
import { WorkspaceExportsSettingsContent } from "@/components/workspace/settings/exports-settings";
|
||||
import { WorkspaceMembersSettingsContent } from "@/components/workspace/settings/members-settings";
|
||||
import { StorageSettingsContent } from "@/components/workspace/settings/storage-settings";
|
||||
import { WorkspaceWebhooksSettingsContent } from "@/components/workspace/settings/webhooks-settings";
|
||||
import { WorkspaceDetails } from "@/components/workspace/settings/workspace-details";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
@@ -32,13 +36,14 @@ import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
import {
|
||||
closeWorkspaceSettingsModal,
|
||||
getWorkspaceSettingsModalTabFromSearch,
|
||||
getWorkspaceSettingsWebhookIdFromSearch,
|
||||
openWorkspaceSettingsModal,
|
||||
WORKSPACE_SETTINGS_MODAL_EVENT,
|
||||
type TWorkspaceSettingsModalTab,
|
||||
} from "./workspace-settings-modal.utils";
|
||||
|
||||
const HIDDEN_WORKSPACE_SETTINGS_KEYS = new Set<TWorkspaceSettingsTabs>(["billing-and-plans"]);
|
||||
const MODAL_TABS = new Set<TWorkspaceSettingsTabs>(["general", "storage", "ai-voice-tasker"]);
|
||||
const MODAL_TABS = new Set<TWorkspaceSettingsTabs>(["general", "members", "export", "storage", "webhooks", "ai-voice-tasker"]);
|
||||
|
||||
const getInitialTab = (): TWorkspaceSettingsModalTab => {
|
||||
if (typeof window === "undefined") return "general";
|
||||
@@ -54,16 +59,21 @@ const getInitialOpenState = () => {
|
||||
|
||||
export const WorkspaceSettingsModal = observer(function WorkspaceSettingsModal() {
|
||||
const [activeTab, setActiveTab] = useState<TWorkspaceSettingsModalTab>(getInitialTab);
|
||||
const [activeWebhookId, setActiveWebhookId] = useState<string | undefined>(() =>
|
||||
typeof window === "undefined" ? undefined : getWorkspaceSettingsWebhookIdFromSearch(window.location.search)
|
||||
);
|
||||
const [isOpen, setIsOpen] = useState(getInitialOpenState);
|
||||
// store hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const syncFromLocation = () => {
|
||||
const tab = getWorkspaceSettingsModalTabFromSearch(window.location.search);
|
||||
setIsOpen(Boolean(tab));
|
||||
if (tab) setActiveTab(tab);
|
||||
setActiveWebhookId(tab === "webhooks" ? getWorkspaceSettingsWebhookIdFromSearch(window.location.search) : undefined);
|
||||
};
|
||||
|
||||
const handleModalEvent = (event: Event) => {
|
||||
@@ -71,6 +81,9 @@ export const WorkspaceSettingsModal = observer(function WorkspaceSettingsModal()
|
||||
|
||||
setIsOpen(detail.isOpen);
|
||||
if (detail.tab) setActiveTab(detail.tab);
|
||||
setActiveWebhookId(
|
||||
detail.tab === "webhooks" ? getWorkspaceSettingsWebhookIdFromSearch(window.location.search) : undefined
|
||||
);
|
||||
};
|
||||
|
||||
window.addEventListener(WORKSPACE_SETTINGS_MODAL_EVENT, handleModalEvent);
|
||||
@@ -104,15 +117,28 @@ export const WorkspaceSettingsModal = observer(function WorkspaceSettingsModal()
|
||||
return <AIVoiceTaskerSettingsContent workspaceSlug={currentWorkspace.slug} />;
|
||||
}
|
||||
|
||||
if (activeTab === "members" && currentWorkspace?.slug) {
|
||||
return <WorkspaceMembersSettingsContent workspaceSlug={currentWorkspace.slug} />;
|
||||
}
|
||||
|
||||
if (activeTab === "export") {
|
||||
return <WorkspaceExportsSettingsContent />;
|
||||
}
|
||||
|
||||
if (activeTab === "storage" && currentWorkspace?.slug) {
|
||||
return <StorageSettingsContent workspaceSlug={currentWorkspace.slug} />;
|
||||
}
|
||||
|
||||
if (activeTab === "webhooks" && currentWorkspace?.slug) {
|
||||
return <WorkspaceWebhooksSettingsContent selectedWebhookId={activeWebhookId} workspaceSlug={currentWorkspace.slug} />;
|
||||
}
|
||||
|
||||
return <WorkspaceDetails />;
|
||||
};
|
||||
|
||||
const activeTabLabel =
|
||||
activeTab === "ai-voice-tasker" ? "AI / Voice Tasker" : activeTab === "storage" ? "хранилище" : "основные параметры";
|
||||
const activeTabLabel = WORKSPACE_SETTINGS[activeTab]?.i18n_label
|
||||
? t(WORKSPACE_SETTINGS[activeTab].i18n_label)
|
||||
: "основные параметры";
|
||||
|
||||
return (
|
||||
<ModalCore
|
||||
|
||||
+39
-5
@@ -1,7 +1,9 @@
|
||||
export const WORKSPACE_SETTINGS_MODAL_QUERY_KEY = "workspaceSettings";
|
||||
export const WORKSPACE_SETTINGS_MODAL_EVENT = "nodedc:workspace-settings-modal";
|
||||
|
||||
export type TWorkspaceSettingsModalTab = "general" | "storage" | "ai-voice-tasker";
|
||||
export const WORKSPACE_SETTINGS_WEBHOOK_QUERY_KEY = "webhookId";
|
||||
|
||||
export type TWorkspaceSettingsModalTab = "general" | "members" | "export" | "storage" | "webhooks" | "ai-voice-tasker";
|
||||
|
||||
type TWorkspaceSettingsModalEventDetail = {
|
||||
isOpen: boolean;
|
||||
@@ -15,16 +17,39 @@ const dispatchWorkspaceSettingsModalEvent = (detail: TWorkspaceSettingsModalEven
|
||||
export const getWorkspaceSettingsModalTabFromSearch = (search: string): TWorkspaceSettingsModalTab | undefined => {
|
||||
const value = new URLSearchParams(search).get(WORKSPACE_SETTINGS_MODAL_QUERY_KEY);
|
||||
|
||||
if (value === "general" || value === "storage" || value === "ai-voice-tasker") return value;
|
||||
if (
|
||||
value === "general" ||
|
||||
value === "members" ||
|
||||
value === "export" ||
|
||||
value === "storage" ||
|
||||
value === "webhooks" ||
|
||||
value === "ai-voice-tasker"
|
||||
)
|
||||
return value;
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const setWorkspaceSettingsModalSearch = (tab: TWorkspaceSettingsModalTab, replace = false) => {
|
||||
export const getWorkspaceSettingsWebhookIdFromSearch = (search: string): string | undefined => {
|
||||
const value = new URLSearchParams(search).get(WORKSPACE_SETTINGS_WEBHOOK_QUERY_KEY);
|
||||
|
||||
return value || undefined;
|
||||
};
|
||||
|
||||
export const setWorkspaceSettingsModalSearch = (
|
||||
tab: TWorkspaceSettingsModalTab,
|
||||
replace = false,
|
||||
options?: { webhookId?: string }
|
||||
) => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set(WORKSPACE_SETTINGS_MODAL_QUERY_KEY, tab);
|
||||
if (tab === "webhooks" && options?.webhookId) {
|
||||
url.searchParams.set(WORKSPACE_SETTINGS_WEBHOOK_QUERY_KEY, options.webhookId);
|
||||
} else {
|
||||
url.searchParams.delete(WORKSPACE_SETTINGS_WEBHOOK_QUERY_KEY);
|
||||
}
|
||||
|
||||
window.history[replace ? "replaceState" : "pushState"](window.history.state, "", url);
|
||||
};
|
||||
@@ -34,17 +59,26 @@ export const clearWorkspaceSettingsModalSearch = () => {
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete(WORKSPACE_SETTINGS_MODAL_QUERY_KEY);
|
||||
url.searchParams.delete(WORKSPACE_SETTINGS_WEBHOOK_QUERY_KEY);
|
||||
|
||||
window.history.replaceState(window.history.state, "", url);
|
||||
};
|
||||
|
||||
export const openWorkspaceSettingsModal = (tab: TWorkspaceSettingsModalTab = "general", replace = false) => {
|
||||
export const openWorkspaceSettingsModal = (
|
||||
tab: TWorkspaceSettingsModalTab = "general",
|
||||
replace = false,
|
||||
options?: { webhookId?: string }
|
||||
) => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
setWorkspaceSettingsModalSearch(tab, replace);
|
||||
setWorkspaceSettingsModalSearch(tab, replace, options);
|
||||
dispatchWorkspaceSettingsModalEvent({ isOpen: true, tab });
|
||||
};
|
||||
|
||||
export const openWorkspaceWebhookSettingsModal = (webhookId: string, replace = false) => {
|
||||
openWorkspaceSettingsModal("webhooks", replace, { webhookId });
|
||||
};
|
||||
|
||||
export const closeWorkspaceSettingsModal = () => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user