[WEB-7182] fix: remove profile preferences activity (#9025)
This commit is contained in:
committed by
GitHub
parent
208f35964b
commit
4ca6d6c7b8
@@ -1,194 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import useSWR from "swr";
|
||||
// icons
|
||||
import { History, MessageSquare } from "lucide-react";
|
||||
import { calculateTimeAgo, getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { ActivityIcon, ActivityMessage } from "@/components/core/activity";
|
||||
import { RichTextEditor } from "@/components/editor/rich-text";
|
||||
import { ActivitySettingsLoader } from "@/components/ui/loader/settings/activity";
|
||||
// constants
|
||||
import { USER_ACTIVITY } from "@/constants/fetch-keys";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
// services
|
||||
import { UserService } from "@/services/user.service";
|
||||
const userService = new UserService();
|
||||
|
||||
type Props = {
|
||||
cursor: string;
|
||||
perPage: number;
|
||||
updateResultsCount: (count: number) => void;
|
||||
updateTotalPages: (count: number) => void;
|
||||
updateEmptyState: (state: boolean) => void;
|
||||
};
|
||||
|
||||
export const ProfileActivityListPage = observer(function ProfileActivityListPage(props: Props) {
|
||||
const { cursor, perPage, updateResultsCount, updateTotalPages, updateEmptyState } = props;
|
||||
// store hooks
|
||||
const { data: currentUser } = useUser();
|
||||
|
||||
const { data: userProfileActivity } = useSWR(
|
||||
USER_ACTIVITY({
|
||||
cursor,
|
||||
}),
|
||||
() =>
|
||||
userService.getUserActivity({
|
||||
cursor,
|
||||
per_page: perPage,
|
||||
})
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userProfileActivity) return;
|
||||
|
||||
// if no results found then show empty state
|
||||
if (userProfileActivity.total_results === 0) updateEmptyState(true);
|
||||
|
||||
updateTotalPages(userProfileActivity.total_pages);
|
||||
updateResultsCount(userProfileActivity.results.length);
|
||||
}, [updateResultsCount, updateTotalPages, userProfileActivity, updateEmptyState]);
|
||||
|
||||
// TODO: refactor this component
|
||||
return (
|
||||
<>
|
||||
{userProfileActivity ? (
|
||||
<ul role="list">
|
||||
{userProfileActivity.results.map((activityItem: any) => {
|
||||
if (activityItem.field === "comment")
|
||||
return (
|
||||
<div key={activityItem.id} className="mt-2">
|
||||
<div className="relative flex items-start space-x-3">
|
||||
<div className="relative px-1">
|
||||
{activityItem.field ? (
|
||||
activityItem.new_value === "restore" && <History className="h-3.5 w-3.5 text-secondary" />
|
||||
) : activityItem.actor_detail.avatar_url && activityItem.actor_detail.avatar_url !== "" ? (
|
||||
<img
|
||||
src={getFileURL(activityItem.actor_detail.avatar_url)}
|
||||
alt={activityItem.actor_detail.display_name}
|
||||
height={30}
|
||||
width={30}
|
||||
className="grid h-7 w-7 place-items-center rounded-full border-2 border-subtle-1 bg-layer-3"
|
||||
/>
|
||||
) : (
|
||||
<div className="grid h-7 w-7 place-items-center rounded-full border-2 border-subtle-1 bg-layer-3 capitalize">
|
||||
{activityItem.actor_detail.display_name?.[0]}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-layer-3 p-2 text-secondary">
|
||||
<MessageSquare className="!text-20 text-secondary" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div>
|
||||
<div className="text-11">
|
||||
{activityItem.actor_detail.is_bot
|
||||
? activityItem.actor_detail.first_name + " Bot"
|
||||
: activityItem.actor_detail.display_name}
|
||||
</div>
|
||||
<p className="mt-0.5 text-11 text-secondary">
|
||||
Commented {calculateTimeAgo(activityItem.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="issue-comments-section p-0">
|
||||
<RichTextEditor
|
||||
editable={false}
|
||||
id={activityItem.id}
|
||||
initialValue={
|
||||
activityItem?.new_value !== "" ? activityItem.new_value : activityItem.old_value
|
||||
}
|
||||
containerClassName="text-11 bg-surface-1"
|
||||
workspaceId={activityItem?.workspace_detail?.id?.toString() ?? ""}
|
||||
workspaceSlug={activityItem?.workspace_detail?.slug?.toString() ?? ""}
|
||||
projectId={activityItem.project ?? ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const message = <ActivityMessage activity={activityItem} showIssue />;
|
||||
|
||||
if ("field" in activityItem && activityItem.field !== "updated_by")
|
||||
return (
|
||||
<li key={activityItem.id}>
|
||||
<div className="relative pb-1">
|
||||
<div className="relative flex items-start space-x-2">
|
||||
<>
|
||||
<div>
|
||||
<div className="relative mt-4 px-1.5">
|
||||
<div className="mt-1.5">
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-lg border border-subtle shadow-raised-100">
|
||||
{activityItem.field ? (
|
||||
activityItem.new_value === "restore" ? (
|
||||
<History className="h-5 w-5 text-secondary" />
|
||||
) : (
|
||||
<ActivityIcon activity={activityItem} />
|
||||
)
|
||||
) : activityItem.actor_detail.avatar_url &&
|
||||
activityItem.actor_detail.avatar_url !== "" ? (
|
||||
<img
|
||||
src={getFileURL(activityItem.actor_detail.avatar_url)}
|
||||
alt={activityItem.actor_detail.display_name}
|
||||
height={24}
|
||||
width={24}
|
||||
className="h-full w-full rounded-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="grid h-6 w-6 place-items-center rounded-full border-2 border-subtle-1 bg-layer-3 text-11 capitalize">
|
||||
{activityItem.actor_detail.display_name?.[0]}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 border-b border-subtle py-4">
|
||||
<div className="text-caption-md-regular break-words text-secondary">
|
||||
{activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? (
|
||||
<span className="text-gray font-medium">Plane</span>
|
||||
) : activityItem.actor_detail.is_bot ? (
|
||||
<span className="text-gray font-medium">{activityItem.actor_detail.first_name} Bot</span>
|
||||
) : (
|
||||
<Link
|
||||
href={`/${activityItem.workspace_detail.slug}/profile/${activityItem.actor_detail.id}`}
|
||||
className="inline"
|
||||
>
|
||||
<span className="text-gray font-medium">
|
||||
{currentUser?.id === activityItem.actor_detail.id
|
||||
? "You"
|
||||
: activityItem.actor_detail.display_name}
|
||||
</span>
|
||||
</Link>
|
||||
)}{" "}
|
||||
<div className="inline gap-1">
|
||||
{message}{" "}
|
||||
<span className="flex-shrink-0 whitespace-nowrap">
|
||||
{calculateTimeAgo(activityItem.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
) : (
|
||||
<ActivitySettingsLoader />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -1,194 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import useSWR from "swr";
|
||||
// icons
|
||||
import { History, MessageSquare } from "lucide-react";
|
||||
import { calculateTimeAgo, getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { ActivityIcon, ActivityMessage } from "@/components/core/activity";
|
||||
import { RichTextEditor } from "@/components/editor/rich-text";
|
||||
import { ActivitySettingsLoader } from "@/components/ui/loader/settings/activity";
|
||||
// constants
|
||||
import { USER_ACTIVITY } from "@/constants/fetch-keys";
|
||||
// hooks
|
||||
import { useUserProfile } from "@/hooks/store/user/user-user-profile";
|
||||
// services
|
||||
import { UserService } from "@/services/user.service";
|
||||
const userService = new UserService();
|
||||
|
||||
type Props = {
|
||||
cursor: string;
|
||||
perPage: number;
|
||||
updateResultsCount: (count: number) => void;
|
||||
updateTotalPages: (count: number) => void;
|
||||
updateEmptyState: (state: boolean) => void;
|
||||
};
|
||||
|
||||
export const ActivityProfileSettingsList = observer(function ProfileActivityListPage(props: Props) {
|
||||
const { cursor, perPage, updateResultsCount, updateTotalPages, updateEmptyState } = props;
|
||||
// store hooks
|
||||
const { data: currentUser } = useUserProfile();
|
||||
|
||||
const { data: userProfileActivity } = useSWR(
|
||||
USER_ACTIVITY({
|
||||
cursor,
|
||||
}),
|
||||
() =>
|
||||
userService.getUserActivity({
|
||||
cursor,
|
||||
per_page: perPage,
|
||||
})
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userProfileActivity) return;
|
||||
|
||||
// if no results found then show empty state
|
||||
if (userProfileActivity.total_results === 0) updateEmptyState(true);
|
||||
|
||||
updateTotalPages(userProfileActivity.total_pages);
|
||||
updateResultsCount(userProfileActivity.results.length);
|
||||
}, [updateResultsCount, updateTotalPages, userProfileActivity, updateEmptyState]);
|
||||
|
||||
// TODO: refactor this component
|
||||
return (
|
||||
<>
|
||||
{userProfileActivity ? (
|
||||
<ul>
|
||||
{userProfileActivity.results.map((activityItem: any) => {
|
||||
if (activityItem.field === "comment")
|
||||
return (
|
||||
<div key={activityItem.id} className="mt-2">
|
||||
<div className="relative flex items-start space-x-3">
|
||||
<div className="relative px-1">
|
||||
{activityItem.field ? (
|
||||
activityItem.new_value === "restore" && <History className="h-3.5 w-3.5 text-secondary" />
|
||||
) : activityItem.actor_detail.avatar_url && activityItem.actor_detail.avatar_url !== "" ? (
|
||||
<img
|
||||
src={getFileURL(activityItem.actor_detail.avatar_url)}
|
||||
alt={activityItem.actor_detail.display_name}
|
||||
height={30}
|
||||
width={30}
|
||||
className="grid h-7 w-7 place-items-center rounded-full border-2 border-subtle-1 bg-layer-3"
|
||||
/>
|
||||
) : (
|
||||
<div className="grid h-7 w-7 place-items-center rounded-full border-2 border-subtle-1 bg-layer-3 capitalize">
|
||||
{activityItem.actor_detail.display_name?.[0]}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-layer-3 p-2 text-secondary">
|
||||
<MessageSquare className="!text-20 text-secondary" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div>
|
||||
<div className="text-11">
|
||||
{activityItem.actor_detail.is_bot
|
||||
? activityItem.actor_detail.first_name + " Bot"
|
||||
: activityItem.actor_detail.display_name}
|
||||
</div>
|
||||
<p className="mt-0.5 text-11 text-secondary">
|
||||
Commented {calculateTimeAgo(activityItem.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="issue-comments-section p-0">
|
||||
<RichTextEditor
|
||||
editable={false}
|
||||
id={activityItem.id}
|
||||
initialValue={
|
||||
activityItem?.new_value !== "" ? activityItem.new_value : activityItem.old_value
|
||||
}
|
||||
containerClassName="text-11 bg-surface-1"
|
||||
workspaceId={activityItem?.workspace_detail?.id?.toString() ?? ""}
|
||||
workspaceSlug={activityItem?.workspace_detail?.slug?.toString() ?? ""}
|
||||
projectId={activityItem.project ?? ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const message = <ActivityMessage activity={activityItem} showIssue />;
|
||||
|
||||
if ("field" in activityItem && activityItem.field !== "updated_by")
|
||||
return (
|
||||
<li key={activityItem.id}>
|
||||
<div className="relative pb-1">
|
||||
<div className="relative flex items-start space-x-2">
|
||||
<>
|
||||
<div>
|
||||
<div className="relative mt-4 px-1.5">
|
||||
<div className="mt-1.5">
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-lg border border-subtle shadow-raised-100">
|
||||
{activityItem.field ? (
|
||||
activityItem.new_value === "restore" ? (
|
||||
<History className="h-5 w-5 text-secondary" />
|
||||
) : (
|
||||
<ActivityIcon activity={activityItem} />
|
||||
)
|
||||
) : activityItem.actor_detail.avatar_url &&
|
||||
activityItem.actor_detail.avatar_url !== "" ? (
|
||||
<img
|
||||
src={getFileURL(activityItem.actor_detail.avatar_url)}
|
||||
alt={activityItem.actor_detail.display_name}
|
||||
height={24}
|
||||
width={24}
|
||||
className="h-full w-full rounded-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="grid h-6 w-6 place-items-center rounded-full border-2 border-subtle-1 bg-layer-3 text-11 capitalize">
|
||||
{activityItem.actor_detail.display_name?.[0]}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 border-b border-subtle py-4">
|
||||
<div className="text-caption-md-regular break-words text-secondary">
|
||||
{activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? (
|
||||
<span className="text-gray font-medium">Plane</span>
|
||||
) : activityItem.actor_detail.is_bot ? (
|
||||
<span className="text-gray font-medium">{activityItem.actor_detail.first_name} Bot</span>
|
||||
) : (
|
||||
<Link
|
||||
href={`/${activityItem.workspace_detail.slug}/profile/${activityItem.actor_detail.id}`}
|
||||
className="inline"
|
||||
>
|
||||
<span className="text-gray font-medium">
|
||||
{currentUser?.id === activityItem.actor_detail.id
|
||||
? "You"
|
||||
: activityItem.actor_detail.display_name}
|
||||
</span>
|
||||
</Link>
|
||||
)}{" "}
|
||||
<div className="inline gap-1">
|
||||
{message}{" "}
|
||||
<span className="flex-shrink-0 whitespace-nowrap">
|
||||
{calculateTimeAgo(activityItem.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
) : (
|
||||
<ActivitySettingsLoader />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export * from "./root";
|
||||
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
* 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";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTheme } from "next-themes";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
// assets
|
||||
import darkActivityAsset from "@/app/assets/empty-state/profile/activity-dark.webp?url";
|
||||
import lightActivityAsset from "@/app/assets/empty-state/profile/activity-light.webp?url";
|
||||
// components
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { ProfileSettingsHeading } from "@/components/settings/profile/heading";
|
||||
// local imports
|
||||
import { ActivityProfileSettingsList } from "./activity-list";
|
||||
|
||||
const PER_PAGE = 100;
|
||||
|
||||
export const ActivityProfileSettings = observer(function ActivityProfileSettings() {
|
||||
// states
|
||||
const [pageCount, setPageCount] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(0);
|
||||
const [resultsCount, setResultsCount] = useState(0);
|
||||
const [isEmpty, setIsEmpty] = useState(false);
|
||||
// theme hook
|
||||
const { resolvedTheme } = useTheme();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const resolvedPath = resolvedTheme === "light" ? lightActivityAsset : darkActivityAsset;
|
||||
|
||||
const updateTotalPages = (count: number) => setTotalPages(count);
|
||||
|
||||
const updateResultsCount = (count: number) => setResultsCount(count);
|
||||
|
||||
const updateEmptyState = (isEmpty: boolean) => setIsEmpty(isEmpty);
|
||||
|
||||
const handleLoadMore = () => setPageCount((prev) => prev + 1);
|
||||
|
||||
const activityPages: React.ReactNode[] = [];
|
||||
for (let i = 0; i < pageCount; i++)
|
||||
activityPages.push(
|
||||
<ActivityProfileSettingsList
|
||||
key={i}
|
||||
cursor={`${PER_PAGE}:${i}:0`}
|
||||
perPage={PER_PAGE}
|
||||
updateResultsCount={updateResultsCount}
|
||||
updateTotalPages={updateTotalPages}
|
||||
updateEmptyState={updateEmptyState}
|
||||
/>
|
||||
);
|
||||
|
||||
const isLoadMoreVisible = pageCount < totalPages && resultsCount !== 0;
|
||||
|
||||
if (isEmpty) {
|
||||
return (
|
||||
<div className="flex size-full flex-col gap-y-7">
|
||||
<ProfileSettingsHeading
|
||||
title={t("account_settings.activity.heading")}
|
||||
description={t("account_settings.activity.description")}
|
||||
/>
|
||||
<DetailedEmptyState
|
||||
title={""}
|
||||
description={""}
|
||||
assetPath={resolvedPath}
|
||||
className="mx-auto min-h-fit w-full justify-center p-0!"
|
||||
size="base"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="size-full">
|
||||
<ProfileSettingsHeading
|
||||
title={t("account_settings.activity.heading")}
|
||||
description={t("account_settings.activity.description")}
|
||||
/>
|
||||
<div className="mt-7 w-full">{activityPages}</div>
|
||||
{isLoadMoreVisible && (
|
||||
<div className="mt-4 flex w-full items-center justify-center">
|
||||
<Button variant="ghost" onClick={handleLoadMore} appendIcon={<ChevronDown />}>
|
||||
{t("load_more")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -13,6 +13,5 @@ export const PROFILE_SETTINGS_PAGES_MAP: Record<TProfileSettingsTabs, React.Lazy
|
||||
preferences: lazy(() => import("./preferences").then((m) => ({ default: m.PreferencesProfileSettings }))),
|
||||
notifications: lazy(() => import("./notifications").then((m) => ({ default: m.NotificationsProfileSettings }))),
|
||||
security: lazy(() => import("./security").then((m) => ({ default: m.SecurityProfileSettings }))),
|
||||
activity: lazy(() => import("./activity").then((m) => ({ default: m.ActivityProfileSettings }))),
|
||||
"api-tokens": lazy(() => import("./api-tokens").then((m) => ({ default: m.APITokensProfileSettings }))),
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import type React from "react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { Activity, Bell, CircleUser, KeyRound, LockIcon, Settings2 } from "lucide-react";
|
||||
import { Bell, CircleUser, KeyRound, LockIcon, Settings2 } from "lucide-react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "react-router";
|
||||
// plane imports
|
||||
@@ -25,7 +25,6 @@ import { ProfileSettingsSidebarWorkspaceOptions } from "./workspace-options";
|
||||
const ICONS: Record<TProfileSettingsTabs, LucideIcon | React.FC<ISvgIcons>> = {
|
||||
general: CircleUser,
|
||||
security: LockIcon,
|
||||
activity: Activity,
|
||||
preferences: Settings2,
|
||||
notifications: Bell,
|
||||
"api-tokens": KeyRound,
|
||||
|
||||
@@ -145,14 +145,6 @@ export class UserService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async getUserActivity(params: { per_page: number; cursor?: string }): Promise<IUserActivityResponse> {
|
||||
return this.get("/api/users/me/activities/", { params })
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async changePassword(token: string, data: { old_password?: string; new_password: string }): Promise<any> {
|
||||
return this.post(`/auth/change-password/`, data, {
|
||||
headers: {
|
||||
|
||||
@@ -37,10 +37,6 @@ export const PROFILE_SETTINGS: Record<
|
||||
key: "security",
|
||||
i18n_label: "profile.actions.security",
|
||||
},
|
||||
activity: {
|
||||
key: "activity",
|
||||
i18n_label: "profile.actions.activity",
|
||||
},
|
||||
preferences: {
|
||||
key: "preferences",
|
||||
i18n_label: "profile.actions.preferences",
|
||||
@@ -66,7 +62,6 @@ export const GROUPED_PROFILE_SETTINGS: Record<
|
||||
PROFILE_SETTINGS["preferences"],
|
||||
PROFILE_SETTINGS["notifications"],
|
||||
PROFILE_SETTINGS["security"],
|
||||
PROFILE_SETTINGS["activity"],
|
||||
],
|
||||
[PROFILE_SETTINGS_CATEGORY.DEVELOPER]: [PROFILE_SETTINGS["api-tokens"]],
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import type { EUserProjectRoles } from ".";
|
||||
import type { EUserWorkspaceRoles } from "./workspace";
|
||||
|
||||
export type TProfileSettingsTabs = "general" | "preferences" | "activity" | "notifications" | "security" | "api-tokens";
|
||||
export type TProfileSettingsTabs = "general" | "preferences" | "notifications" | "security" | "api-tokens";
|
||||
|
||||
export type TWorkspaceSettingsTabs = "general" | "members" | "billing-and-plans" | "export" | "webhooks";
|
||||
export type TWorkspaceSettingsItem = {
|
||||
|
||||
Reference in New Issue
Block a user