diff --git a/web/src/apps/console/components/common-console-components.tsx b/web/src/apps/console/components/common-console-components.tsx
index 86979d4ef..7ea1a4404 100644
--- a/web/src/apps/console/components/common-console-components.tsx
+++ b/web/src/apps/console/components/common-console-components.tsx
@@ -1,27 +1,36 @@
import { ReactNode, useState } from 'react';
import { Button } from '~/components/atoms/button';
+import TooltipV2 from '~/components/atoms/tooltipV2';
+import { toast } from '~/components/molecule/toast';
import { cn } from '~/components/utils';
+import { Check, Copy } from '~/console/components/icons';
import useClipboard from '~/root/lib/client/hooks/use-clipboard';
-import { toast } from '~/components/molecule/toast';
-import { Copy, Check } from '~/console/components/icons';
import { Truncate } from '~/root/lib/utils/common';
-import TooltipV2 from '~/components/atoms/tooltipV2';
interface IDeleteContainer {
title: ReactNode;
children: ReactNode;
action: () => void;
+ content?: string;
+ disabled?: boolean;
}
export const DeleteContainer = ({
title,
children,
action,
+ content,
+ disabled = false,
}: IDeleteContainer) => {
return (
);
};
@@ -48,7 +57,7 @@ export const Box = ({ children, title, className }: IBox) => {
{title}
diff --git a/web/src/apps/console/routes/_main+/$account+/settings+/_layout.tsx b/web/src/apps/console/routes/_main+/$account+/settings+/_layout.tsx
index cbbcbbcc1..6f849185b 100644
--- a/web/src/apps/console/routes/_main+/$account+/settings+/_layout.tsx
+++ b/web/src/apps/console/routes/_main+/$account+/settings+/_layout.tsx
@@ -1,11 +1,18 @@
-import { Outlet, useOutletContext } from '@remix-run/react';
+import { Outlet, useLoaderData, useOutletContext } from '@remix-run/react';
import SidebarLayout from '~/console/components/sidebar-layout';
+import { GQLServerHandler } from '~/console/server/gql/saved-queries';
+import { ensureAccountSet } from '~/console/server/utils/auth-utils';
import { useHandleFromMatches } from '~/root/lib/client/hooks/use-custom-matches';
+import { IExtRemixCtx, LoaderResult } from '~/root/lib/types/common';
+import { IAccountContext } from '../_layout';
const Settings = () => {
- const rootContext = useOutletContext();
+ // const rootContext = useOutletContext();
+ const rootContext = useOutletContext
();
const noLayout = useHandleFromMatches('noLayout', null);
+ const { teamMembers, currentUser } = useLoaderData();
+
if (noLayout) {
return ;
}
@@ -20,12 +27,49 @@ const Settings = () => {
// { label: 'VPN', value: 'vpn' },
]}
parentPath="/settings"
- // headerTitle="Settings"
- // headerActions={subNavAction.data}
+ // headerTitle="Settings"
+ // headerActions={subNavAction.data}
>
-
+
);
};
+export const loader = async (ctx: IExtRemixCtx) => {
+ const { account } = ctx.params;
+ ensureAccountSet(ctx);
+ const { data, errors } = await GQLServerHandler(
+ ctx.request
+ ).listMembershipsForAccount({
+ accountName: account,
+ });
+ if (errors) {
+ throw errors[0];
+ }
+
+ const { data: currentUser, errors: cErrors } = await GQLServerHandler(
+ ctx.request
+ ).whoAmI({});
+
+ if (cErrors) {
+ throw cErrors[0];
+ }
+
+ return {
+ teamMembers: data,
+ currentUser,
+ };
+};
+
+export interface ISettingsContext extends IAccountContext {
+ teamMembers: LoaderResult['teamMembers'];
+ currentUser: LoaderResult['currentUser'];
+}
+
export default Settings;
diff --git a/web/src/apps/console/routes/_main+/$account+/settings+/general.tsx b/web/src/apps/console/routes/_main+/$account+/settings+/general.tsx
index 5be042c33..c1b17df4e 100644
--- a/web/src/apps/console/routes/_main+/$account+/settings+/general.tsx
+++ b/web/src/apps/console/routes/_main+/$account+/settings+/general.tsx
@@ -1,29 +1,30 @@
-import { Buildings, CopySimple } from '~/console/components/icons';
import { useNavigate, useOutletContext } from '@remix-run/react';
-import { useEffect, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
import { Avatar } from '~/components/atoms/avatar';
import { Button } from '~/components/atoms/button';
import { TextInput } from '~/components/atoms/input';
import { toast } from '~/components/molecule/toast';
+import { Buildings, CopySimple } from '~/console/components/icons';
+import { parseName } from '~/console/server/r-utils/common';
import useClipboard from '~/root/lib/client/hooks/use-clipboard';
import useForm from '~/root/lib/client/hooks/use-form';
import { useUnsavedChanges } from '~/root/lib/client/hooks/use-unsaved-changes';
import { consoleBaseUrl } from '~/root/lib/configs/base-url.cjs';
import Yup from '~/root/lib/server/helpers/yup';
import { handleError } from '~/root/lib/utils/common';
-import { parseName } from '~/console/server/r-utils/common';
-import SecondarySubHeader from '~/console/components/secondary-sub-header';
-import { useConsoleApi } from '~/console/server/gql/api-provider';
-import { ConsoleApiType } from '~/console/server/gql/saved-queries';
import {
Box,
DeleteContainer,
} from '~/console/components/common-console-components';
-import { IAccount } from '~/console/server/gql/queries/account-queries';
import DeleteDialog from '~/console/components/delete-dialog';
+import SecondarySubHeader from '~/console/components/secondary-sub-header';
+import { useConsoleApi } from '~/console/server/gql/api-provider';
+import { IAccount } from '~/console/server/gql/queries/account-queries';
+import { ConsoleApiType } from '~/console/server/gql/saved-queries';
import { useReload } from '~/root/lib/client/helpers/reloader';
import { IAccountContext } from '../_layout';
+import { ISettingsContext } from './_layout';
// import SubNavAction from '../components/sub-nav-action';
// import { useConsoleApi } from '../server/gql/api-provider';
// import { IAccount } from '../server/gql/queries/access-queries';
@@ -59,6 +60,7 @@ export const updateAccount = async ({
const SettingGeneral = () => {
const { account } = useOutletContext();
+ const { teamMembers, currentUser } = useOutletContext();
const [deleteAccount, setDeleteAccount] = useState(false);
const { setHasChanges, resetAndReload } = useUnsavedChanges();
@@ -72,6 +74,12 @@ const SettingGeneral = () => {
},
});
+ const isOwner = useMemo(() => {
+ if (!teamMembers || !currentUser) return false;
+ const owner = teamMembers.find((member) => member.role === 'account_owner');
+ return owner?.user?.email === currentUser?.email;
+ }, [teamMembers, currentUser]);
+
const { values, handleChange, submit, isLoading, resetValues } = useForm({
initialValues: {
displayName: account.displayName,
@@ -127,7 +135,7 @@ const SettingGeneral = () => {
@@ -135,6 +143,7 @@ const SettingGeneral = () => {
label="Account name"
value={values.displayName}
onChange={handleChange('displayName')}
+ disabled={!isOwner}
/>
@@ -213,20 +222,40 @@ const SettingGeneral = () => {
{
setDeleteAccount(true);
}}
+ content="Disable"
+ disabled={!isOwner}
>
- Permanently remove your Account and all of its contents from the
- Kloudlite platform. This action is not reversible — please continue
- with caution.
+ For permanent delete or reverse your account and all of its contents
+ from the Kloudlite platform, please contact us at support@kloudlite.io
+ Are you sure you want to disable “{parseName(account)}
+ ”?
+
+ ),
+ prompt: (
+ <>
+ Enter the account name
+
+ {' '}
+ {parseName(account)}{' '}
+
+ to continue:
+ >
+ ),
+ }}
onSubmit={async () => {
try {
const { errors } = await api.deleteAccount({
@@ -237,7 +266,7 @@ const SettingGeneral = () => {
throw errors[0];
}
reload();
- toast.success(`Account deleted successfully`);
+ toast.success(`Account disabled successfully`);
setDeleteAccount(false);
navigate(`/`);
} catch (err) {
diff --git a/web/src/apps/console/routes/_main+/$account+/settings+/user-management/handle-user.tsx b/web/src/apps/console/routes/_main+/$account+/settings+/user-management/handle-user.tsx
index b2ebc1e9e..234c6050a 100644
--- a/web/src/apps/console/routes/_main+/$account+/settings+/user-management/handle-user.tsx
+++ b/web/src/apps/console/routes/_main+/$account+/settings+/user-management/handle-user.tsx
@@ -1,20 +1,18 @@
import { useOutletContext } from '@remix-run/react';
import { TextInput } from '~/components/atoms/input';
-import SelectPrimitive from '~/components/atoms/select-primitive';
import Popup from '~/components/molecule/popup';
import { toast } from '~/components/molecule/toast';
+import CommonPopupHandle from '~/console/components/common-popup-handle';
import { IDialogBase } from '~/console/components/types.d';
+import {
+ IMemberType
+} from '~/console/routes/_main+/$account+/settings+/user-management/user-access-resource';
import { useConsoleApi } from '~/console/server/gql/api-provider';
+import { parseName } from '~/console/server/r-utils/common';
import useForm from '~/root/lib/client/hooks/use-form';
import Yup from '~/root/lib/server/helpers/yup';
import { handleError } from '~/root/lib/utils/common';
import { Github__Com___Kloudlite___Api___Apps___Iam___Types__Role as Role } from '~/root/src/generated/gql/server';
-import { parseName } from '~/console/server/r-utils/common';
-import CommonPopupHandle from '~/console/components/common-popup-handle';
-import {
- IMemberType,
- mapRoleToDisplayName,
-} from '~/console/routes/_main+/$account+/settings+/user-management/user-access-resource';
import { IAccountContext } from '../../_layout';
type IDialog = IDialogBase;
@@ -40,7 +38,8 @@ const Root = (props: IDialog) => {
initialValues: isUpdate
? {
email: props?.data.email || '',
- role: props?.data.role || 'account_member',
+ // role: props?.data.role || 'account_member',
+ role: 'account_member',
}
: {
email: '',
@@ -98,7 +97,7 @@ const Root = (props: IDialog) => {
)}
-
{
);
})}
-
+ */}
diff --git a/web/src/apps/console/routes/_main+/$account+/settings+/user-management/route.tsx b/web/src/apps/console/routes/_main+/$account+/settings+/user-management/route.tsx
index 5a44d1034..1ea3b1a2b 100644
--- a/web/src/apps/console/routes/_main+/$account+/settings+/user-management/route.tsx
+++ b/web/src/apps/console/routes/_main+/$account+/settings+/user-management/route.tsx
@@ -1,22 +1,23 @@
-import { Plus, SmileySad } from '~/console/components/icons';
import { useOutletContext } from '@remix-run/react';
-import { useCallback, useState } from 'react';
+import { motion } from 'framer-motion';
+import { useCallback, useMemo, useState } from 'react';
import { Button } from '~/components/atoms/button';
+import { dayjs } from '~/components/molecule/dayjs';
import Profile from '~/components/molecule/profile';
+import { useSort } from '~/components/utils';
+import { EmptyState } from '~/console/components/empty-state';
import ExtendedFilledTab from '~/console/components/extended-filled-tab';
+import { Plus, SmileySad } from '~/console/components/icons';
import SecondarySubHeader from '~/console/components/secondary-sub-header';
import Wrapper from '~/console/components/wrapper';
import { useConsoleApi } from '~/console/server/gql/api-provider';
-import { useSearch } from '~/root/lib/client/helpers/search-filter';
-import { ExtractArrayType, NonNullableString } from '~/root/lib/types/common';
-import { EmptyState } from '~/console/components/empty-state';
-import useCustomSwr from '~/root/lib/client/hooks/use-custom-swr';
-import { motion } from 'framer-motion';
import { parseName } from '~/console/server/r-utils/common';
-import { useSort } from '~/components/utils';
-import { dayjs } from '~/components/molecule/dayjs';
import Pulsable from '~/root/lib/client/components/pulsable';
+import { useSearch } from '~/root/lib/client/helpers/search-filter';
+import useCustomSwr from '~/root/lib/client/hooks/use-custom-swr';
+import { ExtractArrayType, NonNullableString } from '~/root/lib/types/common';
import { IAccountContext } from '../../_layout';
+import { ISettingsContext } from '../_layout';
import HandleUser from './handle-user';
import Tools from './tools';
import UserAccessResources from './user-access-resource';
@@ -28,6 +29,7 @@ interface ITeams {
sortByProperty: string;
sortByTime: string;
};
+ isOwner?: boolean;
}
const placeHolderUsers = Array(3)
@@ -39,7 +41,12 @@ const placeHolderUsers = Array(3)
email: 'sampleuser@gmail.com',
}));
-const Teams = ({ setShowUserInvite, searchText, sortTeamMembers }: ITeams) => {
+const Teams = ({
+ setShowUserInvite,
+ searchText,
+ sortTeamMembers,
+ isOwner,
+}: ITeams) => {
const { account } = useOutletContext();
const api = useConsoleApi();
const { data: teamMembers, isLoading } = useCustomSwr(
@@ -134,6 +141,7 @@ const Teams = ({ setShowUserInvite, searchText, sortTeamMembers }: ITeams) => {
}))
}
isPendingInvitation={false}
+ isOwner={isOwner || false}
/>
)}
@@ -146,6 +154,7 @@ const Invitations = ({
setShowUserInvite,
searchText,
sortTeamMembers,
+ isOwner,
}: ITeams) => {
const { account } = useOutletContext();
const api = useConsoleApi();
@@ -241,6 +250,7 @@ const Invitations = ({
}))
}
isPendingInvitation
+ isOwner={isOwner || false}
/>
@@ -253,7 +263,8 @@ const SettingUserManagement = () => {
'team' | 'invitations' | NonNullableString
>('team');
const [visible, setVisible] = useState(false);
- const { account } = useOutletContext();
+ // const { account } = useOutletContext();
+ const { teamMembers, currentUser } = useOutletContext();
const [searchText, setSearchText] = useState('');
@@ -262,21 +273,29 @@ const SettingUserManagement = () => {
sortByTime: 'des',
});
- const api = useConsoleApi();
+ // const api = useConsoleApi();
- const { data: teamMembers, isLoading } = useCustomSwr(
- `${parseName(account)}-owners`,
- async () => {
- return api.listMembershipsForAccount({
- accountName: parseName(account),
- });
- }
- );
+ const isOwner = useMemo(() => {
+ if (!teamMembers || !currentUser) return false;
+ const owner = teamMembers.find((member) => member.role === 'account_owner');
+ return owner?.user?.email === currentUser?.email;
+ }, [teamMembers, currentUser]);
+
+ // const { data: teamMembers, isLoading } = useCustomSwr(
+ // `${parseName(account)}-owners`,
+ // async () => {
+ // return api.listMembershipsForAccount({
+ // accountName: parseName(account),
+ // });
+ // }
+ // );
+
+ // const owners = useCallback(
+ // () => teamMembers?.filter((i) => i.role === 'account_owner') || [],
+ // [teamMembers]
+ // )();
- const owners = useCallback(
- () => teamMembers?.filter((i) => i.role === 'account_owner') || [],
- [teamMembers]
- )();
+ const accountOwner = teamMembers?.find((i) => i.role === 'account_owner');
return (
@@ -284,18 +303,30 @@ const SettingUserManagement = () => {
setVisible(true)}
- />
+ isOwner && (
+ setVisible(true)}
+ />
+ )
}
/>
Account owners
-
+
+
+
+
+ {/*
{[
...(isLoading
@@ -318,7 +349,7 @@ const SettingUserManagement = () => {
);
})}
-
+ */}
@@ -328,9 +359,9 @@ const SettingUserManagement = () => {
value={active}
onChange={setActive}
items={[
- { label: 'Team member', to: 'team-member', value: 'team' },
+ { label: 'Team members', to: 'team-member', value: 'team' },
{
- label: 'Pending invitation',
+ label: 'Pending invitations',
to: 'pending-invitation',
value: 'invitations',
},
@@ -348,12 +379,14 @@ const SettingUserManagement = () => {
setShowUserInvite={setVisible}
searchText={searchText}
sortTeamMembers={sortByProperty}
+ isOwner={isOwner}
/>
) : (
)}
diff --git a/web/src/apps/console/routes/_main+/$account+/settings+/user-management/user-access-resource.tsx b/web/src/apps/console/routes/_main+/$account+/settings+/user-management/user-access-resource.tsx
index ebc36c13f..fbaed561f 100644
--- a/web/src/apps/console/routes/_main+/$account+/settings+/user-management/user-access-resource.tsx
+++ b/web/src/apps/console/routes/_main+/$account+/settings+/user-management/user-access-resource.tsx
@@ -1,26 +1,24 @@
-import { PencilSimple, Trash } from '~/console/components/icons';
import { useOutletContext } from '@remix-run/react';
import { useState } from 'react';
import { Avatar } from '~/components/atoms/avatar';
import { toast } from '~/components/molecule/toast';
import { titleCase } from '~/components/utils';
import {
- ListBody,
ListItemV2,
- ListTitle,
ListTitleV2,
} from '~/console/components/console-list-components';
import DeleteDialog from '~/console/components/delete-dialog';
+import { Trash } from '~/console/components/icons';
import List from '~/console/components/list';
import ListGridView from '~/console/components/list-grid-view';
import ResourceExtraAction, {
IResourceExtraItem,
} from '~/console/components/resource-extra-action';
+import HandleUser from '~/console/routes/_main+/$account+/settings+/user-management/handle-user';
import { useConsoleApi } from '~/console/server/gql/api-provider';
+import { parseName } from '~/console/server/r-utils/common';
import { useReload } from '~/root/lib/client/helpers/reloader';
import { handleError } from '~/root/lib/utils/common';
-import { parseName } from '~/console/server/r-utils/common';
-import HandleUser from '~/console/routes/_main+/$account+/settings+/user-management/handle-user';
import { IAccountContext } from '../../_layout';
const RESOURCE_NAME = 'user';
@@ -59,7 +57,7 @@ export const mapRoleToDisplayName = (role: string): string => {
};
const ExtraButton = ({ onAction, item, isInvite }: IExtraButton) => {
- let items: IResourceExtraItem[] = [
+ const items: IResourceExtraItem[] = [
{
label: 'Remove',
icon: ,
@@ -69,18 +67,18 @@ const ExtraButton = ({ onAction, item, isInvite }: IExtraButton) => {
className: '!text-text-critical',
},
];
- if (!isInvite) {
- items = [
- {
- label: 'Edit',
- icon: ,
- type: 'item',
- onClick: () => onAction({ action: 'edit', item }),
- key: 'edit',
- },
- ...items,
- ];
- }
+ // if (!isInvite) {
+ // items = [
+ // {
+ // label: 'Edit',
+ // icon: ,
+ // type: 'item',
+ // onClick: () => onAction({ action: 'edit', item }),
+ // key: 'edit',
+ // },
+ // ...items,
+ // ];
+ // }
return ;
};
@@ -88,9 +86,10 @@ interface IResource {
items: BaseType[];
onAction: OnAction;
isInvite: boolean;
+ isOwner: boolean;
}
-const ListView = ({ items = [], onAction, isInvite }: IResource) => {
+const ListView = ({ items = [], onAction, isInvite, isOwner }: IResource) => {
return (
{items.map((item) => (
@@ -118,13 +117,19 @@ const ListView = ({ items = [], onAction, isInvite }: IResource) => {
},
{
key: 3,
- render: () => (
-
- ),
+ render: () => {
+ if (item.role === 'account_owner') return null;
+ if (isOwner) {
+ return (
+
+ );
+ }
+ return null;
+ },
},
]}
/>
@@ -136,12 +141,14 @@ const ListView = ({ items = [], onAction, isInvite }: IResource) => {
const UserAccessResources = ({
items = [],
isPendingInvitation = false,
+ isOwner,
}: {
items: BaseType[];
isPendingInvitation: boolean;
+ isOwner: boolean;
}) => {
const [showDeleteDialog, setShowDeleteDialog] = useState(
- null,
+ null
);
const [showUserInvite, setShowUserInvite] = useState(null);
@@ -153,6 +160,7 @@ const UserAccessResources = ({
const props: IResource = {
items,
isInvite: isPendingInvitation,
+ isOwner,
onAction: ({ action, item }) => {
switch (action) {
case 'edit':
diff --git a/web/src/apps/console/routes/_main+/_layout/_layout.tsx b/web/src/apps/console/routes/_main+/_layout/_layout.tsx
index 9610b8a58..0f764bca3 100644
--- a/web/src/apps/console/routes/_main+/_layout/_layout.tsx
+++ b/web/src/apps/console/routes/_main+/_layout/_layout.tsx
@@ -65,7 +65,7 @@ export type IConsoleRootContext = {
export const meta = (c: IRemixCtx) => {
return [
- { title: `Account ${constants.metadot} ${c.params?.account || ''}` },
+ { title: `Team ${constants.metadot} ${c.params?.account || ''}` },
{ name: 'theme-color', content: LightTitlebarColor },
];
};
@@ -143,9 +143,9 @@ const AccountTabs = () => {
};
const Logo = () => {
- const { account } = useParams();
+ // const { account } = useParams();
return (
-
+
);
@@ -171,11 +171,10 @@ const ProfileMenu = ({ hideProfileName }: { hideProfileName: boolean }) => {
- {!hideProfileName ? (
-
- ) : (
-
- )}
+