mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
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
This commit is contained in:
@@ -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')}
|
||||
</p>
|
||||
<OrgMembersTable
|
||||
isLoading={isOrgUserLoading && IsWsMembershipLoading}
|
||||
isMoreUserNotAllowed={isMoreUsersNotAllowed}
|
||||
orgName={currentOrg?.name || ''}
|
||||
members={orgUsers}
|
||||
@@ -261,6 +264,7 @@ export const OrgSettingsPage = () => {
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<OrgIncidentContactsTable
|
||||
isLoading={IsIncidentContactLoading}
|
||||
contacts={incidentContact}
|
||||
onRemoveContact={onRemoveIncidentContact}
|
||||
onAddContact={onAddIncidentContact}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { faMagnifyingGlass, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||
import {
|
||||
faContactBook,
|
||||
faMagnifyingGlass,
|
||||
faPlus,
|
||||
faTrash
|
||||
} 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 +13,7 @@ import * as yup from 'yup';
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
EmptyState,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
@@ -15,16 +21,17 @@ import {
|
||||
ModalContent,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
} from '@app/components/v2';
|
||||
Tr} from '@app/components/v2';
|
||||
import { usePopUp } from '@app/hooks';
|
||||
import { IncidentContact } from '@app/hooks/api/types';
|
||||
|
||||
type Props = {
|
||||
isLoading?: boolean;
|
||||
contacts?: IncidentContact[];
|
||||
onRemoveContact: (email: string) => Promise<void>;
|
||||
onAddContact: (email: string) => Promise<void>;
|
||||
@@ -39,7 +46,8 @@ type TAddContactForm = yup.InferType<typeof addContactFormSchema>;
|
||||
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 (
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex">
|
||||
@@ -96,28 +108,25 @@ export const OrgIncidentContactsTable = ({
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{contacts
|
||||
?.filter(({ email }) => email.toLocaleLowerCase().includes(searchContact))
|
||||
?.map(({ email }) => (
|
||||
<Tr key={email}>
|
||||
<Td className="w-full">{email}</Td>
|
||||
<Td className="mr-4">
|
||||
<IconButton
|
||||
ariaLabel="delete"
|
||||
colorSchema="danger"
|
||||
onClick={() => handlePopUpOpen('removeContact', { email })}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
{isLoading && <TableSkeleton columns={2} key="incident-contact" />}
|
||||
{filteredContacts?.map(({ email }) => (
|
||||
<Tr key={email}>
|
||||
<Td className="w-full">{email}</Td>
|
||||
<Td className="mr-4">
|
||||
<IconButton
|
||||
ariaLabel="delete"
|
||||
colorSchema="danger"
|
||||
onClick={() => handlePopUpOpen('removeContact', { email })}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</TBody>
|
||||
</Table>
|
||||
{contacts
|
||||
?.filter(({ email }) => email.toLocaleLowerCase().includes(searchContact))
|
||||
?.length === 0 && (
|
||||
<div className='py-4 bg-bunker-800 text-sm text-center text-bunker-400 w-full mx-auto flex justify-center'>No incident contacts found</div>
|
||||
{filteredContacts?.length === 0 && !isLoading && (
|
||||
<EmptyState title="No incident contacts found" icon={faContactBook} />
|
||||
)}
|
||||
</TableContainer>
|
||||
</div>
|
||||
|
||||
@@ -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<string, Workspace[]>;
|
||||
orgName: string;
|
||||
isLoading?: boolean;
|
||||
isMoreUserNotAllowed: boolean;
|
||||
onRemoveMember: (userId: string) => Promise<void>;
|
||||
onInviteMember: (email: string) => Promise<void>;
|
||||
@@ -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<TAddMemberForm>({ 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 = ({
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{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 && <TableSkeleton columns={5} key="org-members" />}
|
||||
{!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 (
|
||||
<Tr key={`org-membership-${orgMembershipId}`} className="w-full">
|
||||
<Td>{name}</Td>
|
||||
<Td>{email}</Td>
|
||||
<Td>
|
||||
{status === 'accepted' && (
|
||||
<Select
|
||||
defaultValue={role}
|
||||
isDisabled={userId === user?._id}
|
||||
className="w-full bg-mineshaft-600"
|
||||
onValueChange={(selectedRole) =>
|
||||
onRoleChange(orgMembershipId, selectedRole)
|
||||
}
|
||||
>
|
||||
{(isIamOwner || role === 'owner') && (
|
||||
<SelectItem value="owner">owner</SelectItem>
|
||||
)}
|
||||
<SelectItem value="admin">admin</SelectItem>
|
||||
<SelectItem value="member">member</SelectItem>
|
||||
</Select>
|
||||
)}
|
||||
{(status === 'invited' || status === 'verified') && (
|
||||
<Button colorSchema="secondary" onClick={() => onInviteMember(email)}>
|
||||
Resent Invite
|
||||
</Button>
|
||||
)}
|
||||
{status === 'completed' && (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
onClick={() => onGrantAccess(user?._id, user?.publicKey)}
|
||||
>
|
||||
Grant Access
|
||||
</Button>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{userWs ? (
|
||||
userWs?.map(({ name: wsName, _id }) => (
|
||||
<Tag key={`user-${user._id}-workspace-${_id}`} className="my-1">
|
||||
{wsName}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<Tag colorSchema="red">This user isn't part of any projects yet</Tag>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{userId !== user?._id && <IconButton
|
||||
ariaLabel="delete"
|
||||
colorSchema="danger"
|
||||
isDisabled={userId === user?._id}
|
||||
onClick={() => handlePopUpOpen('removeMember', { id: orgMembershipId })}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<Tr key={`org-membership-${orgMembershipId}`} className="w-full">
|
||||
<Td>{name}</Td>
|
||||
<Td>{email}</Td>
|
||||
<Td>
|
||||
{status === 'accepted' && (
|
||||
<Select
|
||||
defaultValue={role}
|
||||
isDisabled={userId === user?._id}
|
||||
className="w-full bg-mineshaft-600"
|
||||
onValueChange={(selectedRole) =>
|
||||
onRoleChange(orgMembershipId, selectedRole)
|
||||
}
|
||||
>
|
||||
{(isIamOwner || role === 'owner') && (
|
||||
<SelectItem value="owner">owner</SelectItem>
|
||||
)}
|
||||
<SelectItem value="admin">admin</SelectItem>
|
||||
<SelectItem value="member">member</SelectItem>
|
||||
</Select>
|
||||
)}
|
||||
{(status === 'invited' || status === 'verified') && (
|
||||
<Button colorSchema="secondary" onClick={() => onInviteMember(email)}>
|
||||
Resent Invite
|
||||
</Button>
|
||||
)}
|
||||
{status === 'completed' && (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
onClick={() => onGrantAccess(user?._id, user?.publicKey)}
|
||||
>
|
||||
Grant Access
|
||||
</Button>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{userWs ? (
|
||||
userWs?.map(({ name: wsName, _id }) => (
|
||||
<Tag key={`user-${user._id}-workspace-${_id}`} className="my-1">
|
||||
{wsName}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<Tag colorSchema="red">This user isn't part of any projects yet</Tag>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{userId !== user?._id && (
|
||||
<IconButton
|
||||
ariaLabel="delete"
|
||||
colorSchema="danger"
|
||||
isDisabled={userId === user?._id}
|
||||
onClick={() => handlePopUpOpen('removeMember', { id: orgMembershipId })}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{filterdUser.length === 0 && <tr className='bg-bunker-800 text-sm py-4 text-center text-bunker-400 w-full mx-auto flex justify-center'><td className='col-span-5'>No project members found</td></tr>}
|
||||
{!isLoading && filterdUser?.length === 0 && (
|
||||
<EmptyState title="No project members found" icon={faUsers} />
|
||||
)}
|
||||
</TableContainer>
|
||||
</div>
|
||||
<Modal
|
||||
|
||||
@@ -45,11 +45,9 @@ import {
|
||||
|
||||
export const ProjectSettingsPage = () => {
|
||||
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 (
|
||||
<div className="container mx-auto flex flex-col px-8 text-mineshaft-50 dark dark:[color-scheme:dark]">
|
||||
<div className="dark container mx-auto flex flex-col px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||
{/* TODO(akhilmhdh): Remove this right when layout is refactored */}
|
||||
<div className="relative right-5">
|
||||
<NavHeader pageName={t('settings-project:title')} isProjectRelated />
|
||||
@@ -319,6 +320,7 @@ export const ProjectSettingsPage = () => {
|
||||
/>
|
||||
<CopyProjectIDSection workspaceID={currentWorkspace?._id || ''} />
|
||||
<EnvironmentSection
|
||||
isLoading={isWorkspaceLoading}
|
||||
environments={currentWorkspace?.environments || []}
|
||||
onCreate={onCreateWsEnv}
|
||||
onDelete={onDeleteWsEnv}
|
||||
@@ -326,6 +328,7 @@ export const ProjectSettingsPage = () => {
|
||||
isEnvServiceAllowed={isEnvServiceAllowed}
|
||||
/>
|
||||
<ServiceTokenSection
|
||||
isLoading={isServiceTokenLoading}
|
||||
tokens={serviceTokens || []}
|
||||
environments={currentWorkspace?.environments || []}
|
||||
onDeleteToken={onDeleteServiceToken}
|
||||
@@ -333,6 +336,7 @@ export const ProjectSettingsPage = () => {
|
||||
onCreateToken={onCreateServiceToken}
|
||||
/>
|
||||
<SecretTagsSection
|
||||
isLoading={isTagLoading}
|
||||
tags={wsTags || []}
|
||||
onDeleteTag={onDeleteTag}
|
||||
workspaceName={currentWorkspace?.name || ''}
|
||||
|
||||
@@ -13,22 +13,18 @@ export const AutoCapitalizationSection = ({
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<form>
|
||||
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2">
|
||||
<p className="mb-4 mt-2 text-xl font-semibold">
|
||||
{t('settings-project:auto-capitalization')}
|
||||
</p>
|
||||
<Checkbox
|
||||
className="data-[state=checked]:bg-primary"
|
||||
id="autoCapitalization"
|
||||
isChecked={workspaceAutoCapitalization}
|
||||
onCheckedChange={(state) => {
|
||||
onAutoCapitalizationChange(state as boolean);
|
||||
}}
|
||||
>
|
||||
{t('settings-project:auto-capitalization-description')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</form>
|
||||
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2">
|
||||
<p className="mb-4 mt-2 text-xl font-semibold">{t('settings-project:auto-capitalization')}</p>
|
||||
<Checkbox
|
||||
className="data-[state=checked]:bg-primary"
|
||||
id="autoCapitalization"
|
||||
isChecked={workspaceAutoCapitalization}
|
||||
onCheckedChange={(state) => {
|
||||
onAutoCapitalizationChange(state as boolean);
|
||||
}}
|
||||
>
|
||||
{t('settings-project:auto-capitalization-description')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<void>;
|
||||
onUpdate: (oldEnvSlug: string, data: CreateUpdateEnvFormData) => Promise<void>;
|
||||
@@ -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 = ({
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{environments?.length > 0 ? (
|
||||
{isLoading && <TableSkeleton columns={3} key="project-envs" />}
|
||||
{!isLoading &&
|
||||
environments.map(({ name, slug }) => (
|
||||
<Tr key={name}>
|
||||
<Td>{name}</Td>
|
||||
@@ -152,11 +157,11 @@ export const EnvironmentSection = ({
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
))
|
||||
) : (
|
||||
))}
|
||||
{!isLoading && environments?.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={4} className="pt-7 pb-5 text-center text-bunker-400">
|
||||
No environments found
|
||||
<Td colSpan={3}>
|
||||
<EmptyState title="No environments found" />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
|
||||
@@ -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<typeof createTagSchema>;
|
||||
|
||||
type Props = {
|
||||
tags: WorkspaceTag[];
|
||||
isLoading?: boolean;
|
||||
workspaceName: string;
|
||||
onDeleteTag: (tagID: string) => Promise<void>;
|
||||
onCreateTag: (data: CreateWsTag) => Promise<string>;
|
||||
@@ -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 = ({
|
||||
<div className="flex w-full flex-row justify-between">
|
||||
<div className="flex w-full flex-col">
|
||||
<p className="mb-3 text-xl font-semibold">Secret Tags</p>
|
||||
<p className="text-sm text-gray-400">Every secret can be assigned to one or more tags. Here you can add and remove tags for the current project.</p>
|
||||
<p className="text-sm text-gray-400">
|
||||
Every secret can be assigned to one or more tags. Here you can add and remove tags for
|
||||
the current project.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Modal
|
||||
@@ -92,8 +98,8 @@ export const SecretTagsSection = ({
|
||||
</Button>
|
||||
</ModalTrigger>
|
||||
<ModalContent
|
||||
title={`Add a tag for ${ workspaceName}`}
|
||||
subTitle='Specify your tag name, and the slug will be created automatically.'
|
||||
title={`Add a tag for ${workspaceName}`}
|
||||
subTitle="Specify your tag name, and the slug will be created automatically."
|
||||
>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<Controller
|
||||
@@ -102,7 +108,7 @@ export const SecretTagsSection = ({
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label='Tag Name'
|
||||
label="Tag Name"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
@@ -130,7 +136,7 @@ export const SecretTagsSection = ({
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
<TableContainer className='mt-4'>
|
||||
<TableContainer className="mt-4">
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
@@ -140,7 +146,8 @@ export const SecretTagsSection = ({
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{tags?.length > 0 ? (
|
||||
{isLoading && <TableSkeleton columns={3} key="secret-tags" />}
|
||||
{!isLoading &&
|
||||
tags.map(({ _id, name, slug }) => (
|
||||
<Tr key={name}>
|
||||
<Td>{name}</Td>
|
||||
@@ -149,7 +156,7 @@ export const SecretTagsSection = ({
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
handlePopUpOpen('deleteTagConfirmation', {
|
||||
name,
|
||||
name,
|
||||
id: _id
|
||||
})
|
||||
}
|
||||
@@ -160,11 +167,11 @@ export const SecretTagsSection = ({
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
))
|
||||
) : (
|
||||
))}
|
||||
{!isLoading && tags?.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={4} className="py-6 text-center text-bunker-400">
|
||||
No tags found for this project
|
||||
<Td colSpan={3}>
|
||||
<EmptyState title="No secret tags found" icon={faTags} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
|
||||
@@ -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<typeof createServiceTokenSchema>;
|
||||
|
||||
type Props = {
|
||||
tokens: ServiceToken[];
|
||||
isLoading?: boolean;
|
||||
workspaceName: string;
|
||||
environments: WorkspaceEnv[];
|
||||
onDeleteToken: (serviceTokenID: string) => Promise<void>;
|
||||
@@ -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 = ({
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{tokens?.length > 0 ? (
|
||||
{isLoading && <TableSkeleton columns={4} key="project-service-tokens" />}
|
||||
{!isLoading &&
|
||||
tokens.map((row) => (
|
||||
<Tr key={row._id}>
|
||||
<Td>{row.name}</Td>
|
||||
@@ -290,11 +295,11 @@ export const ServiceTokenSection = ({
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
))
|
||||
) : (
|
||||
))}
|
||||
{!isLoading && tokens?.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={4} className="py-6 text-center text-bunker-400">
|
||||
No service tokens found
|
||||
<EmptyState title="No service tokens found" icon={faKey} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user