mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-20 11:22:55 -05:00
tweak UI
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { timeSince } from "@formbricks/lib/time";
|
||||
import { EyeSlashIcon, PlusIcon, EyeIcon } from "@heroicons/react/24/solid";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useResponses } from "@/lib/responses/responses";
|
||||
import { Button } from "@formbricks/ui";
|
||||
import clsx from "clsx";
|
||||
import { addResponseNote } from "@/lib/responseNotes/responsesNotes";
|
||||
import { FormEvent } from "react";
|
||||
import { OpenTextSummaryProps } from "@/app/environments/[environmentId]/surveys/[surveyId]/responses/SingleResponse";
|
||||
import { addResponseNote } from "@/lib/responseNotes/responsesNotes";
|
||||
import { useResponses } from "@/lib/responses/responses";
|
||||
import { timeSince } from "@formbricks/lib/time";
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
import { MinusIcon } from "@heroicons/react/24/solid";
|
||||
import clsx from "clsx";
|
||||
import { Maximize2Icon } from "lucide-react";
|
||||
import { FormEvent, useEffect, useRef, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
export default function ResponseNote({
|
||||
data,
|
||||
@@ -22,6 +23,8 @@ export default function ResponseNote({
|
||||
const [isCreatingNote, setIsCreatingNote] = useState(false);
|
||||
const { mutateResponses } = useResponses(environmentId, surveyId);
|
||||
const responseNotes = data?.responseNotes;
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleNoteSubmission = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsCreatingNote(true);
|
||||
@@ -36,12 +39,18 @@ export default function ResponseNote({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (divRef.current) {
|
||||
divRef.current.scrollTop = divRef.current.scrollHeight;
|
||||
}
|
||||
}, [responseNotes]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"absolute w-1/5 cursor-pointer rounded-lg border border-slate-200 shadow-sm transition-all",
|
||||
!isOpen && responseNotes.length && "bg-white",
|
||||
!isOpen && !responseNotes.length && "bg-slate-50",
|
||||
"absolute w-1/4 rounded-lg border border-slate-200 shadow-sm transition-all",
|
||||
!isOpen && responseNotes.length && "group/hint cursor-pointer bg-white hover:-right-3",
|
||||
!isOpen && !responseNotes.length && "cursor-pointer bg-slate-50",
|
||||
isOpen
|
||||
? "-right-5 top-0 h-full w-1/5 bg-white"
|
||||
: responseNotes.length
|
||||
@@ -56,7 +65,7 @@ export default function ResponseNote({
|
||||
<div
|
||||
className={clsx(
|
||||
"space-y-2 rounded-t-lg px-2 pb-2 pt-2",
|
||||
responseNotes.length ? "flex h-16 items-center justify-end bg-amber-50" : "bg-slate-200"
|
||||
responseNotes.length ? "flex h-12 items-center justify-end bg-amber-50" : "bg-slate-200"
|
||||
)}>
|
||||
{!responseNotes.length ? (
|
||||
<div className="flex items-center justify-end">
|
||||
@@ -66,43 +75,43 @@ export default function ResponseNote({
|
||||
</div>
|
||||
) : (
|
||||
<div className="float-left mr-1.5">
|
||||
<EyeIcon className="h-4 w-4 text-amber-400" />
|
||||
<Maximize2Icon className="h-4 w-4 text-amber-500 hover:text-amber-600 group-hover/hint:scale-110" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!responseNotes.length ? (
|
||||
<div className="flex flex-1 items-center justify-end pr-2">
|
||||
<button className="h-6 w-6 rounded-full bg-slate-600">
|
||||
<span>
|
||||
<PlusIcon className="text-white" />
|
||||
</span>
|
||||
</button>
|
||||
<div className="flex flex-1 items-center justify-end pr-3">
|
||||
<span>
|
||||
<PlusIcon className=" h-5 w-5 text-slate-400" />
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative flex h-full flex-col">
|
||||
<div className="rounded-t-lg bg-amber-50 px-4 pb-5 pt-6">
|
||||
<div className="rounded-t-lg bg-amber-50 px-4 pb-3 pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="group flex items-center">
|
||||
<h3 className="pb-1 text-sm text-slate-600">Note</h3>
|
||||
<h3 className="pb-1 text-sm text-slate-500">Note</h3>
|
||||
</div>
|
||||
<div
|
||||
className="h-4 w-4 cursor-pointer"
|
||||
<button
|
||||
className="h-6 w-6 cursor-pointer"
|
||||
onClick={() => {
|
||||
setIsOpen(!isOpen);
|
||||
}}>
|
||||
<EyeSlashIcon className="text-amber-400" />
|
||||
</div>
|
||||
<MinusIcon className="h-5 w-5 text-amber-500 hover:text-amber-600" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto px-4 pt-2">
|
||||
<div className="flex-1 overflow-auto px-4 pt-2" ref={divRef}>
|
||||
{responseNotes.map((note) => (
|
||||
<div className="mb-2" key={note.id}>
|
||||
<span className="block text-xs text-slate-500">
|
||||
{note.user.name} wrote{" "}
|
||||
<time className="text-slate-500" dateTime={timeSince(data.updatedAt)}>
|
||||
({timeSince(note.updatedAt)})
|
||||
<div className="mb-3" key={note.id}>
|
||||
<span className="block font-semibold text-slate-700">
|
||||
{note.user.name}
|
||||
<time
|
||||
className="ml-2 text-xs font-normal text-slate-500"
|
||||
dateTime={timeSince(data.updatedAt)}>
|
||||
{timeSince(note.updatedAt)}
|
||||
</time>
|
||||
</span>
|
||||
<span className="block text-slate-700">{note.text}</span>
|
||||
@@ -112,7 +121,7 @@ export default function ResponseNote({
|
||||
<div className="h-[120px]">
|
||||
<div
|
||||
className={clsx(
|
||||
"absolute bottom-0 w-full px-4 pb-2",
|
||||
"absolute bottom-0 w-full px-3 pb-3",
|
||||
!responseNotes.length && "absolute bottom-0"
|
||||
)}>
|
||||
<form onSubmit={handleNoteSubmission}>
|
||||
@@ -131,12 +140,8 @@ export default function ResponseNote({
|
||||
}}
|
||||
required></textarea>
|
||||
</div>
|
||||
<div className="mt-4 flex w-full justify-end">
|
||||
<Button
|
||||
className="bg-slate-600 hover:bg-slate-400"
|
||||
size="sm"
|
||||
type="submit"
|
||||
loading={isCreatingNote}>
|
||||
<div className="mt-2 flex w-full justify-end">
|
||||
<Button variant="darkCTA" size="sm" type="submit" loading={isCreatingNote}>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import toast from "react-hot-toast";
|
||||
import { RatingResponse } from "../RatingResponse";
|
||||
import { deleteSubmission, useResponses } from "@/lib/responses/responses";
|
||||
import clsx from "clsx";
|
||||
import ResponseNote from "@/app/environments/[environmentId]/surveys/[surveyId]/responses/ResponseNote";
|
||||
import ResponseNote from "./ResponseNote";
|
||||
|
||||
export interface OpenTextSummaryProps {
|
||||
data: {
|
||||
@@ -25,15 +25,15 @@ export interface OpenTextSummaryProps {
|
||||
environmentId: string;
|
||||
attributes: [];
|
||||
};
|
||||
responseNote: {
|
||||
responseNotes: {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
id: string;
|
||||
text: string;
|
||||
user: {
|
||||
name: string;
|
||||
}
|
||||
}[]
|
||||
};
|
||||
}[];
|
||||
value: string;
|
||||
updatedAt: string;
|
||||
finished: boolean;
|
||||
@@ -58,11 +58,11 @@ function findEmail(person) {
|
||||
export default function SingleResponse({ data, environmentId, surveyId }: OpenTextSummaryProps) {
|
||||
const email = data.person && findEmail(data.person);
|
||||
const displayIdentifier = email || data.personId;
|
||||
const responseNotes = data?.responseNote;
|
||||
const responseNotes = data?.responseNotes;
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const { mutateResponses } = useResponses(environmentId, surveyId);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const handleDeleteSubmission = async () => {
|
||||
setIsDeleting(true);
|
||||
@@ -74,11 +74,12 @@ export default function SingleResponse({ data, environmentId, surveyId }: OpenTe
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx("relative group", isOpen && "min-h-[300px]")}>
|
||||
<div className={clsx(
|
||||
"my-6 rounded-lg border transition-all border-slate-200 bg-slate-50 shadow-sm z-10 relative",
|
||||
isOpen ? "w-4/5" : responseNotes.length ? "w-[96.5%]" : "w-full group-hover:w-[96.5%]"
|
||||
)}>
|
||||
<div className={clsx("group relative", isOpen && "min-h-[300px]")}>
|
||||
<div
|
||||
className={clsx(
|
||||
"relative z-10 my-6 rounded-lg border border-slate-200 bg-slate-50 shadow-sm transition-all",
|
||||
isOpen ? "w-3/4" : responseNotes.length ? "w-[96.5%]" : "w-full group-hover:w-[96.5%]"
|
||||
)}>
|
||||
<div className="space-y-2 px-6 pb-5 pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
{data.personId ? (
|
||||
@@ -128,7 +129,9 @@ export default function SingleResponse({ data, environmentId, surveyId }: OpenTe
|
||||
<p className="ph-no-capture my-1 font-semibold text-slate-700">{response.answer}</p>
|
||||
)
|
||||
) : (
|
||||
<p className="ph-no-capture my-1 font-semibold text-slate-700">{response.answer.join(", ")}</p>
|
||||
<p className="ph-no-capture my-1 font-semibold text-slate-700">
|
||||
{response.answer.join(", ")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
@@ -141,7 +144,13 @@ export default function SingleResponse({ data, environmentId, surveyId }: OpenTe
|
||||
isDeleting={isDeleting}
|
||||
/>
|
||||
</div>
|
||||
<ResponseNote data={data} environmentId={environmentId} surveyId={surveyId} isOpen={isOpen} setIsOpen={setIsOpen} />
|
||||
<ResponseNote
|
||||
data={data}
|
||||
environmentId={environmentId}
|
||||
surveyId={surveyId}
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function MultipleChoiceMultiQuestion({
|
||||
<div className="mt-4">
|
||||
<fieldset>
|
||||
<legend className="sr-only">Options</legend>
|
||||
<div className="xs:max-h-[40vh] relative max-h-[60vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
|
||||
<div className="xs:max-h-[41vh] relative max-h-[60vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
|
||||
{question.choices &&
|
||||
question.choices.map((choice) => (
|
||||
<>
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function MultipleChoiceSingleQuestion({
|
||||
<div className="mt-4">
|
||||
<fieldset>
|
||||
<legend className="sr-only">Options</legend>
|
||||
<div className="xs:max-h-[40vh] relative max-h-[60vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
|
||||
<div className="xs:max-h-[41vh] relative max-h-[60vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
|
||||
{question.choices &&
|
||||
question.choices.map((choice, idx) => (
|
||||
<label
|
||||
|
||||
Reference in New Issue
Block a user