From 86a264713448752c0b6149ddde4bd42e6ba9ec09 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Wed, 22 Feb 2023 23:29:26 +0530 Subject: [PATCH] feat(ui): added table skeleton and loading for settings page, fix(ui): resolved missing loading state in add new member and whitespace in project settings page --- .../OrgSettingsPage/OrgSettingsPage.tsx | 14 +- .../OrgIncidentContactsTable.tsx | 57 ++++--- .../OrgMembersTable/OrgMembersTable.tsx | 149 ++++++++++-------- .../ProjectSettingsPage.tsx | 16 +- .../AutoCapitalizationSection.tsx | 30 ++-- .../EnvironmentSection/EnvironmentSection.tsx | 15 +- .../SecretTagsSection/SecretTagsSection.tsx | 37 +++-- .../ServiceTokenSection.tsx | 15 +- 8 files changed, 186 insertions(+), 147 deletions(-) diff --git a/frontend/src/views/Settings/OrgSettingsPage/OrgSettingsPage.tsx b/frontend/src/views/Settings/OrgSettingsPage/OrgSettingsPage.tsx index 26c06a9d2..04ef841d2 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/OrgSettingsPage.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/OrgSettingsPage.tsx @@ -36,10 +36,12 @@ export const OrgSettingsPage = () => { const { createNotification } = useNotificationContext(); const orgId = currentOrg?._id || ''; - const { data: orgUsers } = useGetOrgUsers(orgId); - const { data: workspaceMemberships } = useGetUserWorkspaceMemberships(orgId); + const { data: orgUsers, isLoading: isOrgUserLoading } = useGetOrgUsers(orgId); + const { data: workspaceMemberships, isLoading: IsWsMembershipLoading } = + useGetUserWorkspaceMemberships(orgId); const { data: wsKey } = useGetUserWsKey(currentWorkspace?._id || ''); - const { data: incidentContact } = useGetOrgIncidentContact(orgId); + const { data: incidentContact, isLoading: IsIncidentContactLoading } = + useGetOrgIncidentContact(orgId); const renameOrg = useRenameOrg(); const removeUserOrgMembership = useDeleteOrgMembership(); @@ -197,9 +199,9 @@ export const OrgSettingsPage = () => { /** * This function deleted a workspace. - * It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete + * It first checks if there is more than one workspace available. Otherwise, it doesn't delete * It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete. - * It then deletes the workspace and forwards the user to another aviable workspace. + * It then deletes the workspace and forwards the user to another available workspace. */ // const executeDeletingWorkspace = async () => { // const userWorkspaces = await getWorkspaces(); @@ -237,6 +239,7 @@ export const OrgSettingsPage = () => { {t('section-members:org-members-description')}

{
Promise; onAddContact: (email: string) => Promise; @@ -39,7 +46,8 @@ type TAddContactForm = yup.InferType; export const OrgIncidentContactsTable = ({ contacts = [], onAddContact, - onRemoveContact + onRemoveContact, + isLoading }: Props) => { const [searchContact, setSearchContact] = useState(''); const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ @@ -66,6 +74,10 @@ export const OrgIncidentContactsTable = ({ handlePopUpClose('removeContact'); }; + const filteredContacts = contacts.filter(({ email }) => + email.toLocaleLowerCase().includes(searchContact) + ); + return (
@@ -96,28 +108,25 @@ export const OrgIncidentContactsTable = ({ - {contacts - ?.filter(({ email }) => email.toLocaleLowerCase().includes(searchContact)) - ?.map(({ email }) => ( - - {email} - - handlePopUpOpen('removeContact', { email })} - > - - - - - ))} + {isLoading && } + {filteredContacts?.map(({ email }) => ( + + {email} + + handlePopUpOpen('removeContact', { email })} + > + + + + + ))} - {contacts - ?.filter(({ email }) => email.toLocaleLowerCase().includes(searchContact)) - ?.length === 0 && ( -
No incident contacts found
+ {filteredContacts?.length === 0 && !isLoading && ( + )}
diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx index 2911ae603..020eae963 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; -import { faMagnifyingGlass, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { faMagnifyingGlass, faPlus, faTrash, faUsers } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; @@ -8,6 +8,7 @@ import * as yup from 'yup'; import { Button, DeleteActionModal, + EmptyState, FormControl, IconButton, Input, @@ -17,14 +18,14 @@ import { SelectItem, Table, TableContainer, + TableSkeleton, Tag, TBody, Td, Th, THead, Tr, - UpgradePlanModal -} from '@app/components/v2'; + UpgradePlanModal} from '@app/components/v2'; import { usePopUp } from '@app/hooks'; import { OrgUser, Workspace } from '@app/hooks/api/types'; @@ -32,6 +33,7 @@ type Props = { members?: OrgUser[]; workspaceMemberships?: Record; orgName: string; + isLoading?: boolean; isMoreUserNotAllowed: boolean; onRemoveMember: (userId: string) => Promise; onInviteMember: (email: string) => Promise; @@ -56,7 +58,8 @@ export const OrgMembersTable = ({ onInviteMember, onGrantAccess, onRoleChange, - userId + userId, + isLoading }: Props) => { const [searchMemberFilter, setSearchMemberFilter] = useState(''); const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ @@ -72,8 +75,8 @@ export const OrgMembersTable = ({ formState: { isSubmitting } } = useForm({ resolver: yupResolver(addMemberFormSchema) }); - const onAddMember = ({ email }: TAddMemberForm) => { - onInviteMember(email); + const onAddMember = async ({ email }: TAddMemberForm) => { + await onInviteMember(email); handlePopUpClose('addMember'); reset(); }; @@ -140,73 +143,79 @@ export const OrgMembersTable = ({ - {filterdUser.map(({ user, inviteEmail, role, _id: orgMembershipId, status }) => { - const name = user ? `${user.firstName} ${user.lastName}` : '-'; - const email = user?.email || inviteEmail; - const userWs = workspaceMemberships?.[user?._id]; + {isLoading && } + {!isLoading && + filterdUser.map(({ user, inviteEmail, role, _id: orgMembershipId, status }) => { + const name = user ? `${user.firstName} ${user.lastName}` : '-'; + const email = user?.email || inviteEmail; + const userWs = workspaceMemberships?.[user?._id]; - return ( - - {name} - {email} - - {status === 'accepted' && ( - - )} - {(status === 'invited' || status === 'verified') && ( - - )} - {status === 'completed' && ( - - )} - - - {userWs ? ( - userWs?.map(({ name: wsName, _id }) => ( - - {wsName} - - )) - ) : ( - This user isn't part of any projects yet - )} - - - {userId !== user?._id && handlePopUpOpen('removeMember', { id: orgMembershipId })} - > - - } - - - ); - })} + return ( + + {name} + {email} + + {status === 'accepted' && ( + + )} + {(status === 'invited' || status === 'verified') && ( + + )} + {status === 'completed' && ( + + )} + + + {userWs ? ( + userWs?.map(({ name: wsName, _id }) => ( + + {wsName} + + )) + ) : ( + This user isn't part of any projects yet + )} + + + {userId !== user?._id && ( + handlePopUpOpen('removeMember', { id: orgMembershipId })} + > + + + )} + + + ); + })} - {filterdUser.length === 0 && No project members found} + {!isLoading && filterdUser?.length === 0 && ( + + )}
{ const { t } = useTranslation(); - const { currentWorkspace, workspaces } = useWorkspace(); + const { currentWorkspace, workspaces, isLoading: isWorkspaceLoading } = useWorkspace(); const router = useRouter(); - const { data: serviceTokens } = useGetUserWsServiceTokens({ - workspaceID: currentWorkspace?._id || '' - }); + const workspaceID = currentWorkspace?._id || ''; const { createNotification } = useNotificationContext(); // delete action worksapce @@ -66,12 +64,15 @@ export const ProjectSettingsPage = () => { const deleteWsEnv = useDeleteWsEnvironment(); // service token + const { data: serviceTokens, isLoading: isServiceTokenLoading } = useGetUserWsServiceTokens({ + workspaceID: currentWorkspace?._id || '' + }); const { data: latestFileKey } = useGetUserWsKey(workspaceID); const createServiceToken = useCreateServiceToken(); const deleteServiceToken = useDeleteServiceToken(); // tag - const { data: wsTags } = useGetWsTags(workspaceID); + const { data: wsTags, isLoading: isTagLoading } = useGetWsTags(workspaceID); const createWsTag = useCreateWsTag(); const deleteWsTag = useDeleteWsTag(); @@ -300,7 +301,7 @@ export const ProjectSettingsPage = () => { }; return ( -
+
{/* TODO(akhilmhdh): Remove this right when layout is refactored */}
@@ -319,6 +320,7 @@ export const ProjectSettingsPage = () => { /> { isEnvServiceAllowed={isEnvServiceAllowed} /> { onCreateToken={onCreateServiceToken} /> { const { t } = useTranslation(); return ( -
-
-

- {t('settings-project:auto-capitalization')} -

- { - onAutoCapitalizationChange(state as boolean); - }} - > - {t('settings-project:auto-capitalization-description')} - -
-
+
+

{t('settings-project:auto-capitalization')}

+ { + onAutoCapitalizationChange(state as boolean); + }} + > + {t('settings-project:auto-capitalization-description')} + +
); }; diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentSection.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentSection.tsx index ddcf7f4b3..817435a12 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentSection.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/EnvironmentSection/EnvironmentSection.tsx @@ -7,6 +7,7 @@ import * as yup from 'yup'; import { Button, DeleteActionModal, + EmptyState, FormControl, IconButton, Input, @@ -14,6 +15,7 @@ import { ModalContent, Table, TableContainer, + TableSkeleton, TBody, Td, Th, @@ -25,6 +27,7 @@ import { usePopUp } from '@app/hooks/usePopUp'; type Props = { environments: Array<{ name: string; slug: string }>; + isLoading?: boolean; isEnvServiceAllowed: boolean; onCreate: (data: CreateUpdateEnvFormData) => Promise; onUpdate: (oldEnvSlug: string, data: CreateUpdateEnvFormData) => Promise; @@ -43,6 +46,7 @@ export const EnvironmentSection = ({ isEnvServiceAllowed, onCreate, onDelete, + isLoading, onUpdate }: Props): JSX.Element => { const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ @@ -116,7 +120,8 @@ export const EnvironmentSection = ({ - {environments?.length > 0 ? ( + {isLoading && } + {!isLoading && environments.map(({ name, slug }) => ( {name} @@ -152,11 +157,11 @@ export const EnvironmentSection = ({ - )) - ) : ( + ))} + {!isLoading && environments?.length === 0 && ( - - No environments found + + )} diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsSection.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsSection.tsx index 6aa4d26be..98019e0ad 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsSection.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsSection.tsx @@ -1,5 +1,5 @@ import { Controller, useForm } from 'react-hook-form'; -import { faPlus, faTrashCan } from '@fortawesome/free-solid-svg-icons'; +import { faPlus, faTags, faTrashCan } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; @@ -7,6 +7,7 @@ import * as yup from 'yup'; import { Button, DeleteActionModal, + EmptyState, FormControl, IconButton, Input, @@ -16,23 +17,24 @@ import { ModalTrigger, Table, TableContainer, + TableSkeleton, TBody, Td, Th, THead, - Tr, -} from '@app/components/v2'; + Tr} from '@app/components/v2'; import { usePopUp } from '@app/hooks'; import { WorkspaceTag } from '@app/hooks/api/types'; const createTagSchema = yup.object({ - name: yup.string().required().label('Tag Name'), + name: yup.string().required().label('Tag Name') }); export type CreateWsTag = yup.InferType; type Props = { tags: WorkspaceTag[]; + isLoading?: boolean; workspaceName: string; onDeleteTag: (tagID: string) => Promise; onCreateTag: (data: CreateWsTag) => Promise; @@ -42,6 +44,7 @@ type DeleteModalData = { name: string; id: string }; export const SecretTagsSection = ({ tags = [], + isLoading, onDeleteTag, workspaceName, onCreateTag @@ -76,7 +79,10 @@ export const SecretTagsSection = ({

Secret Tags

-

Every secret can be assigned to one or more tags. Here you can add and remove tags for the current project.

+

+ Every secret can be assigned to one or more tags. Here you can add and remove tags for + the current project. +

( @@ -130,7 +136,7 @@ export const SecretTagsSection = ({
- + @@ -140,7 +146,8 @@ export const SecretTagsSection = ({ - {tags?.length > 0 ? ( + {isLoading && } + {!isLoading && tags.map(({ _id, name, slug }) => ( @@ -149,7 +156,7 @@ export const SecretTagsSection = ({ handlePopUpOpen('deleteTagConfirmation', { - name, + name, id: _id }) } @@ -160,11 +167,11 @@ export const SecretTagsSection = ({ - )) - ) : ( + ))} + {!isLoading && tags?.length === 0 && ( - )} diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/ServiceTokenSection.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/ServiceTokenSection.tsx index 052fdc678..88500c6e9 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/ServiceTokenSection.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/ServiceTokenSection.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; -import { faCheck, faCopy, faPlus, faTrashCan } from '@fortawesome/free-solid-svg-icons'; +import { faCheck, faCopy, faKey, faPlus, faTrashCan } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; @@ -9,6 +9,7 @@ import * as yup from 'yup'; import { Button, DeleteActionModal, + EmptyState, FormControl, IconButton, Input, @@ -20,6 +21,7 @@ import { SelectItem, Table, TableContainer, + TableSkeleton, TBody, Td, Th, @@ -47,6 +49,7 @@ export type CreateServiceToken = yup.InferType; type Props = { tokens: ServiceToken[]; + isLoading?: boolean; workspaceName: string; environments: WorkspaceEnv[]; onDeleteToken: (serviceTokenID: string) => Promise; @@ -57,6 +60,7 @@ type DeleteModalData = { name: string; id: string }; export const ServiceTokenSection = ({ tokens = [], + isLoading, onDeleteToken, workspaceName, environments = [], @@ -269,7 +273,8 @@ export const ServiceTokenSection = ({ - {tokens?.length > 0 ? ( + {isLoading && } + {!isLoading && tokens.map((row) => ( @@ -290,11 +295,11 @@ export const ServiceTokenSection = ({ - )) - ) : ( + ))} + {!isLoading && tokens?.length === 0 && ( )}
{name}
- No tags found for this project + +
{row.name}
- No service tokens found +