Continue secret sharing

This commit is contained in:
Tuan Dang
2024-07-29 14:32:11 -07:00
parent 938a7b7e72
commit c041e44399
32 changed files with 795 additions and 547 deletions
@@ -88,7 +88,6 @@ export const AddShareSecretForm = ({
encryptedValue: ciphertext,
iv,
tag,
hashedHex,
expiresAt,
expiresAfterViews: expiresAfterViews === "-1" ? undefined : Number(expiresAfterViews),
accessType: accessType as SecretSharingAccessType
@@ -1,120 +0,0 @@
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { Modal, ModalContent } from "@app/components/v2";
import { useTimedReset } from "@app/hooks";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { AddShareSecretForm } from "./AddShareSecretForm";
import { ViewAndCopySharedSecret } from "./ViewAndCopySharedSecret";
const schema = yup.object({
value: yup.string().max(10000).required().label("Shared Secret Value"),
expiresInValue: yup.string().required().label("Expiration Value"),
expiresAfterViews: yup.string().required().label("Expires After Views")
});
export type FormData = yup.InferType<typeof schema>;
type Props = {
popUp: UsePopUpState<["createSharedSecret"]>;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["createSharedSecret"]>,
state?: boolean
) => void;
isPublic: boolean;
inModal: boolean;
};
export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModal }: Props) => {
const {
control,
reset,
handleSubmit,
setValue,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: yupResolver(schema)
});
const [newSharedSecret, setNewSharedSecret] = useState("");
const hasSharedSecret = Boolean(newSharedSecret);
const [isUrlCopied, , setIsUrlCopied] = useTimedReset<boolean>({
initialState: false
});
const [isSecretInputDisabled, setIsSecretInputDisabled] = useState(false);
const copyUrlToClipboard = () => {
navigator.clipboard.writeText(newSharedSecret);
setIsUrlCopied(true);
};
useEffect(() => {
if (isUrlCopied) {
setTimeout(() => setIsUrlCopied(false), 2000);
}
}, [isUrlCopied]);
useEffect(() => {
if (popUp.createSharedSecret.data) {
setValue("value", (popUp.createSharedSecret.data as { value: string }).value);
setIsSecretInputDisabled(true);
}
}, [popUp.createSharedSecret.data]);
// eslint-disable-next-line no-nested-ternary
return inModal ? (
<Modal
isOpen={popUp?.createSharedSecret?.isOpen}
onOpenChange={(open) => {
handlePopUpToggle("createSharedSecret", open);
reset();
setNewSharedSecret("");
setIsSecretInputDisabled(false);
}}
>
<ModalContent
title="Share a Secret"
subTitle="Once you share a secret, the share link is only accessible once."
>
{!hasSharedSecret ? (
<AddShareSecretForm
isPublic={isPublic}
inModal={inModal}
control={control}
handleSubmit={handleSubmit}
isSubmitting={isSubmitting}
setNewSharedSecret={setNewSharedSecret}
isInputDisabled={isSecretInputDisabled}
/>
) : (
<ViewAndCopySharedSecret
inModal={inModal}
newSharedSecret={newSharedSecret}
isUrlCopied={isUrlCopied}
copyUrlToClipboard={copyUrlToClipboard}
/>
)}
</ModalContent>
</Modal>
) : !hasSharedSecret ? (
<AddShareSecretForm
isPublic={isPublic}
inModal={inModal}
control={control}
handleSubmit={handleSubmit}
isSubmitting={isSubmitting}
setNewSharedSecret={setNewSharedSecret}
isInputDisabled={isSecretInputDisabled}
/>
) : (
<ViewAndCopySharedSecret
inModal={inModal}
newSharedSecret={newSharedSecret}
isUrlCopied={isUrlCopied}
copyUrlToClipboard={copyUrlToClipboard}
/>
);
};
@@ -0,0 +1,32 @@
import { Modal, ModalContent } from "@app/components/v2";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { ShareSecretForm } from "@app/views/ShareSecretPublicPage/components";
type Props = {
popUp: UsePopUpState<["createSharedSecret"]>;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["createSharedSecret"]>,
state?: boolean
) => void;
};
export const AddShareSecretModal2 = ({ popUp, handlePopUpToggle }: Props) => {
return (
<Modal
isOpen={popUp?.createSharedSecret?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("createSharedSecret", isOpen);
}}
>
<ModalContent
title="Share a Secret"
subTitle="Once you share a secret, the share link is only accessible once."
>
<ShareSecretForm
isPublic={false}
value={(popUp.createSharedSecret.data as { value?: string })?.value}
/>
</ModalContent>
</Modal>
);
};
@@ -7,7 +7,7 @@ import { Button, DeleteActionModal } from "@app/components/v2";
import { usePopUp } from "@app/hooks";
import { useDeleteSharedSecret } from "@app/hooks/api/secretSharing";
import { AddShareSecretModal } from "./AddShareSecretModal";
import { AddShareSecretModal2 } from "./AddShareSecretModal2";
import { ShareSecretsTable } from "./ShareSecretsTable";
type DeleteModalData = { name: string; id: string };
@@ -59,12 +59,7 @@ export const ShareSecretSection = () => {
</Button>
</div>
<ShareSecretsTable handlePopUpOpen={handlePopUpOpen} />
<AddShareSecretModal
popUp={popUp}
handlePopUpToggle={handlePopUpToggle}
isPublic={false}
inModal
/>
<AddShareSecretModal2 popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.deleteSharedSecretConfirmation.isOpen}
title={`Delete ${
@@ -1,8 +1,10 @@
import { faTrash } from "@fortawesome/free-solid-svg-icons";
import { faEnvelope, faEnvelopeOpen, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import { IconButton, Td, Tr } from "@app/components/v2";
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
// import { useToggle } from "@app/hooks";
import { Badge } from "@app/components/v2/Badge";
import { TSharedSecret } from "@app/hooks/api/secretSharing";
import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -22,26 +24,71 @@ export const ShareSecretsRow = ({
}
) => void;
}) => {
// const [isRowExpanded, setIsRowExpanded] = useToggle();
const lastViewedAt = row.lastViewedAt
? format(new Date(row.lastViewedAt), "yyyy-MM-dd - HH:mm a")
: undefined;
let isExpired = false;
if (row.expiresAfterViews !== null && row.expiresAfterViews <= 0) {
isExpired = true;
}
if (row.expiresAt !== null && new Date(row.expiresAt) < new Date()) {
isExpired = true;
}
return (
<Tr key={row.id} className="h-10">
<Td>{`${row.encryptedValue.substring(0, 5)}...`}</Td>
<Td>{format(new Date(row.createdAt), "yyyy-MM-dd - HH:mm a")}</Td>
<Td>{format(new Date(row.expiresAt), "yyyy-MM-dd - HH:mm a")}</Td>
<Td>{row.expiresAfterViews ? row.expiresAfterViews : "-"}</Td>
<Td>
<IconButton
onClick={() =>
handlePopUpOpen("deleteSharedSecretConfirmation", {
name: "delete",
id: row.id
})
}
variant="plain"
ariaLabel="delete"
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</Td>
</Tr>
<>
<Tr
key={row.id}
// className="h-10 cursor-pointer transition-colors duration-300 hover:bg-mineshaft-700"
// onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<Tooltip content={lastViewedAt ? `Last opened at ${lastViewedAt}` : "Not yet opened"}>
<FontAwesomeIcon icon={lastViewedAt ? faEnvelopeOpen : faEnvelope} />
</Tooltip>
</Td>
<Td>{row.name ? `${row.name}` : "-"}</Td>
<Td>
<Badge variant={isExpired ? "danger" : "success"}>
{isExpired ? "Expired" : "Active"}
</Badge>
</Td>
<Td>{`${format(new Date(row.createdAt), "yyyy-MM-dd - HH:mm a")}`}</Td>
<Td>{format(new Date(row.expiresAt), "yyyy-MM-dd - HH:mm a")}</Td>
<Td>{row.expiresAfterViews !== null ? row.expiresAfterViews : "-"}</Td>
<Td>
<IconButton
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("deleteSharedSecretConfirmation", {
name: "delete",
id: row.id
});
}}
variant="plain"
ariaLabel="delete"
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</Td>
</Tr>
{/* {isRowExpanded && (
<Tr>
<Td
colSpan={6}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && " border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
<div>Test 1</div>
<div>Test 2</div>
<div>Test 3</div>
</div>
</Td>
</Tr>
)} */}
</>
);
};
@@ -1,12 +1,13 @@
import { useState } from "react";
import { faKey } from "@fortawesome/free-solid-svg-icons";
import {
EmptyState,
Pagination,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
@@ -30,34 +31,46 @@ type Props = {
};
export const ShareSecretsTable = ({ handlePopUpOpen }: Props) => {
const { isLoading, data = [] } = useGetSharedSecrets();
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(10);
const { isLoading, data } = useGetSharedSecrets({
offset: (page - 1) * perPage,
limit: perPage
});
return (
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Encrypted Secret</Th>
<Th>Created</Th>
<Th className="w-5" />
<Th>Name</Th>
<Th>Status</Th>
<Th>Created At</Th>
<Th>Valid Until</Th>
<Th>Views Left</Th>
<Th aria-label="button" className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={4} innerKey="shared-secrets" />}
{isLoading && <TableSkeleton columns={7} innerKey="shared-secrets" />}
{!isLoading &&
data?.map((row) => (
data?.secrets?.map((row) => (
<ShareSecretsRow key={row.id} row={row} handlePopUpOpen={handlePopUpOpen} />
))}
{!isLoading && data?.length === 0 && (
<Tr>
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-400">
<EmptyState title="No secrets shared yet" icon={faKey} />
</Td>
</Tr>
)}
</TBody>
</Table>
{!isLoading && data?.totalCount !== undefined && (
<Pagination
count={data.totalCount}
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
/>
)}
{!isLoading && !data?.secrets?.length && (
<EmptyState title="No secrets shared yet" icon={faKey} />
)}
</TableContainer>
);
};