diff --git a/frontend/src/components/v2/Tag/Tag.tsx b/frontend/src/components/v2/Tag/Tag.tsx index 13672e7e9..cbb833b30 100644 --- a/frontend/src/components/v2/Tag/Tag.tsx +++ b/frontend/src/components/v2/Tag/Tag.tsx @@ -9,6 +9,7 @@ type Props = { className?: string; onClose?: () => void; color?: string; + styles?: Record isDisabled?: boolean; } & VariantProps; @@ -35,11 +36,12 @@ export const Tag = ({ color, isDisabled, size = "sm", - onClose + onClose, + styles = {} }: Props) => (
{children} {onClose && ( diff --git a/frontend/src/hooks/api/tags/types.ts b/frontend/src/hooks/api/tags/types.ts index 56b7171bf..87486ee03 100644 --- a/frontend/src/hooks/api/tags/types.ts +++ b/frontend/src/hooks/api/tags/types.ts @@ -36,4 +36,17 @@ export type DeleteWsTagRes = { createdAt: string; user: string; _id: string; -}; \ No newline at end of file +}; + +export type TagDesign = { + tagBackground: string; + tagLabel: string +} + +export type SecretTags = { + id: string; + _id: string; + slug: string; + tagBackground: string; + tagLabel: string +} \ No newline at end of file diff --git a/frontend/src/views/DashboardPage/components/DesignTagModal/DesignTagModal.tsx b/frontend/src/views/DashboardPage/components/DesignTagModal/DesignTagModal.tsx new file mode 100644 index 000000000..6993471e9 --- /dev/null +++ b/frontend/src/views/DashboardPage/components/DesignTagModal/DesignTagModal.tsx @@ -0,0 +1,138 @@ +import { Controller, useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; + +import { Button, FormControl, Input, ModalClose, Tooltip, IconButton, Tag } from "@app/components/v2"; +import { useToggle } from "@app/hooks"; +import { TagDesign } from "~/hooks/api/tags/types"; + +import { + faEye, + faEyeSlash +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useState } from 'react'; +import { WsTag } from '../../../../hooks/api/tags/types'; + +type TagData = { + tagBackground: string; + tagLabel: string +} + +type Props = { + onDesignTag: (tagData: TagData) => void; + selectedTag: WsTag +}; + +const designTagSchema = yup.object({ + tagBackground: yup.string().required().trim().label("Tag Background"), + tagLabel: yup.string().required().trim().label("Tag Label"), +}); +type FormData = yup.InferType; + + +export const DesignTagModal = ({ onDesignTag, selectedTag }: Props): JSX.Element => { + const [tagDesignObj, setTagDesignObj] = useState({ + tagColor: { + bg: "", + text: "" + } + }) + + const { + control, + reset, + formState, + handleSubmit, + setValue + } = useForm({ + resolver: yupResolver(designTagSchema) + }); + + const onFormSubmit = ({ tagBackground, tagLabel }: FormData) => { + onDesignTag({ tagBackground, tagLabel }); + reset(); + }; + + const [previewTag, setPreviewTag] = useToggle(false); + + const handleInputChange = (e: React.ChangeEvent, type: string) => { + setTagDesignObj((prev: { tagColor: { bg: string, text: string }; }) => ({ + tagColor: { + ...prev.tagColor, + [type]: e.target.value + } + })) + if (type === 'bg') { + setValue('tagBackground', e.target.value) + } else { + setValue('tagLabel', e.target.value) + } + } + + return ( +
+
+ { + return ( + <> + + ) => handleInputChange(e, 'bg')} /> + + + ) + }} + /> + {/* */} +
+ setPreviewTag.toggle()} className="absolute top-[2px] left-[127px] cursor-pointer" /> + {previewTag && ( + void (0)} + key={selectedTag._id} + className="absolute top-[-5px] right-[-5px] cursor-pointer" + > + {selectedTag.slug} + + )} + +
+ {/*
*/} +
+ + { + return ( + <> + + ) => handleInputChange(e, 'text')} value={tagDesignObj.tagColor.text} /> + + + ) + }} + /> +
+ + + + +
+ + ); +}; diff --git a/frontend/src/views/DashboardPage/components/DesignTagModal/index.tsx b/frontend/src/views/DashboardPage/components/DesignTagModal/index.tsx new file mode 100644 index 000000000..165e8e3a3 --- /dev/null +++ b/frontend/src/views/DashboardPage/components/DesignTagModal/index.tsx @@ -0,0 +1 @@ +export {DesignTagModal} from "./DesignTagModal" \ No newline at end of file diff --git a/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx b/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx index 65e5436b7..f58360dcb 100644 --- a/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx +++ b/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx @@ -38,12 +38,17 @@ import { SecretInput, Tag, TextArea, - Tooltip + Tooltip, + Modal, + ModalContent, } from "@app/components/v2"; -import { useToggle } from "@app/hooks"; + import { WsTag } from "@app/hooks/api/types"; import { FormData, SecretActionType } from "../../DashboardPage.utils"; +import { SecretTags, TagDesign } from "~/hooks/api/tags/types"; +import { DesignTagModal } from "../../components/DesignTagModal"; +import { useLeaveConfirm, usePopUp, useToggle } from "@app/hooks"; const tagColors = [ { bg: "bg-[#f1c40f]/40", text: "text-[#fcf0c3]/70" }, @@ -73,6 +78,7 @@ type Props = { // tag props wsTags?: WsTag[]; onCreateTagOpen: () => void; + onDesignTagOpen: (selectedTag: WsTag, selectedFieldIndex: number) => void; // rhf specific functions, dont put this using useFormContext. This is passed as props to avoid re-rendering control: Control; register: UseFormRegister; @@ -80,6 +86,9 @@ type Props = { isKeyError?: boolean; keyError?: string; autoCapitalization?: boolean; + designObj: TagDesign & WsTag; + updateDesign: boolean; + selectedFieldIndex: number }; export const SecretInputRow = memo( @@ -92,6 +101,8 @@ export const SecretInputRow = memo( isAddOnly, wsTags, onCreateTagOpen, + onDesignTagOpen, + designObj, onSecretDelete, searchTerm, control, @@ -100,7 +111,9 @@ export const SecretInputRow = memo( isKeyError, keyError, secUniqId, - autoCapitalization + autoCapitalization, + updateDesign, + selectedFieldIndex }: Props): JSX.Element => { const isKeySubDisabled = useRef(false); // comment management in a row @@ -113,7 +126,7 @@ export const SecretInputRow = memo( const tagColorByTagId = new Map((wsTags || []).map((wsTag, i) => [wsTag._id, tagColors[i % tagColors.length]])) // display the tags in alphabetical order - secretTags.sort((a, b) => a.name.localeCompare(b.name)) + secretTags.sort((a, b) => a?.name?.localeCompare(b?.name)) // to get details on a secret const overrideAction = useWatch({ @@ -145,7 +158,10 @@ export const SecretInputRow = memo( // when secret is override by personal values const isOverridden = overrideAction === SecretActionType.Created || overrideAction === SecretActionType.Modified; + const [editorRef, setEditorRef] = useState(isOverridden ? secValueOverride : secValue); + const [tagDesignObj, setTagDesignObj] = useState({}) + const [selectedTag, setSelectedTag] = useState({}) const secId = useWatch({ control, name: `secrets.${index}._id`, exact: true }); const tags = @@ -174,6 +190,20 @@ export const SecretInputRow = memo( setInviteLinkCopied.on(); }; + const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ + "secretDetails", + "addTag", + "secretSnapshots", + "uploadedSecOpts", + "compareSecrets", + "folderForm", + "deleteFolder", + "upgradePlan", + "addSecretImport", + "deleteSecretImport", + "designTag" + ] as const); + const onSecretOverride = () => { if (isOverridden) { // when user created a new override but then removes @@ -193,14 +223,22 @@ export const SecretInputRow = memo( }; const onSelectTag = (selectedTag: WsTag) => { + const checkBoxSelected = !selectedTagIds[selectedTag.slug] + checkBoxSelected && handlePopUpOpen('designTag') + setSelectedTag(selectedTag) + }; + + const onDesignWsTag = (_tagDesignObj: TagDesign) => { + setTagDesignObj(() => (_tagDesignObj)) + handlePopUpClose("designTag"); const shouldAppend = !selectedTagIds[selectedTag.slug]; if (shouldAppend) { - append(selectedTag); + append({...selectedTag, ..._tagDesignObj}); } else { - const pos = tags.findIndex(({ slug }) => selectedTag.slug === slug); + const pos = tags.findIndex(({ slug }: {slug: string}) => selectedTag.slug === slug); remove(pos); } - }; + } const isCreatedSecret = !secId; const shouldBeBlockedInAddOnly = !isCreatedSecret && isAddOnly; @@ -223,11 +261,28 @@ export const SecretInputRow = memo( return <>; } + + return (
{index + 1}
+ {/* Add a custom design to new tag to make visible */} + { + handlePopUpToggle("designTag", open); + }} + > + + + + +
- {secretTags.map(({ id, _id, slug }, i) => { + {secretTags.map(({ id, _id, slug, tagBackground, tagLabel }: SecretTags, i: number) => { // This map lookup shouldn't ever fail, but if it does we default to the first color const tagColor = tagColorByTagId.get(_id) || tagColors[0] return ( @@ -335,6 +390,10 @@ export const SecretInputRow = memo( tagColor.bg, tagColor.text )} + styles={{ + backgroundColor: tagBackground, + color: tagLabel + }} isDisabled={isReadOnly || isAddOnly || isRollbackMode} onClose={() => remove(i)} key={id} @@ -395,9 +454,8 @@ export const SecretInputRow = memo( className="mr-0 data-[state=checked]:bg-primary" id="autoCapitalization" isChecked={selectedTagIds?.[wsTag.slug]} - onCheckedChange={() => {}} > - {} + { } } key={wsTag._id}