mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 16:28:11 +00:00
feat: public page has direct secret creation (no modal))
This commit is contained in:
@@ -0,0 +1,233 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
import { Controller } from "react-hook-form";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
ModalClose,
|
||||||
|
SecretInput,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing";
|
||||||
|
|
||||||
|
const schema = yup.object({
|
||||||
|
value: yup.string().max(10000).required().label("Shared Secret Value"),
|
||||||
|
expiresAfterViews: yup.number().min(1).required().label("Expires After Views"),
|
||||||
|
expiresInValue: yup.number().min(1).required().label("Expiration Value"),
|
||||||
|
expiresInUnit: yup.string().required().label("Expiration Unit")
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FormData = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
|
export const AddShareSecretForm = ({
|
||||||
|
isPublic,
|
||||||
|
inModal,
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
isSubmitting,
|
||||||
|
setNewSharedSecret
|
||||||
|
}: {
|
||||||
|
isPublic: boolean;
|
||||||
|
inModal: boolean;
|
||||||
|
handleSubmit: any;
|
||||||
|
control: any;
|
||||||
|
isSubmitting: boolean;
|
||||||
|
setNewSharedSecret: (value: string) => void;
|
||||||
|
}) => {
|
||||||
|
const publicSharedSecretCreator = useCreatePublicSharedSecret();
|
||||||
|
const privateSharedSecretCreator = useCreateSharedSecret();
|
||||||
|
const createSharedSecret = isPublic ? publicSharedSecretCreator : privateSharedSecretCreator;
|
||||||
|
|
||||||
|
const expirationUnitsAndActions = [
|
||||||
|
{
|
||||||
|
unit: "Minutes",
|
||||||
|
action: (expiresAt: Date, expiresInValue: number) =>
|
||||||
|
expiresAt.setMinutes(expiresAt.getMinutes() + expiresInValue)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
unit: "Hours",
|
||||||
|
action: (expiresAt: Date, expiresInValue: number) =>
|
||||||
|
expiresAt.setHours(expiresAt.getHours() + expiresInValue)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
unit: "Days",
|
||||||
|
action: (expiresAt: Date, expiresInValue: number) =>
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + expiresInValue)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
unit: "Weeks",
|
||||||
|
action: (expiresAt: Date, expiresInValue: number) =>
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + expiresInValue * 7)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const onFormSubmit = async ({
|
||||||
|
value,
|
||||||
|
expiresInValue,
|
||||||
|
expiresInUnit,
|
||||||
|
expiresAfterViews
|
||||||
|
}: FormData) => {
|
||||||
|
try {
|
||||||
|
const key = crypto.randomBytes(16).toString("hex");
|
||||||
|
const hashedHex = crypto.createHash("sha256").update(key).digest("hex");
|
||||||
|
const { ciphertext, iv, tag } = encryptSymmetric({
|
||||||
|
plaintext: value,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const expiresAt = new Date();
|
||||||
|
const updateExpiresAt = expirationUnitsAndActions.find(
|
||||||
|
(item) => item.unit === expiresInUnit
|
||||||
|
)?.action;
|
||||||
|
if (updateExpiresAt && expiresInValue) {
|
||||||
|
updateExpiresAt(expiresAt, expiresInValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = await createSharedSecret.mutateAsync({
|
||||||
|
encryptedValue: ciphertext,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
hashedHex,
|
||||||
|
expiresAt,
|
||||||
|
expiresAfterViews
|
||||||
|
});
|
||||||
|
setNewSharedSecret(
|
||||||
|
`${window.location.origin}/shared/secret/${id}?key=${encodeURIComponent(
|
||||||
|
hashedHex
|
||||||
|
)}-${encodeURIComponent(key)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully created a shared secret",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
const axiosError = err as AxiosError;
|
||||||
|
if (axiosError?.response?.status === 401) {
|
||||||
|
createNotification({
|
||||||
|
text: "You do not have access to create shared secrets",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to create a shared secret",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<form className="flex w-full flex-col items-center" onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
<div
|
||||||
|
className={`w-[${inModal ? "33rem" : "36rem"}] ${
|
||||||
|
!inModal && "border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="mb-4">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="value"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Shared Secret"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<SecretInput
|
||||||
|
isVisible
|
||||||
|
{...field}
|
||||||
|
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2 text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 min-h-[70px]"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full flex-row justify-center">
|
||||||
|
<div className="w-2/7 flex">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="expiresAfterViews"
|
||||||
|
defaultValue={6}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
className="mb-4 w-full"
|
||||||
|
label="Expires After Views"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText="Please enter a valid number of views"
|
||||||
|
>
|
||||||
|
<Input {...field} type="number" min={1} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-1/7 flex items-center justify-center px-2">
|
||||||
|
<p className="px-4 text-sm text-gray-400">OR</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-4/7 flex">
|
||||||
|
<div className="flex w-full">
|
||||||
|
<div className="flex w-2/5 w-full justify-center">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="expiresInValue"
|
||||||
|
defaultValue={6}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Expires after Time"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText="Please enter a valid time duration"
|
||||||
|
>
|
||||||
|
<Input {...field} type="number" min={0} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-3/5 w-full justify-center">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="expiresInUnit"
|
||||||
|
defaultValue={expirationUnitsAndActions[0].unit}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl label="Unit" errorText={error?.message} isError={Boolean(error)}>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{expirationUnitsAndActions.map(({ unit }) => (
|
||||||
|
<SelectItem value={unit} key={unit}>
|
||||||
|
{unit}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`flex items-center ${!inModal && "justify-center"}`}>
|
||||||
|
<Button className="mr-4" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}>
|
||||||
|
{inModal ? "Create" : "Share your own Secret"}
|
||||||
|
</Button>
|
||||||
|
{inModal && (
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button variant="plain" colorSchema="secondary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,53 +1,14 @@
|
|||||||
import crypto from "crypto";
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { AxiosError } from "axios";
|
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { Modal, ModalContent } from "@app/components/v2";
|
||||||
import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
FormControl,
|
|
||||||
IconButton,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModalClose,
|
|
||||||
ModalContent,
|
|
||||||
SecretInput,
|
|
||||||
Select,
|
|
||||||
SelectItem
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { useTimedReset } from "@app/hooks";
|
import { useTimedReset } from "@app/hooks";
|
||||||
import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing";
|
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const expirationUnitsAndActions = [
|
import { AddShareSecretForm } from "./AddShareSecretForm";
|
||||||
{
|
import { ViewAndCopySharedSecret } from "./ViewAndCopySharedSecret";
|
||||||
unit: "Minutes",
|
|
||||||
action: (expiresAt: Date, expiresInValue: number) =>
|
|
||||||
expiresAt.setMinutes(expiresAt.getMinutes() + expiresInValue)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
unit: "Hours",
|
|
||||||
action: (expiresAt: Date, expiresInValue: number) =>
|
|
||||||
expiresAt.setHours(expiresAt.getHours() + expiresInValue)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
unit: "Days",
|
|
||||||
action: (expiresAt: Date, expiresInValue: number) =>
|
|
||||||
expiresAt.setDate(expiresAt.getDate() + expiresInValue)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
unit: "Weeks",
|
|
||||||
action: (expiresAt: Date, expiresInValue: number) =>
|
|
||||||
expiresAt.setDate(expiresAt.getDate() + expiresInValue * 7)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
value: yup.string().max(10000).required().label("Shared Secret Value"),
|
value: yup.string().max(10000).required().label("Shared Secret Value"),
|
||||||
@@ -65,9 +26,10 @@ type Props = {
|
|||||||
state?: boolean
|
state?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
|
inModal: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic }: Props) => {
|
export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModal }: Props) => {
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
reset,
|
reset,
|
||||||
@@ -76,11 +38,8 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic }: Prop
|
|||||||
} = useForm<FormData>({
|
} = useForm<FormData>({
|
||||||
resolver: yupResolver(schema)
|
resolver: yupResolver(schema)
|
||||||
});
|
});
|
||||||
const publicSharedSecretCreator = useCreatePublicSharedSecret();
|
|
||||||
const privateSharedSecretCreator = useCreateSharedSecret();
|
|
||||||
const createSharedSecret = isPublic ? publicSharedSecretCreator : privateSharedSecretCreator;
|
|
||||||
|
|
||||||
const [newSharedSecret, setnewSharedSecret] = useState("");
|
const [newSharedSecret, setNewSharedSecret] = useState("");
|
||||||
const hasSharedSecret = Boolean(newSharedSecret);
|
const hasSharedSecret = Boolean(newSharedSecret);
|
||||||
const [isUrlCopied, , setIsUrlCopied] = useTimedReset<boolean>({
|
const [isUrlCopied, , setIsUrlCopied] = useTimedReset<boolean>({
|
||||||
initialState: false
|
initialState: false
|
||||||
@@ -96,70 +55,14 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic }: Prop
|
|||||||
}
|
}
|
||||||
}, [isUrlCopied]);
|
}, [isUrlCopied]);
|
||||||
|
|
||||||
const onFormSubmit = async ({
|
// eslint-disable-next-line no-nested-ternary
|
||||||
value,
|
return inModal ? (
|
||||||
expiresInValue,
|
|
||||||
expiresInUnit,
|
|
||||||
expiresAfterViews
|
|
||||||
}: FormData) => {
|
|
||||||
try {
|
|
||||||
const key = crypto.randomBytes(16).toString("hex");
|
|
||||||
const hashedHex = crypto.createHash("sha256").update(key).digest("hex");
|
|
||||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
|
||||||
plaintext: value,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
const expiresAt = new Date();
|
|
||||||
const updateExpiresAt = expirationUnitsAndActions.find(
|
|
||||||
(item) => item.unit === expiresInUnit
|
|
||||||
)?.action;
|
|
||||||
if (updateExpiresAt && expiresInValue) {
|
|
||||||
updateExpiresAt(expiresAt, expiresInValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = await createSharedSecret.mutateAsync({
|
|
||||||
encryptedValue: ciphertext,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
hashedHex,
|
|
||||||
expiresAt,
|
|
||||||
expiresAfterViews
|
|
||||||
});
|
|
||||||
setnewSharedSecret(
|
|
||||||
`${window.location.origin}/shared/secret/${id}?key=${encodeURIComponent(
|
|
||||||
hashedHex
|
|
||||||
)}-${encodeURIComponent(key)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: "Successfully created a shared secret",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
const axiosError = err as AxiosError;
|
|
||||||
if (axiosError?.response?.status === 401) {
|
|
||||||
createNotification({
|
|
||||||
text: "You do not have access to create shared secrets",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to create a shared secret",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={popUp?.createSharedSecret?.isOpen}
|
isOpen={popUp?.createSharedSecret?.isOpen}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
handlePopUpToggle("createSharedSecret", open);
|
handlePopUpToggle("createSharedSecret", open);
|
||||||
reset();
|
reset();
|
||||||
setnewSharedSecret("");
|
setNewSharedSecret("");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
@@ -167,127 +70,39 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic }: Prop
|
|||||||
subTitle="This link is only accessible once. Please share this link with intended recipients. "
|
subTitle="This link is only accessible once. Please share this link with intended recipients. "
|
||||||
>
|
>
|
||||||
{!hasSharedSecret ? (
|
{!hasSharedSecret ? (
|
||||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
<AddShareSecretForm
|
||||||
<Controller
|
isPublic={isPublic}
|
||||||
|
inModal={inModal}
|
||||||
control={control}
|
control={control}
|
||||||
name="value"
|
handleSubmit={handleSubmit}
|
||||||
defaultValue=""
|
isSubmitting={isSubmitting}
|
||||||
render={({ field, fieldState: { error } }) => (
|
setNewSharedSecret={setNewSharedSecret}
|
||||||
<FormControl
|
|
||||||
label="Shared Secret"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
|
||||||
<SecretInput
|
|
||||||
isVisible={false}
|
|
||||||
{...field}
|
|
||||||
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2 text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 min-h-[70px]"
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex w-full flex-row">
|
|
||||||
<div className="w-2/7 flex">
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="expiresAfterViews"
|
|
||||||
defaultValue={6}
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
className="mb-4 w-full"
|
|
||||||
label="Expires After Views"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText="Please enter a valid number of views"
|
|
||||||
>
|
|
||||||
<Input {...field} type="number" min={1} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-1/7 flex items-center justify-center px-2">
|
|
||||||
<p className="px-4 text-sm text-gray-400">OR</p>
|
|
||||||
</div>
|
|
||||||
<div className="w-4/7 flex">
|
|
||||||
<div className="flex w-full">
|
|
||||||
<div className="flex w-2/5 w-full justify-center">
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="expiresInValue"
|
|
||||||
defaultValue={6}
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Expires after Time"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText="Please enter a valid time duration"
|
|
||||||
>
|
|
||||||
<Input {...field} type="number" min={0} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-3/5 w-full justify-center">
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="expiresInUnit"
|
|
||||||
defaultValue={expirationUnitsAndActions[0].unit}
|
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Unit"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
defaultValue={field.value}
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
{expirationUnitsAndActions.map(({ unit }) => (
|
|
||||||
<SelectItem value={unit} key={unit}>
|
|
||||||
{unit}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Button
|
|
||||||
className="mr-4"
|
|
||||||
type="submit"
|
|
||||||
isDisabled={isSubmitting}
|
|
||||||
isLoading={isSubmitting}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
<ModalClose asChild>
|
|
||||||
<Button variant="plain" colorSchema="secondary">
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</ModalClose>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-2 mb-3 mr-2 flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
<ViewAndCopySharedSecret
|
||||||
<p className="mr-4 break-all">{newSharedSecret}</p>
|
inModal={inModal}
|
||||||
<IconButton
|
newSharedSecret={newSharedSecret}
|
||||||
ariaLabel="copy icon"
|
isUrlCopied={isUrlCopied}
|
||||||
colorSchema="secondary"
|
copyUrlToClipboard={copyUrlToClipboard}
|
||||||
className="group relative"
|
/>
|
||||||
onClick={copyUrlToClipboard}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={isUrlCopied ? faCheck : faCopy} />
|
|
||||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
|
||||||
Click to Copy
|
|
||||||
</span>
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
) : !hasSharedSecret ? (
|
||||||
|
<AddShareSecretForm
|
||||||
|
isPublic={isPublic}
|
||||||
|
inModal={inModal}
|
||||||
|
control={control}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
setNewSharedSecret={setNewSharedSecret}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ViewAndCopySharedSecret
|
||||||
|
inModal={inModal}
|
||||||
|
newSharedSecret={newSharedSecret}
|
||||||
|
isUrlCopied={isUrlCopied}
|
||||||
|
copyUrlToClipboard={copyUrlToClipboard}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -60,7 +60,12 @@ export const ShareSecretSection = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<ShareSecretsTable handlePopUpOpen={handlePopUpOpen} />
|
<ShareSecretsTable handlePopUpOpen={handlePopUpOpen} />
|
||||||
<AddShareSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} isPublic={false} />
|
<AddShareSecretModal
|
||||||
|
popUp={popUp}
|
||||||
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
|
isPublic={false}
|
||||||
|
inModal
|
||||||
|
/>
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.deleteSharedSecretConfirmation.isOpen}
|
isOpen={popUp.deleteSharedSecretConfirmation.isOpen}
|
||||||
title={`Delete ${
|
title={`Delete ${
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { IconButton } from "@app/components/v2";
|
||||||
|
|
||||||
|
export const ViewAndCopySharedSecret = ({
|
||||||
|
inModal,
|
||||||
|
newSharedSecret,
|
||||||
|
isUrlCopied,
|
||||||
|
copyUrlToClipboard
|
||||||
|
}: {
|
||||||
|
inModal: boolean;
|
||||||
|
newSharedSecret: string;
|
||||||
|
isUrlCopied: boolean;
|
||||||
|
copyUrlToClipboard: () => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`w-[${inModal ? "33rem" : "36rem"}] ${
|
||||||
|
!inModal && "border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="mt-2 mb-3 mr-2 flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||||
|
<p className="mr-4 break-all">{newSharedSecret}</p>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
onClick={copyUrlToClipboard}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isUrlCopied ? faCheck : faCopy} />
|
||||||
|
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||||
|
Click to Copy
|
||||||
|
</span>
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,11 +3,8 @@ import Head from "next/head";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import { decryptSymmetric } from "@app/components/utilities/cryptography/crypto";
|
import { decryptSymmetric } from "@app/components/utilities/cryptography/crypto";
|
||||||
import { Button } from "@app/components/v2";
|
|
||||||
import { usePopUp, useTimedReset } from "@app/hooks";
|
import { usePopUp, useTimedReset } from "@app/hooks";
|
||||||
import { useGetActiveSharedSecretByIdAndHashedHex } from "@app/hooks/api/secretSharing";
|
import { useGetActiveSharedSecretByIdAndHashedHex } from "@app/hooks/api/secretSharing";
|
||||||
|
|
||||||
@@ -54,7 +51,7 @@ export const ShareSecretPublicPage = () => {
|
|||||||
navigator.clipboard.writeText(decryptedSecret);
|
navigator.clipboard.writeText(decryptedSecret);
|
||||||
setIsUrlCopied(true);
|
setIsUrlCopied(true);
|
||||||
};
|
};
|
||||||
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["createSharedSecret"] as const);
|
const { popUp, handlePopUpToggle } = usePopUp(["createSharedSecret"] as const);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col bg-bunker-800 text-gray-200">
|
<div className="flex h-screen flex-col bg-bunker-800 text-gray-200">
|
||||||
@@ -119,19 +116,16 @@ export const ShareSecretPublicPage = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
</div>
|
||||||
className="mt-4 max-w-[600px] md:mt-0"
|
<div className="pt-20">
|
||||||
colorSchema="primary"
|
<AddShareSecretModal
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
popUp={popUp}
|
||||||
onClick={() => {
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
handlePopUpOpen("createSharedSecret");
|
isPublic
|
||||||
}}
|
inModal={false}
|
||||||
>
|
/>
|
||||||
Share your own Secret
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AddShareSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} isPublic />
|
|
||||||
<div className="flex items-center justify-center p-4">
|
<div className="flex items-center justify-center p-4">
|
||||||
<p className="text-center text-sm text-gray-200">
|
<p className="text-center text-sm text-gray-200">
|
||||||
Developed by{" "}
|
Developed by{" "}
|
||||||
|
|||||||
Reference in New Issue
Block a user