mirror of
https://github.com/formbricks/formbricks.git
synced 2026-03-07 01:02:21 -06:00
feat: added logic to show tags for viewer role (#2454)
This commit is contained in:
@@ -21,6 +21,7 @@ interface ResponseTagsWrapperProps {
|
||||
responseId: string;
|
||||
environmentTags: TTag[];
|
||||
updateFetchedResponses: () => void;
|
||||
isViewer?: boolean;
|
||||
}
|
||||
|
||||
const ResponseTagsWrapper: React.FC<ResponseTagsWrapperProps> = ({
|
||||
@@ -29,6 +30,7 @@ const ResponseTagsWrapper: React.FC<ResponseTagsWrapperProps> = ({
|
||||
responseId,
|
||||
environmentTags,
|
||||
updateFetchedResponses,
|
||||
isViewer,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
@@ -57,15 +59,17 @@ const ResponseTagsWrapper: React.FC<ResponseTagsWrapperProps> = ({
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 p-6">
|
||||
<Button
|
||||
variant="minimal"
|
||||
size="sm"
|
||||
className="cursor-pointer p-0"
|
||||
onClick={() => {
|
||||
router.push(`/environments/${environmentId}/settings/tags`);
|
||||
}}>
|
||||
<SettingsIcon className="h-5 w-5 text-slate-300 hover:text-slate-400" />
|
||||
</Button>
|
||||
{!isViewer && (
|
||||
<Button
|
||||
variant="minimal"
|
||||
size="sm"
|
||||
className="cursor-pointer p-0"
|
||||
onClick={() => {
|
||||
router.push(`/environments/${environmentId}/settings/tags`);
|
||||
}}>
|
||||
<SettingsIcon className="h-5 w-5 text-slate-300 hover:text-slate-400" />
|
||||
</Button>
|
||||
)}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{tagsState?.map((tag) => (
|
||||
<Tag
|
||||
@@ -76,64 +80,67 @@ const ResponseTagsWrapper: React.FC<ResponseTagsWrapperProps> = ({
|
||||
tags={tagsState}
|
||||
setTagsState={setTagsState}
|
||||
highlight={tagIdToHighlight === tag.tagId}
|
||||
allowDelete={!isViewer}
|
||||
/>
|
||||
))}
|
||||
|
||||
<TagsCombobox
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
tags={environmentTags?.map((tag) => ({ value: tag.id, label: tag.name })) ?? []}
|
||||
currentTags={tagsState.map((tag) => ({ value: tag.tagId, label: tag.tagName }))}
|
||||
createTag={async (tagName) => {
|
||||
await createTagAction(environmentId, tagName?.trim() ?? "")
|
||||
.then((tag) => {
|
||||
setTagsState((prevTags) => [
|
||||
...prevTags,
|
||||
{
|
||||
tagId: tag.id,
|
||||
tagName: tag.name,
|
||||
},
|
||||
]);
|
||||
createTagToResponeAction(responseId, tag.id).then(() => {
|
||||
updateFetchedResponses();
|
||||
{!isViewer && (
|
||||
<TagsCombobox
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
tags={environmentTags?.map((tag) => ({ value: tag.id, label: tag.name })) ?? []}
|
||||
currentTags={tagsState.map((tag) => ({ value: tag.tagId, label: tag.tagName }))}
|
||||
createTag={async (tagName) => {
|
||||
await createTagAction(environmentId, tagName?.trim() ?? "")
|
||||
.then((tag) => {
|
||||
setTagsState((prevTags) => [
|
||||
...prevTags,
|
||||
{
|
||||
tagId: tag.id,
|
||||
tagName: tag.name,
|
||||
},
|
||||
]);
|
||||
createTagToResponeAction(responseId, tag.id).then(() => {
|
||||
updateFetchedResponses();
|
||||
setSearchValue("");
|
||||
setOpen(false);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err?.message.includes("Unique constraint failed on the fields")) {
|
||||
toast.error("Tag already exists", {
|
||||
duration: 2000,
|
||||
icon: <AlertCircleIcon className="h-5 w-5 text-orange-500" />,
|
||||
});
|
||||
} else {
|
||||
toast.error(err?.message ?? "Something went wrong", {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
setSearchValue("");
|
||||
setOpen(false);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err?.message.includes("Unique constraint failed on the fields")) {
|
||||
toast.error("Tag already exists", {
|
||||
duration: 2000,
|
||||
icon: <AlertCircleIcon className="h-5 w-5 text-orange-500" />,
|
||||
});
|
||||
} else {
|
||||
toast.error(err?.message ?? "Something went wrong", {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
}}
|
||||
addTag={(tagId) => {
|
||||
setTagsState((prevTags) => [
|
||||
...prevTags,
|
||||
{
|
||||
tagId,
|
||||
tagName: environmentTags?.find((tag) => tag.id === tagId)?.name ?? "",
|
||||
},
|
||||
]);
|
||||
|
||||
createTagToResponeAction(responseId, tagId).then(() => {
|
||||
updateFetchedResponses();
|
||||
setSearchValue("");
|
||||
setOpen(false);
|
||||
});
|
||||
}}
|
||||
addTag={(tagId) => {
|
||||
setTagsState((prevTags) => [
|
||||
...prevTags,
|
||||
{
|
||||
tagId,
|
||||
tagName: environmentTags?.find((tag) => tag.id === tagId)?.name ?? "",
|
||||
},
|
||||
]);
|
||||
|
||||
createTagToResponeAction(responseId, tagId).then(() => {
|
||||
updateFetchedResponses();
|
||||
setSearchValue("");
|
||||
setOpen(false);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -471,15 +471,14 @@ export default function SingleResponseCard({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{user && !isViewer && (
|
||||
<ResponseTagsWrapper
|
||||
environmentId={environmentId}
|
||||
responseId={response.id}
|
||||
tags={response.tags.map((tag) => ({ tagId: tag.id, tagName: tag.name }))}
|
||||
environmentTags={environmentTags}
|
||||
updateFetchedResponses={updateFetchedResponses}
|
||||
/>
|
||||
)}
|
||||
<ResponseTagsWrapper
|
||||
environmentId={environmentId}
|
||||
responseId={response.id}
|
||||
tags={response.tags.map((tag) => ({ tagId: tag.id, tagName: tag.name }))}
|
||||
environmentTags={environmentTags}
|
||||
updateFetchedResponses={updateFetchedResponses}
|
||||
isViewer={isViewer}
|
||||
/>
|
||||
|
||||
<DeleteDialog
|
||||
open={deleteDialogOpen}
|
||||
|
||||
@@ -14,9 +14,18 @@ interface ResponseTagsWrapperProps {
|
||||
tags?: Tag[];
|
||||
setTagsState?: (tags: Tag[]) => void;
|
||||
highlight?: boolean;
|
||||
allowDelete?: boolean;
|
||||
}
|
||||
|
||||
export function Tag({ tagId, tagName, onDelete, tags, setTagsState, highlight }: ResponseTagsWrapperProps) {
|
||||
export function Tag({
|
||||
tagId,
|
||||
tagName,
|
||||
onDelete,
|
||||
tags,
|
||||
setTagsState,
|
||||
highlight,
|
||||
allowDelete = true,
|
||||
}: ResponseTagsWrapperProps) {
|
||||
return (
|
||||
<div
|
||||
key={tagId}
|
||||
@@ -28,14 +37,16 @@ export function Tag({ tagId, tagName, onDelete, tags, setTagsState, highlight }:
|
||||
<span className="text-sm">{tagName}</span>
|
||||
</div>
|
||||
|
||||
<span
|
||||
className="cursor-pointer text-sm"
|
||||
onClick={() => {
|
||||
if (tags && setTagsState) setTagsState(tags.filter((tag) => tag.tagId !== tagId));
|
||||
onDelete(tagId);
|
||||
}}>
|
||||
<XCircleIcon fontSize={24} className="h-4 w-4 text-slate-100 hover:text-slate-200" />
|
||||
</span>
|
||||
{allowDelete && (
|
||||
<span
|
||||
className="cursor-pointer text-sm"
|
||||
onClick={() => {
|
||||
if (tags && setTagsState) setTagsState(tags.filter((tag) => tag.tagId !== tagId));
|
||||
onDelete(tagId);
|
||||
}}>
|
||||
<XCircleIcon fontSize={24} className="h-4 w-4 text-slate-100 hover:text-slate-200" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user