mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Feat: added tag color widgt and changed tag popover design
This commit is contained in:
@@ -3203,6 +3203,9 @@
|
||||
"name": {
|
||||
"example": "any"
|
||||
},
|
||||
"tagColor": {
|
||||
"example": "any"
|
||||
},
|
||||
"slug": {
|
||||
"example": "any"
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
|
||||
export const createWorkspaceTag = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
const { name, slug } = req.body;
|
||||
const { name, slug, tagColor } = req.body;
|
||||
|
||||
const tagToCreate = {
|
||||
name,
|
||||
tagColor,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
slug,
|
||||
user: new Types.ObjectId(req.user._id),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Schema, Types, model } from "mongoose";
|
||||
export interface ITag {
|
||||
_id: Types.ObjectId;
|
||||
name: string;
|
||||
tagColor: string;
|
||||
slug: string;
|
||||
user: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
@@ -15,6 +16,11 @@ const tagSchema = new Schema<ITag>(
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
tagColor: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
},
|
||||
slug: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
||||
@@ -48,6 +48,7 @@ router.post(
|
||||
}),
|
||||
param("workspaceId").exists().trim(),
|
||||
body("name").exists().trim(),
|
||||
body("tagColor").exists().trim(),
|
||||
body("slug").exists().trim(),
|
||||
validateRequest,
|
||||
tagController.createWorkspaceTag
|
||||
|
||||
@@ -1949,6 +1949,8 @@ paths:
|
||||
properties:
|
||||
name:
|
||||
example: any
|
||||
tagColor:
|
||||
example: any
|
||||
slug:
|
||||
example: any
|
||||
/api/v2/workspace/tags/{tagId}:
|
||||
|
||||
5
frontend/src/components/utilities/isValidHexColor.ts
Normal file
5
frontend/src/components/utilities/isValidHexColor.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const isValidHexColor = (hexColor: string) => {
|
||||
const hexColorPattern = /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
|
||||
|
||||
return hexColorPattern.test(hexColor);
|
||||
}
|
||||
@@ -11,10 +11,11 @@ type Props = {
|
||||
color?: string;
|
||||
styles?: Record<string, string>
|
||||
isDisabled?: boolean;
|
||||
tagColor: string;
|
||||
} & VariantProps<typeof tagVariants>;
|
||||
|
||||
const tagVariants = cva(
|
||||
"inline-flex items-center whitespace-nowrap text-sm rounded-sm mr-1.5 text-bunker-200",
|
||||
"inline-flex items-center whitespace-nowrap text-sm rounded-sm mr-1.5 text-bunker-200 rounded-[30px] text-gray-400 ",
|
||||
{
|
||||
variants: {
|
||||
colorSchema: {
|
||||
@@ -41,18 +42,7 @@ export const Tag = ({
|
||||
}: Props) => (
|
||||
<div
|
||||
className={twMerge(tagVariants({ colorSchema, className, size }))}
|
||||
style={{ backgroundColor: color, ...styles }}
|
||||
>
|
||||
{children}
|
||||
{onClose && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
disabled={isDisabled}
|
||||
className="ml-2 flex items-center justify-center"
|
||||
>
|
||||
<FontAwesomeIcon icon={faClose} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -51,3 +51,69 @@ const plansProd: Mapping = {
|
||||
export const plans = plansProd || plansDev;
|
||||
|
||||
export const leaveConfirmDefaultMessage = "Your changes will be lost if you leave the page. Are you sure you want to continue?";
|
||||
|
||||
export const secretTagsColors = [
|
||||
{
|
||||
id: 1,
|
||||
hex: '#bec2c8',
|
||||
rgba: "rgb(128,128,128, 0.8)",
|
||||
name: 'Grey',
|
||||
selected: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
hex: '#95a2b3',
|
||||
rgba: "rgb(0,0,255, 0.8)",
|
||||
name: 'blue',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
hex: '#5e6ad2',
|
||||
rgba: "rgb(128,0,128, 0.8)",
|
||||
name: 'Purple',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
hex: '#26b5ce',
|
||||
rgba: "rgb(0,128,128, 0.8)",
|
||||
name: 'Teal',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
hex: '#4cb782',
|
||||
rgba: "rgb(0,128,0, 0.8)",
|
||||
name: 'Green',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
hex: '#f2c94c',
|
||||
rgba: "rgb(255,255,0, 0.8)",
|
||||
name: 'Yellow',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
hex: '#f2994a',
|
||||
rgba: "rgb(128,128,0, 0.8)",
|
||||
name: 'Orange',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
hex: '#f7c8c1',
|
||||
rgba: "rgb(128,0,0, 0.8)",
|
||||
name: 'Pink',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
hex: '#eb5757',
|
||||
rgba: "rgb(255,0,0, 0.8)",
|
||||
name: 'Red',
|
||||
selected: false
|
||||
},
|
||||
]
|
||||
@@ -7,7 +7,9 @@ import {
|
||||
CreateTagRes,
|
||||
DeleteTagDTO,
|
||||
DeleteWsTagRes,
|
||||
UserWsTags
|
||||
QueryTag,
|
||||
UserWsTags,
|
||||
WsTag
|
||||
} from "./types";
|
||||
|
||||
const workspaceTags = {
|
||||
@@ -34,14 +36,15 @@ export const useCreateWsTag = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<CreateTagRes, {}, CreateTagDTO>({
|
||||
mutationFn: async ({ workspaceID, tagName, tagSlug }) => {
|
||||
mutationFn: async ({ workspaceID, tagName, tagColor, tagSlug }: QueryTag) => {
|
||||
const { data } = await apiRequest.post(`/api/v2/workspace/${workspaceID}/tags`, {
|
||||
name: tagName,
|
||||
tagColor: tagColor,
|
||||
slug: tagSlug
|
||||
})
|
||||
return data;
|
||||
},
|
||||
onSuccess: (tagData) => {
|
||||
onSuccess: (tagData: WsTag) => {
|
||||
queryClient.invalidateQueries(workspaceTags.getWsTags(tagData?.workspace));
|
||||
}
|
||||
});
|
||||
@@ -51,11 +54,11 @@ export const useDeleteWsTag = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<DeleteWsTagRes, {}, DeleteTagDTO>({
|
||||
mutationFn: async ({ tagID }) => {
|
||||
mutationFn: async ({ tagID }: {tagID: string}) => {
|
||||
const { data } = await apiRequest.delete(`/api/v2/workspace/tags/${tagID}`);
|
||||
return data
|
||||
},
|
||||
onSuccess: (tagData) => {
|
||||
onSuccess: (tagData: WsTag) => {
|
||||
queryClient.invalidateQueries(workspaceTags.getWsTags(tagData?.workspace));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -38,15 +38,24 @@ export type DeleteWsTagRes = {
|
||||
_id: string;
|
||||
};
|
||||
|
||||
export type TagDesign = {
|
||||
tagBackground: string;
|
||||
tagLabel: string
|
||||
}
|
||||
|
||||
export type SecretTags = {
|
||||
id: string;
|
||||
_id: string;
|
||||
slug: string;
|
||||
tagBackground: string;
|
||||
tagLabel: string
|
||||
tagColor: string;
|
||||
}
|
||||
|
||||
export type TagColor = {
|
||||
id: number;
|
||||
hex: string
|
||||
rgba: string
|
||||
name: string
|
||||
selected: boolean
|
||||
}
|
||||
|
||||
export type QueryTag = {
|
||||
workspaceID: string;
|
||||
tagName: string;
|
||||
tagColor: string;
|
||||
tagSlug: string
|
||||
}
|
||||
@@ -107,6 +107,31 @@
|
||||
@apply bg-primary-400;
|
||||
}
|
||||
}
|
||||
.tags-conic-bg {
|
||||
background: conic-gradient(rgb(235, 87, 87), rgb(242, 201, 76), rgb(76, 183, 130), rgb(78, 167, 252), rgb(250, 96, 122));
|
||||
}
|
||||
|
||||
.show-tags {
|
||||
transform: translateY(10px);
|
||||
transition: all 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
.hide-tags {
|
||||
transform: translateY(-20px);
|
||||
transition: all 0.2s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.show-hex-input {
|
||||
transform: translateY(-33px);
|
||||
transition: all 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
.hide-hex-input {
|
||||
transform: translateY(20px);
|
||||
transition: all 0.2s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@import "@fontsource/inter/400.css";
|
||||
@import "@fontsource/inter/500.css";
|
||||
|
||||
@@ -513,11 +513,12 @@ export const DashboardPage = () => {
|
||||
}, []);
|
||||
|
||||
const onCreateWsTag = useCallback(
|
||||
async (tagName: string) => {
|
||||
async (tagName: string, tagColor: string) => {
|
||||
try {
|
||||
await createWsTag({
|
||||
workspaceID: workspaceId,
|
||||
tagName,
|
||||
tagColor,
|
||||
tagSlug: tagName.replace(" ", "_")
|
||||
});
|
||||
handlePopUpClose("addTag");
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import { secretTagsColors } from "~/const"
|
||||
import {
|
||||
faCheck
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Button, FormControl, Input, ModalClose, Tooltip } from "@app/components/v2";
|
||||
import { isValidHexColor } from "~/components/utilities/isValidHexColor";
|
||||
import { TagColor } from '~/hooks/api/tags/types';
|
||||
|
||||
import { Button, FormControl, Input, ModalClose } from "@app/components/v2";
|
||||
|
||||
type Props = {
|
||||
onCreateTag: (tagName: string) => Promise<void>;
|
||||
onCreateTag: (tagName: string, tagColor: string) => Promise<void>;
|
||||
};
|
||||
|
||||
const createTagSchema = yup.object({
|
||||
@@ -23,33 +33,151 @@ export const CreateTagModal = ({ onCreateTag }: Props): JSX.Element => {
|
||||
resolver: yupResolver(createTagSchema)
|
||||
});
|
||||
|
||||
const [tagsColors, setTagsColors] = useState<TagColor>(secretTagsColors)
|
||||
const [selectedTagColor, setSelectedTagColor] = useState<TagColor>({})
|
||||
const [showHexInput, setShowHexInput] = useState<boolean>(false)
|
||||
const [tagColor, setTagColor] = useState<string>("")
|
||||
|
||||
|
||||
const onFormSubmit = async ({ name }: FormData) => {
|
||||
await onCreateTag(name);
|
||||
await onCreateTag(name, tagColor);
|
||||
reset();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const clonedTagColors = [...tagsColors]
|
||||
for (const tagColor of clonedTagColors) {
|
||||
if (tagColor.selected) {
|
||||
setSelectedTagColor(tagColor)
|
||||
setTagColor(tagColor.hex)
|
||||
break
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const tagsList = document.querySelector(".secret-tags-wrapper")
|
||||
const tagsHexWrapper = document.querySelector(".tags-hex-wrapper")
|
||||
|
||||
if (showHexInput) {
|
||||
tagsList?.classList.add('hide-tags')
|
||||
tagsList?.classList.remove('show-tags')
|
||||
tagsHexWrapper?.classList.add('show-hex-input')
|
||||
tagsHexWrapper?.classList.remove('hide-hex-input')
|
||||
} else {
|
||||
tagsList?.classList.remove('hide-tags')
|
||||
tagsList?.classList.add('show-tags')
|
||||
tagsHexWrapper?.classList.remove('show-hex-input')
|
||||
tagsHexWrapper?.classList.add('hide-hex-input')
|
||||
}
|
||||
}, [showHexInput])
|
||||
|
||||
const handleColorChange = (tagColor: TagColor) => {
|
||||
const clonedTagColors = [...tagsColors]
|
||||
const tagColorIndex = clonedTagColors.findIndex(_tagColor => _tagColor.id === tagColor.id)
|
||||
const _selectedTagColor = clonedTagColors[tagColorIndex]
|
||||
clonedTagColors.forEach(tagColor => {
|
||||
tagColor.selected = false
|
||||
})
|
||||
if (selectedTagColor.id !== tagColor.id) {
|
||||
_selectedTagColor.selected = !_selectedTagColor.selected
|
||||
setSelectedTagColor(_selectedTagColor)
|
||||
setTagColor(_selectedTagColor.hex)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="name"
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Tag Name" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input {...field} placeholder="Type your tag name" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button className="mr-4" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}>
|
||||
Create
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button variant="plain" colorSchema="secondary">
|
||||
Cancel
|
||||
<>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="name"
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Tag Name" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input {...field} placeholder="Type your tag name" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="mt-2">
|
||||
<label className="text-mineshaft-400">Tag color</label>
|
||||
<div className="flex gap-2 h-[50px]">
|
||||
<div className="w-[12%] h-[2.813rem] inline-flex font-inter items-center justify-center border relative rounded-md border-mineshaft-500 bg-mineshaft-900 hover:bg-mineshaft-800">
|
||||
<div className={`w-[26px] h-[26px] rounded-full`} style={{ background: `${tagColor}` }}></div>
|
||||
</div>
|
||||
|
||||
<div className="w-[88%] h-[2.813rem] flex-wrap inline-flex gap-3 items-center border relative rounded-md border-mineshaft-500 bg-mineshaft-900 hover:bg-mineshaft-800 relative">
|
||||
{
|
||||
(
|
||||
<div className="flex-wrap inline-flex gap-3 items-center secret-tags-wrapper pl-3">
|
||||
{
|
||||
tagsColors.map((tagColor: TagColor) => {
|
||||
return (
|
||||
<Tooltip content={`${tagColor.name}`}>
|
||||
<div className={`flex items-center justify-center w-[26px] h-[26px] hover:ring-offset-2 hover:ring-2 bg-[#bec2c8] border-2 p-2 hover:shadow-lg border-transparent hover:border-black rounded-full`} key={`tag-${tagColor.id}`} style={{ backgroundColor: `${tagColor.hex}` }} onClick={() => handleColorChange(tagColor)}>
|
||||
{
|
||||
tagColor.selected && <FontAwesomeIcon icon={faCheck} style={{ color: `#00000070` }} />
|
||||
}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div className="flex items-center gap-2 px-2 tags-hex-wrapper" >
|
||||
<div className="w-1/6 flex items-center relative rounded-md hover:bg-mineshaft-800">
|
||||
{
|
||||
isValidHexColor(tagColor) && (
|
||||
<div className={`w-[26px] h-[26px] rounded-full flex items-center justify-center`} style={{ background: `${tagColor}` }}>
|
||||
<FontAwesomeIcon icon={faCheck} style={{ color: `#00000070` }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
!isValidHexColor(tagColor) && (
|
||||
<div class="border-dashed border bg-blue rounded-full w-[26px] h-[26px] border-mineshaft-500"></div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="w-10/12">
|
||||
<Input
|
||||
variant="plain"
|
||||
className="w-full focus:text-bunker-100 focus:ring-transparent bg-transparent"
|
||||
autoCapitalization={false}
|
||||
value={tagColor}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setTagColor(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-[26px] h-[26px] flex items-center justify-center absolute top-[10px] right-[-4px] translate-x-[-50%]">
|
||||
<div className="border-mineshaft-500 border h-[2.1rem] mr-4 absolute right-5"></div>
|
||||
<div className={`flex items-center justify-center w-[26px] h-[26px] bg-transparent cursor-pointer hover:ring-offset-1 hover:ring-2 border-mineshaft-500 border bg-mineshaft-900 rounded-[3px] p-2 ${showHexInput ? 'tags-conic-bg rounded-full' : ''}`} onClick={() => setShowHexInput((prev) => !prev)} style={{ border: '1px solid rgba(220, 216, 254, 0.376)' }}>
|
||||
{
|
||||
!showHexInput && <span>#</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button className="mr-4" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}>
|
||||
Create
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
<ModalClose asChild>
|
||||
<Button variant="plain" colorSchema="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -46,9 +46,8 @@ import {
|
||||
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";
|
||||
import { SecretTags } from "~/hooks/api/tags/types";
|
||||
import { useToggle } from "@app/hooks";
|
||||
|
||||
const tagColors = [
|
||||
{ bg: "bg-[#f1c40f]/40", text: "text-[#fcf0c3]/70" },
|
||||
@@ -86,9 +85,6 @@ type Props = {
|
||||
isKeyError?: boolean;
|
||||
keyError?: string;
|
||||
autoCapitalization?: boolean;
|
||||
designObj: TagDesign & WsTag;
|
||||
updateDesign: boolean;
|
||||
selectedFieldIndex: number
|
||||
};
|
||||
|
||||
export const SecretInputRow = memo(
|
||||
@@ -102,7 +98,6 @@ export const SecretInputRow = memo(
|
||||
wsTags,
|
||||
onCreateTagOpen,
|
||||
onDesignTagOpen,
|
||||
designObj,
|
||||
onSecretDelete,
|
||||
searchTerm,
|
||||
control,
|
||||
@@ -112,8 +107,6 @@ export const SecretInputRow = memo(
|
||||
keyError,
|
||||
secUniqId,
|
||||
autoCapitalization,
|
||||
updateDesign,
|
||||
selectedFieldIndex
|
||||
}: Props): JSX.Element => {
|
||||
const isKeySubDisabled = useRef<boolean>(false);
|
||||
// comment management in a row
|
||||
@@ -160,12 +153,10 @@ export const SecretInputRow = memo(
|
||||
overrideAction === SecretActionType.Created || overrideAction === SecretActionType.Modified;
|
||||
|
||||
const [editorRef, setEditorRef] = useState(isOverridden ? secValueOverride : secValue);
|
||||
const [tagDesignObj, setTagDesignObj] = useState<TagDesign & WsTag>({})
|
||||
const [selectedTag, setSelectedTag] = useState<WsTag>({})
|
||||
|
||||
const secId = useWatch({ control, name: `secrets.${index}._id`, exact: true });
|
||||
const tags =
|
||||
useWatch({ control, name: `secrets.${index}.tags`, exact: true, defaultValue: [] }) || [];
|
||||
const tags = useWatch({ control, name: `secrets.${index}.tags`, exact: true, defaultValue: [] }) || [];
|
||||
|
||||
const selectedTagIds = tags.reduce<Record<string, boolean>>(
|
||||
(prev, curr) => ({ ...prev, [curr.slug]: true }),
|
||||
{}
|
||||
@@ -190,20 +181,6 @@ 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
|
||||
@@ -223,23 +200,14 @@ 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, ..._tagDesignObj});
|
||||
append(selectedTag);
|
||||
} else {
|
||||
const pos = tags.findIndex(({ slug }: {slug: string}) => selectedTag.slug === slug);
|
||||
const pos = tags.findIndex(({ slug }: { slug: string }) => selectedTag.slug === slug);
|
||||
remove(pos);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
const isCreatedSecret = !secId;
|
||||
const shouldBeBlockedInAddOnly = !isCreatedSecret && isAddOnly;
|
||||
|
||||
@@ -261,27 +229,13 @@ export const SecretInputRow = memo(
|
||||
return <></>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<tr className="group flex flex-row hover:bg-mineshaft-700" key={index}>
|
||||
<td className="flex h-10 w-10 items-center justify-center border-none px-4">
|
||||
<div className="w-10 text-center text-xs text-bunker-400">{index + 1}</div>
|
||||
</td>
|
||||
{/* Add a custom design to new tag to make visible */}
|
||||
<Modal
|
||||
isOpen={popUp?.designTag?.isOpen}
|
||||
onOpenChange={(open: boolean) => {
|
||||
handlePopUpToggle("designTag", open);
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title={`Customise design for ${selectedTag.slug}`}
|
||||
subTitle="Choose custom background and label text colors for the tag."
|
||||
>
|
||||
<DesignTagModal selectedTag={selectedTag} onDesignTag={onDesignWsTag} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -381,25 +335,68 @@ export const SecretInputRow = memo(
|
||||
</td>
|
||||
<td className="min-w-sm flex">
|
||||
<div className="flex h-8 items-center pl-2">
|
||||
{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]
|
||||
{secretTags.map(({ id, _id, slug, tagColor }: SecretTags, i: number) => {
|
||||
return (
|
||||
<Tag
|
||||
className={cx(
|
||||
tagColor.bg,
|
||||
tagColor.text
|
||||
)}
|
||||
styles={{
|
||||
backgroundColor: tagBackground,
|
||||
color: tagLabel
|
||||
}}
|
||||
isDisabled={isReadOnly || isAddOnly || isRollbackMode}
|
||||
onClose={() => remove(i)}
|
||||
key={id}
|
||||
>
|
||||
{slug}
|
||||
</Tag>)
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div>
|
||||
<Tag
|
||||
isDisabled={isReadOnly || isAddOnly || isRollbackMode}
|
||||
onClose={() => remove(i)}
|
||||
key={id}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div className="rounded-md rounded-full border-mineshaft-500 bg-transparent flex items-center gap-1.5 justify-around">
|
||||
<div className="w-[10px] h-[10px] rounded-full" style={{ background: tagColor ? tagColor : "#bec2c8" }}></div>
|
||||
{slug}
|
||||
</div>
|
||||
|
||||
</Tag>
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
side="left"
|
||||
className="max-h-96 w-auto min-w-[200px] p-2 overflow-y-auto overflow-x-hidden border border-mineshaft-600 bg-mineshaft-800 text-bunker-200"
|
||||
hideCloseBtn
|
||||
>
|
||||
<div className=" text-center text-sm font-medium text-bunker-200">
|
||||
Add tags to {secKey || "this secret"}
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2.5">
|
||||
{wsTags?.map((wsTag) => (
|
||||
<Button
|
||||
variant="star"
|
||||
size="md"
|
||||
className={`mt-4 justify-start px-1 hover:bg-mineshaft-600 hover:border-mineshaft-500 hover:text-bunker-200 ${selectedTagIds?.[wsTag.slug] && "text-primary hover:text-primary"}`}
|
||||
onClick={() => onSelectTag(wsTag)}
|
||||
leftIcon={
|
||||
<Checkbox
|
||||
className="mr-0 data-[state=checked]:bg-primary border-mineshaft-500 border"
|
||||
id="autoCapitalization"
|
||||
isChecked={selectedTagIds?.[wsTag.slug]}
|
||||
>
|
||||
{ }
|
||||
</Checkbox>
|
||||
}
|
||||
key={wsTag._id}
|
||||
>
|
||||
{wsTag.slug}
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
variant="star"
|
||||
size="md"
|
||||
className="mt-4 justify-start px-1 hover:bg-mineshaft-600 hover:border-mineshaft-500 hover:text-bunker-200"
|
||||
onClick={onCreateTagOpen}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
Add new tag
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
)
|
||||
})}
|
||||
<div className="w-0 overflow-hidden group-hover:w-6">
|
||||
<Tooltip content="Copy value">
|
||||
@@ -433,25 +430,22 @@ export const SecretInputRow = memo(
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
side="left"
|
||||
className="max-h-96 w-auto min-w-[200px] overflow-y-auto overflow-x-hidden border border-mineshaft-600 bg-mineshaft-800 p-2 text-bunker-200"
|
||||
className="max-h-96 w-auto min-w-[200px] p-2 overflow-y-auto overflow-x-hidden border border-mineshaft-600 bg-mineshaft-800 text-bunker-200"
|
||||
hideCloseBtn
|
||||
>
|
||||
<div className="mb-2 px-2 text-center text-sm font-medium text-bunker-200">
|
||||
<div className=" text-center text-sm font-medium text-bunker-200">
|
||||
Add tags to {secKey || "this secret"}
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex flex-col space-y-2.5">
|
||||
{wsTags?.map((wsTag) => (
|
||||
<Button
|
||||
variant="plain"
|
||||
size="sm"
|
||||
className={twMerge(
|
||||
"justify-start bg-mineshaft-600 text-bunker-100 hover:bg-mineshaft-500",
|
||||
selectedTagIds?.[wsTag.slug] && "text-primary"
|
||||
)}
|
||||
variant="star"
|
||||
size="md"
|
||||
className={`mt-4 justify-start px-1 hover:bg-mineshaft-600 hover:border-mineshaft-500 hover:text-bunker-200 ${selectedTagIds?.[wsTag.slug] && "text-primary hover:text-primary"}`}
|
||||
onClick={() => onSelectTag(wsTag)}
|
||||
leftIcon={
|
||||
<Checkbox
|
||||
className="mr-0 data-[state=checked]:bg-primary"
|
||||
className="mr-0 data-[state=checked]:bg-primary border-mineshaft-500"
|
||||
id="autoCapitalization"
|
||||
isChecked={selectedTagIds?.[wsTag.slug]}
|
||||
>
|
||||
@@ -465,9 +459,8 @@ export const SecretInputRow = memo(
|
||||
))}
|
||||
<Button
|
||||
variant="star"
|
||||
color="primary"
|
||||
size="sm"
|
||||
className="mt-4 h-7 justify-start bg-mineshaft-600 px-1"
|
||||
size="md"
|
||||
className="mt-4 justify-start px-1 hover:bg-mineshaft-600 hover:border-mineshaft-500 hover:text-bunker-200"
|
||||
onClick={onCreateTagOpen}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user