mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
fix: resolve metadata in hover confusion + other UI tweaks (#6821)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TIntegrationItem } from "@formbricks/types/integration";
|
||||
import { TIntegrationAirtable } from "@formbricks/types/integration/airtable";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
@@ -16,7 +15,6 @@ interface AirtableWrapperProps {
|
||||
airtableArray: TIntegrationItem[];
|
||||
airtableIntegration?: TIntegrationAirtable;
|
||||
surveys: TSurvey[];
|
||||
environment: TEnvironment;
|
||||
isEnabled: boolean;
|
||||
webAppUrl: string;
|
||||
locale: TUserLocale;
|
||||
@@ -27,7 +25,6 @@ export const AirtableWrapper = ({
|
||||
airtableArray,
|
||||
airtableIntegration,
|
||||
surveys,
|
||||
environment,
|
||||
isEnabled,
|
||||
webAppUrl,
|
||||
locale,
|
||||
@@ -48,7 +45,6 @@ export const AirtableWrapper = ({
|
||||
<ManageIntegration
|
||||
airtableArray={airtableArray}
|
||||
environmentId={environmentId}
|
||||
environment={environment}
|
||||
airtableIntegration={airtableIntegration}
|
||||
setIsConnected={setIsConnected}
|
||||
surveys={surveys}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Trash2Icon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TIntegrationItem } from "@formbricks/types/integration";
|
||||
import { TIntegrationAirtable } from "@formbricks/types/integration/airtable";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
@@ -15,12 +14,11 @@ import { timeSince } from "@/lib/time";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
|
||||
import { EmptySpaceFiller } from "@/modules/ui/components/empty-space-filler";
|
||||
import { EmptyState } from "@/modules/ui/components/empty-state";
|
||||
import { IntegrationModalInputs } from "../lib/types";
|
||||
|
||||
interface ManageIntegrationProps {
|
||||
airtableIntegration: TIntegrationAirtable;
|
||||
environment: TEnvironment;
|
||||
environmentId: string;
|
||||
setIsConnected: (data: boolean) => void;
|
||||
surveys: TSurvey[];
|
||||
@@ -29,7 +27,7 @@ interface ManageIntegrationProps {
|
||||
}
|
||||
|
||||
export const ManageIntegration = (props: ManageIntegrationProps) => {
|
||||
const { airtableIntegration, environment, environmentId, setIsConnected, surveys, airtableArray } = props;
|
||||
const { airtableIntegration, environmentId, setIsConnected, surveys, airtableArray } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const tableHeaders = [
|
||||
@@ -132,12 +130,7 @@ export const ManageIntegration = (props: ManageIntegrationProps) => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-4 w-full">
|
||||
<EmptySpaceFiller
|
||||
type="table"
|
||||
environment={environment}
|
||||
noWidgetRequired={true}
|
||||
emptyMessage={t("environments.integrations.airtable.no_integrations_yet")}
|
||||
/>
|
||||
<EmptyState text={t("environments.integrations.airtable.no_integrations_yet")} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ const Page = async (props) => {
|
||||
airtableArray={airtableArray}
|
||||
environmentId={environment.id}
|
||||
surveys={surveys}
|
||||
environment={environment}
|
||||
webAppUrl={WEBAPP_URL}
|
||||
locale={locale}
|
||||
/>
|
||||
|
||||
@@ -60,7 +60,6 @@ export const GoogleSheetWrapper = ({
|
||||
selectedIntegration={selectedIntegration}
|
||||
/>
|
||||
<ManageIntegration
|
||||
environment={environment}
|
||||
googleSheetIntegration={googleSheetIntegration}
|
||||
setOpenAddIntegrationModal={setIsModalOpen}
|
||||
setIsConnected={setIsConnected}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Trash2Icon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import {
|
||||
TIntegrationGoogleSheets,
|
||||
TIntegrationGoogleSheetsConfigData,
|
||||
@@ -15,10 +14,9 @@ import { timeSince } from "@/lib/time";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
|
||||
import { EmptySpaceFiller } from "@/modules/ui/components/empty-space-filler";
|
||||
import { EmptyState } from "@/modules/ui/components/empty-state";
|
||||
|
||||
interface ManageIntegrationProps {
|
||||
environment: TEnvironment;
|
||||
googleSheetIntegration: TIntegrationGoogleSheets;
|
||||
setOpenAddIntegrationModal: (v: boolean) => void;
|
||||
setIsConnected: (v: boolean) => void;
|
||||
@@ -27,7 +25,6 @@ interface ManageIntegrationProps {
|
||||
}
|
||||
|
||||
export const ManageIntegration = ({
|
||||
environment,
|
||||
googleSheetIntegration,
|
||||
setOpenAddIntegrationModal,
|
||||
setIsConnected,
|
||||
@@ -90,12 +87,7 @@ export const ManageIntegration = ({
|
||||
</div>
|
||||
{!integrationArray || integrationArray.length === 0 ? (
|
||||
<div className="mt-4 w-full">
|
||||
<EmptySpaceFiller
|
||||
type="table"
|
||||
environment={environment}
|
||||
noWidgetRequired={true}
|
||||
emptyMessage={t("environments.integrations.google_sheets.no_integrations_yet")}
|
||||
/>
|
||||
<EmptyState text={t("environments.integrations.google_sheets.no_integrations_yet")} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-4 flex w-full flex-col items-center justify-center">
|
||||
|
||||
@@ -4,7 +4,6 @@ import { RefreshCcwIcon, Trash2Icon } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TIntegrationNotion, TIntegrationNotionConfigData } from "@formbricks/types/integration/notion";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { deleteIntegrationAction } from "@/app/(app)/environments/[environmentId]/project/integrations/actions";
|
||||
@@ -12,11 +11,10 @@ import { timeSince } from "@/lib/time";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
|
||||
import { EmptySpaceFiller } from "@/modules/ui/components/empty-space-filler";
|
||||
import { EmptyState } from "@/modules/ui/components/empty-state";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/modules/ui/components/tooltip";
|
||||
|
||||
interface ManageIntegrationProps {
|
||||
environment: TEnvironment;
|
||||
notionIntegration: TIntegrationNotion;
|
||||
setOpenAddIntegrationModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setIsConnected: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
@@ -28,7 +26,6 @@ interface ManageIntegrationProps {
|
||||
}
|
||||
|
||||
export const ManageIntegration = ({
|
||||
environment,
|
||||
notionIntegration,
|
||||
setOpenAddIntegrationModal,
|
||||
setIsConnected,
|
||||
@@ -101,12 +98,7 @@ export const ManageIntegration = ({
|
||||
</div>
|
||||
{!integrationArray || integrationArray.length === 0 ? (
|
||||
<div className="mt-4 w-full">
|
||||
<EmptySpaceFiller
|
||||
type="table"
|
||||
environment={environment}
|
||||
noWidgetRequired={true}
|
||||
emptyMessage={t("environments.integrations.notion.no_databases_found")}
|
||||
/>
|
||||
<EmptyState text={t("environments.integrations.notion.no_databases_found")} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-4 flex w-full flex-col items-center justify-center">
|
||||
|
||||
@@ -64,7 +64,6 @@ export const NotionWrapper = ({
|
||||
selectedIntegration={selectedIntegration}
|
||||
/>
|
||||
<ManageIntegration
|
||||
environment={environment}
|
||||
notionIntegration={notionIntegration}
|
||||
setOpenAddIntegrationModal={setIsModalOpen}
|
||||
setIsConnected={setIsConnected}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Trash2Icon } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TIntegrationSlack, TIntegrationSlackConfigData } from "@formbricks/types/integration/slack";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { deleteIntegrationAction } from "@/app/(app)/environments/[environmentId]/project/integrations/actions";
|
||||
@@ -12,10 +11,9 @@ import { timeSince } from "@/lib/time";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
|
||||
import { EmptySpaceFiller } from "@/modules/ui/components/empty-space-filler";
|
||||
import { EmptyState } from "@/modules/ui/components/empty-state";
|
||||
|
||||
interface ManageIntegrationProps {
|
||||
environment: TEnvironment;
|
||||
slackIntegration: TIntegrationSlack;
|
||||
setOpenAddIntegrationModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setIsConnected: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
@@ -29,7 +27,6 @@ interface ManageIntegrationProps {
|
||||
}
|
||||
|
||||
export const ManageIntegration = ({
|
||||
environment,
|
||||
slackIntegration,
|
||||
setOpenAddIntegrationModal,
|
||||
setIsConnected,
|
||||
@@ -106,12 +103,7 @@ export const ManageIntegration = ({
|
||||
</div>
|
||||
{!integrationArray || integrationArray.length === 0 ? (
|
||||
<div className="mt-4 w-full">
|
||||
<EmptySpaceFiller
|
||||
type="table"
|
||||
environment={environment}
|
||||
noWidgetRequired={true}
|
||||
emptyMessage={t("environments.integrations.slack.connect_your_first_slack_channel")}
|
||||
/>
|
||||
<EmptyState text={t("environments.integrations.slack.connect_your_first_slack_channel")} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-4 flex w-full flex-col items-center justify-center">
|
||||
|
||||
@@ -78,7 +78,6 @@ export const SlackWrapper = ({
|
||||
selectedIntegration={selectedIntegration}
|
||||
/>
|
||||
<ManageIntegration
|
||||
environment={environment}
|
||||
slackIntegration={slackIntegration}
|
||||
setOpenAddIntegrationModal={setIsModalOpen}
|
||||
setIsConnected={setIsConnected}
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TI18nString, TSurveyQuestionId, TSurveySummary } from "@formbricks/types/surveys/types";
|
||||
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import {
|
||||
TI18nString,
|
||||
TSurvey,
|
||||
TSurveyQuestionId,
|
||||
TSurveyQuestionTypeEnum,
|
||||
TSurveySummary,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import {
|
||||
SelectedFilterValue,
|
||||
@@ -29,7 +33,7 @@ import { RatingSummary } from "@/app/(app)/environments/[environmentId]/surveys/
|
||||
import { constructToastMessage } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils";
|
||||
import { OptionsType } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/QuestionsComboBox";
|
||||
import { getLocalizedValue } from "@/lib/i18n/utils";
|
||||
import { EmptySpaceFiller } from "@/modules/ui/components/empty-space-filler";
|
||||
import { EmptyState } from "@/modules/ui/components/empty-state";
|
||||
import { SkeletonLoader } from "@/modules/ui/components/skeleton-loader";
|
||||
import { AddressSummary } from "./AddressSummary";
|
||||
|
||||
@@ -103,12 +107,7 @@ export const SummaryList = ({ summary, environment, responseCount, survey, local
|
||||
) : summary.length === 0 ? (
|
||||
<SkeletonLoader type="summary" />
|
||||
) : responseCount === 0 ? (
|
||||
<EmptySpaceFiller
|
||||
type="response"
|
||||
environment={environment}
|
||||
noWidgetRequired={survey.type === "link"}
|
||||
emptyMessage={t("environments.surveys.summary.no_responses_found")}
|
||||
/>
|
||||
<EmptyState text={t("environments.surveys.summary.no_responses_found")} />
|
||||
) : (
|
||||
summary.map((questionSummary) => {
|
||||
if (questionSummary.type === TSurveyQuestionTypeEnum.OpenText) {
|
||||
|
||||
@@ -811,7 +811,6 @@ checksums:
|
||||
environments/project/tags/add_tag: 2cfa04ceea966149f2b5d40d9c131141
|
||||
environments/project/tags/count: 9c5848662eb8024ddf360f7e4001a968
|
||||
environments/project/tags/delete_tag_confirmation: a9fb98064cd156242899643f3d2ef032
|
||||
environments/project/tags/empty_message: da71bd7c7b5bf634469d20e010d25503
|
||||
environments/project/tags/manage_tags: 2761d558b82b6104befbc240ae2379c6
|
||||
environments/project/tags/manage_tags_description: ce7cc42da3646fba960502d7e4e49cd2
|
||||
environments/project/tags/merge: 95051c859b8778be51226b43be6f1075
|
||||
@@ -1600,7 +1599,7 @@ checksums:
|
||||
environments/surveys/responses/last_name: 2c9a7de7738ca007ba9023c385149c26
|
||||
environments/surveys/responses/not_completed: df34eab65a6291f2c5e15a0e349c4eba
|
||||
environments/surveys/responses/os: a4c753bb2c004a58d02faeed6b4da476
|
||||
environments/surveys/responses/person_attributes: 8f7f8a9040ce8efb3cb54ce33b590866
|
||||
environments/surveys/responses/person_attributes: 07ae67ae73d7a2a7c67008694a83f0a3
|
||||
environments/surveys/responses/phone: b9537ee90fc5b0116942e0af29d926cc
|
||||
environments/surveys/responses/respondent_skipped_questions: d85daf579ade534dc7e639689156fcd5
|
||||
environments/surveys/responses/response_deleted_successfully: 6cec5427c271800619fee8c812d7db18
|
||||
@@ -1718,7 +1717,6 @@ checksums:
|
||||
environments/surveys/summary/filtered_responses_csv: aad66a98be6a09cac8bef9e4db4a75cf
|
||||
environments/surveys/summary/filtered_responses_excel: 06e57bae9e41979fd7fc4b8bfe3466f9
|
||||
environments/surveys/summary/generating_qr_code: 5026d4a76f995db458195e5215d9bbd9
|
||||
environments/surveys/summary/go_to_setup_checklist: d70bd018d651d01c41ae10370e71d0be
|
||||
environments/surveys/summary/impressions: 7fe38d42d68a64d3fd8436a063751584
|
||||
environments/surveys/summary/impressions_tooltip: 4d0823cbf360304770c7c5913e33fdc8
|
||||
environments/surveys/summary/in_app/connection_description: 9710bbf8048a8a5c3b2b56db9d946b73
|
||||
@@ -1750,7 +1748,6 @@ checksums:
|
||||
environments/surveys/summary/in_app/title: a2d1b633244d0e0504ec6f8f561c7a6b
|
||||
environments/surveys/summary/includes_all: b0e3679282417c62d511c258362f860e
|
||||
environments/surveys/summary/includes_either: 186d6923c1693e80d7b664b8367d4221
|
||||
environments/surveys/summary/install_widget: 55d403de32e3d0da7513ab199f1d1934
|
||||
environments/surveys/summary/is_equal_to: f4aab30ef188eb25dcc0e392cf8e86bb
|
||||
environments/surveys/summary/is_less_than: 6109d595ba21497c59b1c91d7fd09a13
|
||||
environments/surveys/summary/last_30_days: a738894cfc5e592052f1e16787744568
|
||||
@@ -1787,7 +1784,6 @@ checksums:
|
||||
environments/surveys/summary/ttc_tooltip: 9b1cbe32cc81111314bd3b6fd050c2e7
|
||||
environments/surveys/summary/unknown_question_type: e4152a7457d2b94f48dcc70aaba9922f
|
||||
environments/surveys/summary/use_personal_links: da2b3e7e1aaf2ea2bd4efed2dda4247c
|
||||
environments/surveys/summary/waiting_for_response: 0194a84e0850b8e98435632d5331a916
|
||||
environments/surveys/summary/whats_next: d920145bfa2147014062f6f2d1d451a4
|
||||
environments/surveys/summary/your_survey_is_public: 3f5cb5949a5f4020a3d4d74fdfc95e83
|
||||
environments/surveys/summary/youre_not_plugged_in_yet: 9217467742cdcf7edf8d59cc1472ede6
|
||||
|
||||
@@ -872,7 +872,6 @@
|
||||
"add_tag": "Tag hinzufügen",
|
||||
"count": "zählen",
|
||||
"delete_tag_confirmation": "Bist Du sicher, dass Du diesen Tag löschen möchtest?",
|
||||
"empty_message": "Markiere eine Antwort, um deine Liste der Tags hier zu finden.",
|
||||
"manage_tags": "Tags verwalten",
|
||||
"manage_tags_description": "Zusammenführen und Antwort-Tags entfernen.",
|
||||
"merge": "Zusammenführen",
|
||||
@@ -1691,7 +1690,7 @@
|
||||
"last_name": "Nachname",
|
||||
"not_completed": "Nicht abgeschlossen ⏳",
|
||||
"os": "Betriebssystem",
|
||||
"person_attributes": "Personenattribute",
|
||||
"person_attributes": "Personenattribute zum Zeitpunkt der Einreichung",
|
||||
"phone": "Telefon",
|
||||
"respondent_skipped_questions": "Der Befragte hat diese Fragen übersprungen.",
|
||||
"response_deleted_successfully": "Antwort erfolgreich gelöscht.",
|
||||
@@ -1827,7 +1826,6 @@
|
||||
"filtered_responses_csv": "Gefilterte Antworten (CSV)",
|
||||
"filtered_responses_excel": "Gefilterte Antworten (Excel)",
|
||||
"generating_qr_code": "QR-Code wird generiert",
|
||||
"go_to_setup_checklist": "Gehe zur Einrichtungs-Checkliste 👉",
|
||||
"impressions": "Eindrücke",
|
||||
"impressions_tooltip": "Anzahl der Aufrufe der Umfrage.",
|
||||
"in_app": {
|
||||
@@ -1861,7 +1859,6 @@
|
||||
},
|
||||
"includes_all": "Beinhaltet alles",
|
||||
"includes_either": "Beinhaltet entweder",
|
||||
"install_widget": "Formbricks Widget installieren",
|
||||
"is_equal_to": "Ist gleich",
|
||||
"is_less_than": "ist weniger als",
|
||||
"last_30_days": "Letzte 30 Tage",
|
||||
@@ -1898,7 +1895,6 @@
|
||||
"ttc_tooltip": "Durchschnittliche Zeit zum Beantworten der Frage.",
|
||||
"unknown_question_type": "Unbekannter Fragetyp",
|
||||
"use_personal_links": "Nutze persönliche Links",
|
||||
"waiting_for_response": "Warte auf eine Antwort 🧘♂️",
|
||||
"whats_next": "Was kommt als Nächstes?",
|
||||
"your_survey_is_public": "Deine Umfrage ist öffentlich",
|
||||
"youre_not_plugged_in_yet": "Du bist noch nicht verbunden!"
|
||||
|
||||
@@ -872,7 +872,6 @@
|
||||
"add_tag": "Add Tag",
|
||||
"count": "Count",
|
||||
"delete_tag_confirmation": "Are you sure you want to delete this tag?",
|
||||
"empty_message": "Tag a submission to find your list of tags here.",
|
||||
"manage_tags": "Manage Tags",
|
||||
"manage_tags_description": "Merge and remove response tags.",
|
||||
"merge": "Merge",
|
||||
@@ -1691,7 +1690,7 @@
|
||||
"last_name": "Last Name",
|
||||
"not_completed": "Not Completed ⏳",
|
||||
"os": "OS",
|
||||
"person_attributes": "Person attributes",
|
||||
"person_attributes": "Person attributes at time of submission",
|
||||
"phone": "Phone",
|
||||
"respondent_skipped_questions": "Respondent skipped these questions.",
|
||||
"response_deleted_successfully": "Response deleted successfully.",
|
||||
@@ -1827,7 +1826,6 @@
|
||||
"filtered_responses_csv": "Filtered responses (CSV)",
|
||||
"filtered_responses_excel": "Filtered responses (Excel)",
|
||||
"generating_qr_code": "Generating QR code",
|
||||
"go_to_setup_checklist": "Go to Setup Checklist \uD83D\uDC49",
|
||||
"impressions": "Impressions",
|
||||
"impressions_tooltip": "Number of times the survey has been viewed.",
|
||||
"in_app": {
|
||||
@@ -1861,7 +1859,6 @@
|
||||
},
|
||||
"includes_all": "Includes all",
|
||||
"includes_either": "Includes either",
|
||||
"install_widget": "Install Formbricks Widget",
|
||||
"is_equal_to": "Is equal to",
|
||||
"is_less_than": "Is less than",
|
||||
"last_30_days": "Last 30 days",
|
||||
@@ -1898,7 +1895,6 @@
|
||||
"ttc_tooltip": "Average time to complete the question.",
|
||||
"unknown_question_type": "Unknown Question Type",
|
||||
"use_personal_links": "Use personal links",
|
||||
"waiting_for_response": "Waiting for a response \uD83E\uDDD8♂️",
|
||||
"whats_next": "What's next?",
|
||||
"your_survey_is_public": "Your survey is public",
|
||||
"youre_not_plugged_in_yet": "You're not plugged in yet!"
|
||||
|
||||
@@ -872,7 +872,6 @@
|
||||
"add_tag": "Añadir etiqueta",
|
||||
"count": "Recuento",
|
||||
"delete_tag_confirmation": "¿Estás seguro de que quieres eliminar esta etiqueta?",
|
||||
"empty_message": "Etiqueta un envío para encontrar tu lista de etiquetas aquí.",
|
||||
"manage_tags": "Gestionar etiquetas",
|
||||
"manage_tags_description": "Fusionar y eliminar etiquetas de respuesta.",
|
||||
"merge": "Fusionar",
|
||||
@@ -1691,7 +1690,7 @@
|
||||
"last_name": "Apellido",
|
||||
"not_completed": "No completado ⏳",
|
||||
"os": "Sistema operativo",
|
||||
"person_attributes": "Atributos de persona",
|
||||
"person_attributes": "Atributos de la persona en el momento del envío",
|
||||
"phone": "Teléfono",
|
||||
"respondent_skipped_questions": "El encuestado omitió estas preguntas.",
|
||||
"response_deleted_successfully": "Respuesta eliminada correctamente.",
|
||||
@@ -1827,7 +1826,6 @@
|
||||
"filtered_responses_csv": "Respuestas filtradas (CSV)",
|
||||
"filtered_responses_excel": "Respuestas filtradas (Excel)",
|
||||
"generating_qr_code": "Generando código QR",
|
||||
"go_to_setup_checklist": "Ir a la lista de configuración 👉",
|
||||
"impressions": "Impresiones",
|
||||
"impressions_tooltip": "Número de veces que se ha visto la encuesta.",
|
||||
"in_app": {
|
||||
@@ -1861,7 +1859,6 @@
|
||||
},
|
||||
"includes_all": "Incluye todo",
|
||||
"includes_either": "Incluye cualquiera",
|
||||
"install_widget": "Instalar widget de Formbricks",
|
||||
"is_equal_to": "Es igual a",
|
||||
"is_less_than": "Es menor que",
|
||||
"last_30_days": "Últimos 30 días",
|
||||
@@ -1898,7 +1895,6 @@
|
||||
"ttc_tooltip": "Tiempo medio para completar la pregunta.",
|
||||
"unknown_question_type": "Tipo de pregunta desconocido",
|
||||
"use_personal_links": "Usar enlaces personales",
|
||||
"waiting_for_response": "Esperando una respuesta 🧘♂️",
|
||||
"whats_next": "¿Qué sigue?",
|
||||
"your_survey_is_public": "Tu encuesta es pública",
|
||||
"youre_not_plugged_in_yet": "¡Aún no estás conectado!"
|
||||
|
||||
@@ -872,7 +872,6 @@
|
||||
"add_tag": "Ajouter une étiquette",
|
||||
"count": "Compter",
|
||||
"delete_tag_confirmation": "Êtes-vous sûr de vouloir supprimer cette étiquette ?",
|
||||
"empty_message": "Ajoutez une balise à une réponse pour afficher votre liste de balises.",
|
||||
"manage_tags": "Gérer les étiquettes",
|
||||
"manage_tags_description": "Vous pouvez fusionner et supprimer des balises de réponse.",
|
||||
"merge": "Fusionner",
|
||||
@@ -1691,7 +1690,7 @@
|
||||
"last_name": "Nom de famille",
|
||||
"not_completed": "Non terminé ⏳",
|
||||
"os": "Système d'exploitation",
|
||||
"person_attributes": "Attributs de la personne",
|
||||
"person_attributes": "Attributs de la personne au moment de la soumission",
|
||||
"phone": "Téléphone",
|
||||
"respondent_skipped_questions": "Le répondant a sauté ces questions.",
|
||||
"response_deleted_successfully": "Réponse supprimée avec succès.",
|
||||
@@ -1827,7 +1826,6 @@
|
||||
"filtered_responses_csv": "Réponses filtrées (CSV)",
|
||||
"filtered_responses_excel": "Réponses filtrées (Excel)",
|
||||
"generating_qr_code": "Génération du code QR",
|
||||
"go_to_setup_checklist": "Allez à la liste de contrôle de configuration 👉",
|
||||
"impressions": "Impressions",
|
||||
"impressions_tooltip": "Nombre de fois que l'enquête a été consultée.",
|
||||
"in_app": {
|
||||
@@ -1861,7 +1859,6 @@
|
||||
},
|
||||
"includes_all": "Comprend tous",
|
||||
"includes_either": "Comprend soit",
|
||||
"install_widget": "Installer le widget Formbricks",
|
||||
"is_equal_to": "Est égal à",
|
||||
"is_less_than": "est inférieur à",
|
||||
"last_30_days": "30 derniers jours",
|
||||
@@ -1898,7 +1895,6 @@
|
||||
"ttc_tooltip": "Temps moyen pour compléter la question.",
|
||||
"unknown_question_type": "Type de question inconnu",
|
||||
"use_personal_links": "Utilisez des liens personnels",
|
||||
"waiting_for_response": "En attente d'une réponse 🧘♂️",
|
||||
"whats_next": "Qu'est-ce qui vient ensuite ?",
|
||||
"your_survey_is_public": "Votre enquête est publique.",
|
||||
"youre_not_plugged_in_yet": "Vous n'êtes pas encore branché !"
|
||||
|
||||
@@ -872,7 +872,6 @@
|
||||
"add_tag": "タグを追加",
|
||||
"count": "件数",
|
||||
"delete_tag_confirmation": "このタグを削除してもよろしいですか?",
|
||||
"empty_message": "送信にタグ付けすると、ここにタグ一覧が表示されます。",
|
||||
"manage_tags": "タグを管理",
|
||||
"manage_tags_description": "回答タグを統合・削除します。",
|
||||
"merge": "統合",
|
||||
@@ -1691,7 +1690,7 @@
|
||||
"last_name": "姓",
|
||||
"not_completed": "未完了 ⏳",
|
||||
"os": "OS",
|
||||
"person_attributes": "人物属性",
|
||||
"person_attributes": "回答時の個人属性",
|
||||
"phone": "電話",
|
||||
"respondent_skipped_questions": "回答者はこれらの質問をスキップしました。",
|
||||
"response_deleted_successfully": "回答を正常に削除しました。",
|
||||
@@ -1827,7 +1826,6 @@
|
||||
"filtered_responses_csv": "フィルター済み回答 (CSV)",
|
||||
"filtered_responses_excel": "フィルター済み回答 (Excel)",
|
||||
"generating_qr_code": "QRコードを生成中",
|
||||
"go_to_setup_checklist": "セットアップチェックリストへ移動 👉",
|
||||
"impressions": "表示回数",
|
||||
"impressions_tooltip": "フォームが表示された回数。",
|
||||
"in_app": {
|
||||
@@ -1861,7 +1859,6 @@
|
||||
},
|
||||
"includes_all": "すべてを含む",
|
||||
"includes_either": "どちらかを含む",
|
||||
"install_widget": "Formbricksウィジェットをインストール",
|
||||
"is_equal_to": "と等しい",
|
||||
"is_less_than": "より小さい",
|
||||
"last_30_days": "過去30日間",
|
||||
@@ -1898,7 +1895,6 @@
|
||||
"ttc_tooltip": "フォームを完了するまでの平均時間。",
|
||||
"unknown_question_type": "不明な質問の種類",
|
||||
"use_personal_links": "個人リンクを使用",
|
||||
"waiting_for_response": "回答を待っています 🧘♂️",
|
||||
"whats_next": "次は何をしますか?",
|
||||
"your_survey_is_public": "あなたのフォームは公開されています",
|
||||
"youre_not_plugged_in_yet": "まだ接続されていません!"
|
||||
|
||||
@@ -872,7 +872,6 @@
|
||||
"add_tag": "Label toevoegen",
|
||||
"count": "Graaf",
|
||||
"delete_tag_confirmation": "Weet u zeker dat u deze tag wilt verwijderen?",
|
||||
"empty_message": "Tag een inzending om hier uw lijst met tags te vinden.",
|
||||
"manage_tags": "Beheer tags",
|
||||
"manage_tags_description": "Reactietags samenvoegen en verwijderen.",
|
||||
"merge": "Samenvoegen",
|
||||
@@ -1691,7 +1690,7 @@
|
||||
"last_name": "Achternaam",
|
||||
"not_completed": "Niet voltooid ⏳",
|
||||
"os": "Besturingssysteem",
|
||||
"person_attributes": "Persoonsattributen",
|
||||
"person_attributes": "Persoonskenmerken op het moment van indiening",
|
||||
"phone": "Telefoon",
|
||||
"respondent_skipped_questions": "Respondent heeft deze vragen overgeslagen.",
|
||||
"response_deleted_successfully": "Reactie is succesvol verwijderd.",
|
||||
@@ -1827,7 +1826,6 @@
|
||||
"filtered_responses_csv": "Gefilterde reacties (CSV)",
|
||||
"filtered_responses_excel": "Gefilterde reacties (Excel)",
|
||||
"generating_qr_code": "QR-code genereren",
|
||||
"go_to_setup_checklist": "Ga naar Installatiechecklist 👉",
|
||||
"impressions": "Indrukken",
|
||||
"impressions_tooltip": "Aantal keren dat de enquête is bekeken.",
|
||||
"in_app": {
|
||||
@@ -1861,7 +1859,6 @@
|
||||
},
|
||||
"includes_all": "Inclusief alles",
|
||||
"includes_either": "Inclusief beide",
|
||||
"install_widget": "Installeer Formbricks-widget",
|
||||
"is_equal_to": "Is gelijk aan",
|
||||
"is_less_than": "Is minder dan",
|
||||
"last_30_days": "Laatste 30 dagen",
|
||||
@@ -1898,7 +1895,6 @@
|
||||
"ttc_tooltip": "Gemiddelde tijd om de vraag te beantwoorden.",
|
||||
"unknown_question_type": "Onbekend vraagtype",
|
||||
"use_personal_links": "Gebruik persoonlijke links",
|
||||
"waiting_for_response": "Wachten op een reactie 🧘♂️",
|
||||
"whats_next": "Wat is het volgende?",
|
||||
"your_survey_is_public": "Uw enquête is openbaar",
|
||||
"youre_not_plugged_in_yet": "Je bent nog niet aangesloten!"
|
||||
|
||||
@@ -872,7 +872,6 @@
|
||||
"add_tag": "Adicionar Tag",
|
||||
"count": "Contar",
|
||||
"delete_tag_confirmation": "Tem certeza de que quer deletar essa tag?",
|
||||
"empty_message": "Marque uma submissão para encontrar sua lista de tags aqui.",
|
||||
"manage_tags": "Gerenciar Tags",
|
||||
"manage_tags_description": "Mesclar e remover tags de resposta.",
|
||||
"merge": "mesclar",
|
||||
@@ -1691,7 +1690,7 @@
|
||||
"last_name": "Sobrenome",
|
||||
"not_completed": "Não Concluído ⏳",
|
||||
"os": "sistema operacional",
|
||||
"person_attributes": "Atributos da pessoa",
|
||||
"person_attributes": "Atributos da pessoa no momento do envio",
|
||||
"phone": "Celular",
|
||||
"respondent_skipped_questions": "Respondente pulou essas perguntas.",
|
||||
"response_deleted_successfully": "Resposta deletada com sucesso.",
|
||||
@@ -1827,7 +1826,6 @@
|
||||
"filtered_responses_csv": "Respostas filtradas (CSV)",
|
||||
"filtered_responses_excel": "Respostas filtradas (Excel)",
|
||||
"generating_qr_code": "Gerando código QR",
|
||||
"go_to_setup_checklist": "Vai para a Lista de Configuração 👉",
|
||||
"impressions": "Impressões",
|
||||
"impressions_tooltip": "Número de vezes que a pesquisa foi visualizada.",
|
||||
"in_app": {
|
||||
@@ -1861,7 +1859,6 @@
|
||||
},
|
||||
"includes_all": "Inclui tudo",
|
||||
"includes_either": "Inclui ou",
|
||||
"install_widget": "Instalar Widget do Formbricks",
|
||||
"is_equal_to": "É igual a",
|
||||
"is_less_than": "É menor que",
|
||||
"last_30_days": "Últimos 30 dias",
|
||||
@@ -1898,7 +1895,6 @@
|
||||
"ttc_tooltip": "Tempo médio para completar a pergunta.",
|
||||
"unknown_question_type": "Tipo de pergunta desconhecido",
|
||||
"use_personal_links": "Use links pessoais",
|
||||
"waiting_for_response": "Aguardando uma resposta 🧘♂️",
|
||||
"whats_next": "E agora?",
|
||||
"your_survey_is_public": "Sua pesquisa é pública",
|
||||
"youre_not_plugged_in_yet": "Você ainda não tá conectado!"
|
||||
|
||||
@@ -872,7 +872,6 @@
|
||||
"add_tag": "Adicionar Etiqueta",
|
||||
"count": "Contagem",
|
||||
"delete_tag_confirmation": "Tem a certeza de que deseja eliminar esta etiqueta?",
|
||||
"empty_message": "Crie etiquetas para as suas submissões e veja-as aqui",
|
||||
"manage_tags": "Gerir Etiquetas",
|
||||
"manage_tags_description": "Junte e remova etiquetas de resposta",
|
||||
"merge": "Fundir",
|
||||
@@ -1691,7 +1690,7 @@
|
||||
"last_name": "Apelido",
|
||||
"not_completed": "Não Concluído ⏳",
|
||||
"os": "SO",
|
||||
"person_attributes": "Atributos da pessoa",
|
||||
"person_attributes": "Atributos da pessoa no momento da submissão",
|
||||
"phone": "Telefone",
|
||||
"respondent_skipped_questions": "O respondente saltou estas perguntas.",
|
||||
"response_deleted_successfully": "Resposta eliminada com sucesso.",
|
||||
@@ -1827,7 +1826,6 @@
|
||||
"filtered_responses_csv": "Respostas filtradas (CSV)",
|
||||
"filtered_responses_excel": "Respostas filtradas (Excel)",
|
||||
"generating_qr_code": "A gerar código QR",
|
||||
"go_to_setup_checklist": "Ir para a Lista de Verificação de Configuração 👉",
|
||||
"impressions": "Impressões",
|
||||
"impressions_tooltip": "Número de vezes que o inquérito foi visualizado.",
|
||||
"in_app": {
|
||||
@@ -1861,7 +1859,6 @@
|
||||
},
|
||||
"includes_all": "Inclui tudo",
|
||||
"includes_either": "Inclui qualquer um",
|
||||
"install_widget": "Instalar Widget Formbricks",
|
||||
"is_equal_to": "É igual a",
|
||||
"is_less_than": "É menos que",
|
||||
"last_30_days": "Últimos 30 dias",
|
||||
@@ -1898,7 +1895,6 @@
|
||||
"ttc_tooltip": "Tempo médio para concluir a pergunta.",
|
||||
"unknown_question_type": "Tipo de Pergunta Desconhecido",
|
||||
"use_personal_links": "Utilize links pessoais",
|
||||
"waiting_for_response": "A aguardar uma resposta 🧘♂️",
|
||||
"whats_next": "O que se segue?",
|
||||
"your_survey_is_public": "O seu inquérito é público",
|
||||
"youre_not_plugged_in_yet": "Ainda não está ligado!"
|
||||
|
||||
@@ -872,7 +872,6 @@
|
||||
"add_tag": "Adaugă Etichetă",
|
||||
"count": "Număr",
|
||||
"delete_tag_confirmation": "Sigur doriți să ștergeți această etichetă?",
|
||||
"empty_message": "Marcați o trimitere pentru a găsi lista de etichete aici.",
|
||||
"manage_tags": "Gestionați etichetele",
|
||||
"manage_tags_description": "Îmbinați și eliminați etichetele de răspuns.",
|
||||
"merge": "Îmbinare",
|
||||
@@ -1691,7 +1690,7 @@
|
||||
"last_name": "Nume de familie",
|
||||
"not_completed": "Necompletat ⏳",
|
||||
"os": "SO",
|
||||
"person_attributes": "Atribute persoană",
|
||||
"person_attributes": "Atributele persoanei la momentul trimiterii",
|
||||
"phone": "Telefon",
|
||||
"respondent_skipped_questions": "Respondenții au sărit peste aceste întrebări.",
|
||||
"response_deleted_successfully": "Răspuns șters cu succes.",
|
||||
@@ -1827,7 +1826,6 @@
|
||||
"filtered_responses_csv": "Răspunsuri filtrate (CSV)",
|
||||
"filtered_responses_excel": "Răspunsuri filtrate (Excel)",
|
||||
"generating_qr_code": "Se generează codul QR",
|
||||
"go_to_setup_checklist": "Mergi la lista de verificare a configurării 👉",
|
||||
"impressions": "Impresii",
|
||||
"impressions_tooltip": "Număr de ori când sondajul a fost vizualizat.",
|
||||
"in_app": {
|
||||
@@ -1861,7 +1859,6 @@
|
||||
},
|
||||
"includes_all": "Include tot",
|
||||
"includes_either": "Include fie",
|
||||
"install_widget": "Instalați Widgetul Formbricks",
|
||||
"is_equal_to": "Este egal cu",
|
||||
"is_less_than": "Este mai puțin de",
|
||||
"last_30_days": "Ultimele 30 de zile",
|
||||
@@ -1898,7 +1895,6 @@
|
||||
"ttc_tooltip": "Timp mediu pentru a completa întrebarea.",
|
||||
"unknown_question_type": "Tip de întrebare necunoscut",
|
||||
"use_personal_links": "Folosește linkuri personale",
|
||||
"waiting_for_response": "Așteptând un răspuns 🧘♂️",
|
||||
"whats_next": "Ce urmează?",
|
||||
"your_survey_is_public": "Sondajul tău este public",
|
||||
"youre_not_plugged_in_yet": "Nu sunteţi încă conectat!"
|
||||
|
||||
@@ -872,7 +872,6 @@
|
||||
"add_tag": "添加 标签",
|
||||
"count": "数量",
|
||||
"delete_tag_confirmation": "您 确定 要 删除 此 标签 吗?",
|
||||
"empty_message": "标记一个提交以在此处找到您的标签列表。",
|
||||
"manage_tags": "管理标签",
|
||||
"manage_tags_description": "合并 和 删除 response 标签。",
|
||||
"merge": "合并",
|
||||
@@ -1691,7 +1690,7 @@
|
||||
"last_name": "姓",
|
||||
"not_completed": "未完成 ⏳",
|
||||
"os": "操作系统",
|
||||
"person_attributes": "人员 属性",
|
||||
"person_attributes": "提交时的个人属性",
|
||||
"phone": "电话",
|
||||
"respondent_skipped_questions": "受访者跳过 这些问题。",
|
||||
"response_deleted_successfully": "响应 删除 成功",
|
||||
@@ -1827,7 +1826,6 @@
|
||||
"filtered_responses_csv": "过滤 反馈 (CSV)",
|
||||
"filtered_responses_excel": "过滤 反馈 (Excel)",
|
||||
"generating_qr_code": "正在生成二维码",
|
||||
"go_to_setup_checklist": "前往 设置 检查列表 👉",
|
||||
"impressions": "印象",
|
||||
"impressions_tooltip": "调查 被 查看 的 次数",
|
||||
"in_app": {
|
||||
@@ -1861,7 +1859,6 @@
|
||||
},
|
||||
"includes_all": "包括所有 ",
|
||||
"includes_either": "包含 任意一个",
|
||||
"install_widget": "安装 Formbricks 小组件",
|
||||
"is_equal_to": "等于",
|
||||
"is_less_than": "少于",
|
||||
"last_30_days": "最近 30 天",
|
||||
@@ -1898,7 +1895,6 @@
|
||||
"ttc_tooltip": "完成 本 问题 的 平均 时间",
|
||||
"unknown_question_type": "未知 问题 类型",
|
||||
"use_personal_links": "使用 个人 链接",
|
||||
"waiting_for_response": "等待回复 🧘♂️",
|
||||
"whats_next": "接下来 是 什么?",
|
||||
"your_survey_is_public": "您的 调查 是 公共 的",
|
||||
"youre_not_plugged_in_yet": "您 还 没 有 连 接!"
|
||||
|
||||
@@ -872,7 +872,6 @@
|
||||
"add_tag": "新增標籤",
|
||||
"count": "計數",
|
||||
"delete_tag_confirmation": "您確定要刪除此標籤嗎?",
|
||||
"empty_message": "標記提交內容,在此處找到您的標籤清單。",
|
||||
"manage_tags": "管理標籤",
|
||||
"manage_tags_description": "合併和移除回應標籤。",
|
||||
"merge": "合併",
|
||||
@@ -1691,7 +1690,7 @@
|
||||
"last_name": "姓氏",
|
||||
"not_completed": "未完成 ⏳",
|
||||
"os": "作業系統",
|
||||
"person_attributes": "人員屬性",
|
||||
"person_attributes": "提交時的個人屬性",
|
||||
"phone": "電話",
|
||||
"respondent_skipped_questions": "回應者跳過這些問題。",
|
||||
"response_deleted_successfully": "回應已成功刪除。",
|
||||
@@ -1827,7 +1826,6 @@
|
||||
"filtered_responses_csv": "篩選回應 (CSV)",
|
||||
"filtered_responses_excel": "篩選回應 (Excel)",
|
||||
"generating_qr_code": "正在生成 QR code",
|
||||
"go_to_setup_checklist": "前往設定檢查清單 👉",
|
||||
"impressions": "曝光數",
|
||||
"impressions_tooltip": "問卷已檢視的次數。",
|
||||
"in_app": {
|
||||
@@ -1861,7 +1859,6 @@
|
||||
},
|
||||
"includes_all": "包含全部",
|
||||
"includes_either": "包含其中一個",
|
||||
"install_widget": "安裝 Formbricks 小工具",
|
||||
"is_equal_to": "等於",
|
||||
"is_less_than": "小於",
|
||||
"last_30_days": "過去 30 天",
|
||||
@@ -1898,7 +1895,6 @@
|
||||
"ttc_tooltip": "完成 問題 的 平均 時間。",
|
||||
"unknown_question_type": "未知的問題類型",
|
||||
"use_personal_links": "使用 個人 連結",
|
||||
"waiting_for_response": "正在等待回應 🧘♂️",
|
||||
"whats_next": "下一步是什麼?",
|
||||
"your_survey_is_public": "您的問卷是公開的",
|
||||
"youre_not_plugged_in_yet": "您尚未插入任何內容!"
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { SettingsIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
import { TTag } from "@formbricks/types/tags";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { TagError } from "@/modules/projects/settings/types/tag";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Tag } from "@/modules/ui/components/tag";
|
||||
import { TagsCombobox } from "@/modules/ui/components/tags-combobox";
|
||||
import { createTagAction, createTagToResponseAction, deleteTagOnResponseAction } from "../actions";
|
||||
import { SingleResponseCardMetadata } from "./SingleResponseCardMetadata";
|
||||
|
||||
interface ResponseTagsWrapperProps {
|
||||
tags: {
|
||||
@@ -24,6 +24,8 @@ interface ResponseTagsWrapperProps {
|
||||
environmentTags: TTag[];
|
||||
updateFetchedResponses: () => void;
|
||||
isReadOnly?: boolean;
|
||||
response: TResponse;
|
||||
locale: TUserLocale;
|
||||
}
|
||||
|
||||
export const ResponseTagsWrapper: React.FC<ResponseTagsWrapperProps> = ({
|
||||
@@ -33,9 +35,10 @@ export const ResponseTagsWrapper: React.FC<ResponseTagsWrapperProps> = ({
|
||||
environmentTags,
|
||||
updateFetchedResponses,
|
||||
isReadOnly,
|
||||
response,
|
||||
locale,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [tagsState, setTagsState] = useState(tags);
|
||||
@@ -79,7 +82,6 @@ export const ResponseTagsWrapper: React.FC<ResponseTagsWrapperProps> = ({
|
||||
if (errorMessage?.code === TagError.TAG_NAME_ALREADY_EXISTS) {
|
||||
toast.error(t("environments.surveys.responses.tag_already_exists"), {
|
||||
duration: 2000,
|
||||
icon: <SettingsIcon className="h-5 w-5 text-orange-500" />,
|
||||
});
|
||||
} else {
|
||||
toast.error(t("environments.surveys.responses.an_error_occurred_creating_the_tag"));
|
||||
@@ -131,6 +133,7 @@ export const ResponseTagsWrapper: React.FC<ResponseTagsWrapperProps> = ({
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4 border-t border-slate-200 px-6 py-3">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<SingleResponseCardMetadata response={response} locale={locale} />
|
||||
{tagsState?.map((tag) => (
|
||||
<Tag
|
||||
key={tag.tagId}
|
||||
@@ -157,18 +160,6 @@ export const ResponseTagsWrapper: React.FC<ResponseTagsWrapperProps> = ({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isReadOnly && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex-shrink-0"
|
||||
onClick={() => {
|
||||
router.push(`/environments/${environmentId}/project/tags`);
|
||||
}}>
|
||||
<SettingsIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { LanguagesIcon, TrashIcon } from "lucide-react";
|
||||
import { TrashIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getLanguageLabel } from "@formbricks/i18n-utils/src/utils";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
@@ -12,17 +10,12 @@ import { TUser, TUserLocale } from "@formbricks/types/user";
|
||||
import { timeSince } from "@/lib/time";
|
||||
import { getContactIdentifier } from "@/lib/utils/contact";
|
||||
import { PersonAvatar } from "@/modules/ui/components/avatars";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { IdBadge } from "@/modules/ui/components/id-badge";
|
||||
import { SurveyStatusIndicator } from "@/modules/ui/components/survey-status-indicator";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/modules/ui/components/tooltip";
|
||||
import { isSubmissionTimeMoreThan5Minutes } from "../util";
|
||||
|
||||
interface TooltipRendererProps {
|
||||
shouldRender: boolean;
|
||||
tooltipContent: ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
interface SingleResponseCardHeaderProps {
|
||||
pageType: "people" | "response";
|
||||
response: TResponse;
|
||||
@@ -54,140 +47,40 @@ export const SingleResponseCardHeader = ({
|
||||
? true
|
||||
: isSubmissionTimeMoreThan5Minutes(response.updatedAt);
|
||||
|
||||
const TooltipRenderer = ({ children, shouldRender, tooltipContent }: TooltipRendererProps) => {
|
||||
return shouldRender ? (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||
<TooltipContent avoidCollisions align="start" side="bottom" className="max-w-[75vw]">
|
||||
{tooltipContent}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<>{children}</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTooltip = Boolean(
|
||||
(response.contactAttributes && Object.keys(response.contactAttributes).length > 0) ||
|
||||
(response.meta.userAgent && Object.keys(response.meta.userAgent).length > 0)
|
||||
);
|
||||
|
||||
const tooltipContent = (
|
||||
<>
|
||||
{response.singleUseId && (
|
||||
<div>
|
||||
<p className="py-1 font-bold text-slate-700">
|
||||
{t("environments.surveys.responses.single_use_id")}:
|
||||
</p>
|
||||
<span>{response.singleUseId}</span>
|
||||
</div>
|
||||
)}
|
||||
{response.contactAttributes && Object.keys(response.contactAttributes).length > 0 && (
|
||||
<div>
|
||||
<p className="py-1 font-bold text-slate-700">
|
||||
{t("environments.surveys.responses.person_attributes")}:
|
||||
</p>
|
||||
{Object.keys(response.contactAttributes).map((key) => (
|
||||
<p
|
||||
key={key}
|
||||
className="truncate"
|
||||
title={`${key}: ${response.contactAttributes && response.contactAttributes[key]}`}>
|
||||
{key}:{" "}
|
||||
<span className="font-bold">
|
||||
{response.contactAttributes && response.contactAttributes[key]}
|
||||
</span>
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{response.meta.userAgent && Object.keys(response.meta.userAgent).length > 0 && (
|
||||
<div className="text-slate-600">
|
||||
{response.contactAttributes && Object.keys(response.contactAttributes).length > 0 && (
|
||||
<hr className="my-2 border-slate-200" />
|
||||
)}
|
||||
<p className="py-1 font-bold text-slate-700">{t("environments.surveys.responses.device_info")}:</p>
|
||||
{response.meta.userAgent?.browser && (
|
||||
<p className="truncate" title={`Browser: ${response.meta.userAgent.browser}`}>
|
||||
{t("environments.surveys.responses.browser")}: {response.meta.userAgent.browser}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.userAgent?.os && (
|
||||
<p className="truncate" title={`OS: ${response.meta.userAgent.os}`}>
|
||||
{t("environments.surveys.responses.os")}: {response.meta.userAgent.os}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.userAgent && (
|
||||
<p
|
||||
className="truncate"
|
||||
title={`Device: ${response.meta.userAgent.device ? response.meta.userAgent.device : "PC / Generic device"}`}>
|
||||
{t("environments.surveys.responses.device")}:{" "}
|
||||
{response.meta.userAgent.device ? response.meta.userAgent.device : "PC / Generic device"}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.url && (
|
||||
<p className="truncate" title={`URL: ${response.meta.url}`}>
|
||||
{t("common.url")}: {response.meta.url}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.action && (
|
||||
<p className="truncate" title={`Action: ${response.meta.action}`}>
|
||||
{t("common.action")}: {response.meta.action}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.source && (
|
||||
<p className="truncate" title={`Source: ${response.meta.source}`}>
|
||||
{t("environments.surveys.responses.source")}: {response.meta.source}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.country && (
|
||||
<p className="truncate" title={`Country: ${response.meta.country}`}>
|
||||
{t("environments.surveys.responses.country")}: {response.meta.country}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
const deleteSubmissionToolTip = <>{t("environments.surveys.responses.this_response_is_in_progress")}</>;
|
||||
|
||||
return (
|
||||
<div className="space-y-2 border-b border-slate-200 px-6 pb-4 pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-center space-x-4">
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
{pageType === "response" && (
|
||||
<TooltipRenderer shouldRender={renderTooltip} tooltipContent={tooltipContent}>
|
||||
<div className="group">
|
||||
{response.contact?.id ? (
|
||||
user ? (
|
||||
<Link
|
||||
className="flex items-center space-x-2"
|
||||
href={`/environments/${environmentId}/contacts/${response.contact.id}`}>
|
||||
<PersonAvatar personId={response.contact.id} />
|
||||
<h3 className="ph-no-capture ml-4 pb-1 font-semibold text-slate-600 hover:underline">
|
||||
{displayIdentifier}
|
||||
</h3>
|
||||
{response.contact.userId && <IdBadge id={response.contact.userId} />}
|
||||
</Link>
|
||||
) : (
|
||||
<div className="flex items-center space-x-2">
|
||||
<PersonAvatar personId={response.contact.id} />
|
||||
<h3 className="ph-no-capture ml-4 pb-1 font-semibold text-slate-600">
|
||||
{displayIdentifier}
|
||||
</h3>
|
||||
{response.contact.userId && <IdBadge id={response.contact.userId} />}
|
||||
</div>
|
||||
)
|
||||
<>
|
||||
{response.contact?.id ? (
|
||||
user ? (
|
||||
<Link
|
||||
className="flex items-center space-x-2"
|
||||
href={`/environments/${environmentId}/contacts/${response.contact.id}`}>
|
||||
<PersonAvatar personId={response.contact.id} />
|
||||
<h3 className="ph-no-capture ml-4 pb-1 font-semibold text-slate-600 hover:underline">
|
||||
{displayIdentifier}
|
||||
</h3>
|
||||
</Link>
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<PersonAvatar personId="anonymous" />
|
||||
<h3 className="ml-4 pb-1 font-semibold text-slate-600">{t("common.anonymous")}</h3>
|
||||
<div className="flex items-center space-x-2">
|
||||
<PersonAvatar personId={response.contact.id} />
|
||||
<h3 className="ph-no-capture ml-4 pb-1 font-semibold text-slate-600">
|
||||
{displayIdentifier}
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TooltipRenderer>
|
||||
)
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<PersonAvatar personId="anonymous" />
|
||||
<h3 className="ml-4 pb-1 font-semibold text-slate-600">{t("common.anonymous")}</h3>
|
||||
</div>
|
||||
)}
|
||||
{response.contact?.userId && <IdBadge id={response.contact.userId} />}
|
||||
</>
|
||||
)}
|
||||
|
||||
{pageType === "people" && (
|
||||
@@ -202,34 +95,34 @@ export const SingleResponseCardHeader = ({
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{response.language && response.language !== "default" && (
|
||||
<div className="flex space-x-2 rounded-full bg-slate-700 px-2 py-1 text-xs text-white">
|
||||
<div>{getLanguageLabel(response.language, locale)}</div>
|
||||
<LanguagesIcon className="h-4 w-4" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4 text-sm">
|
||||
<div className="flex items-center space-x-2 text-sm">
|
||||
<time className="text-slate-500" dateTime={timeSince(response.createdAt.toISOString(), locale)}>
|
||||
{timeSince(response.createdAt.toISOString(), locale)}
|
||||
</time>
|
||||
{user &&
|
||||
!isReadOnly &&
|
||||
(canResponseBeDeleted ? (
|
||||
<TrashIcon
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setDeleteDialogOpen(true)}
|
||||
className="h-4 w-4 cursor-pointer text-slate-500 hover:text-red-700"
|
||||
aria-label="Delete response"
|
||||
/>
|
||||
aria-label="Delete response">
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
) : (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<TrashIcon
|
||||
className="h-4 w-4 cursor-not-allowed text-slate-400"
|
||||
aria-label="Cannot delete response in progress"
|
||||
/>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled
|
||||
className="text-slate-400"
|
||||
aria-label="Cannot delete response in progress">
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">{deleteSubmissionToolTip}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
"use client";
|
||||
|
||||
import { LanguagesIcon, LucideIcon, MonitorIcon, SmartphoneIcon, Tag } from "lucide-react";
|
||||
import { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getLanguageLabel } from "@formbricks/i18n-utils/src/utils";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/modules/ui/components/tooltip";
|
||||
|
||||
interface InfoIconButtonProps {
|
||||
icon: LucideIcon;
|
||||
tooltipContent: ReactNode;
|
||||
ariaLabel: string;
|
||||
maxWidth?: string;
|
||||
}
|
||||
|
||||
const InfoIconButton = ({
|
||||
icon: Icon,
|
||||
tooltipContent,
|
||||
ariaLabel,
|
||||
maxWidth = "max-w-[75vw]",
|
||||
}: InfoIconButtonProps) => {
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="outline" size="icon" aria-label={ariaLabel}>
|
||||
<Icon className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent avoidCollisions align="start" side="bottom" className={maxWidth}>
|
||||
{tooltipContent}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
interface SingleResponseCardMetadataProps {
|
||||
response: TResponse;
|
||||
locale: TUserLocale;
|
||||
}
|
||||
|
||||
export const SingleResponseCardMetadata = ({ response, locale }: SingleResponseCardMetadataProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const hasContactAttributes =
|
||||
response.contactAttributes && Object.keys(response.contactAttributes).length > 0;
|
||||
const hasUserAgent = response.meta.userAgent && Object.keys(response.meta.userAgent).length > 0;
|
||||
const hasLanguage = response.language && response.language !== "default";
|
||||
|
||||
if (!hasContactAttributes && !hasUserAgent && !hasLanguage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const userAgentDeviceIcon = (() => {
|
||||
if (!hasUserAgent || !response.meta.userAgent?.device) return MonitorIcon;
|
||||
const device = response.meta.userAgent.device.toLowerCase();
|
||||
return device.includes("mobile") || device.includes("phone") ? SmartphoneIcon : MonitorIcon;
|
||||
})();
|
||||
|
||||
const contactAttributesTooltipContent = hasContactAttributes ? (
|
||||
<div>
|
||||
{response.singleUseId && (
|
||||
<div className="mb-2">
|
||||
<p className="py-1 font-semibold text-slate-700">
|
||||
{t("environments.surveys.responses.single_use_id")}
|
||||
</p>
|
||||
<span>{response.singleUseId}</span>
|
||||
</div>
|
||||
)}
|
||||
<p className="py-1 font-semibold text-slate-700">
|
||||
{t("environments.surveys.responses.person_attributes")}
|
||||
</p>
|
||||
{Object.keys(response.contactAttributes || {}).map((key) => (
|
||||
<p key={key} className="truncate" title={`${key}: ${response.contactAttributes?.[key]}`}>
|
||||
{key}: {response.contactAttributes?.[key]}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
const userAgentTooltipContent = hasUserAgent ? (
|
||||
<div className="text-slate-600">
|
||||
<p className="py-1 font-semibold text-slate-700">{t("environments.surveys.responses.device_info")}</p>
|
||||
{response.meta.userAgent?.browser && (
|
||||
<p className="truncate" title={`Browser: ${response.meta.userAgent.browser}`}>
|
||||
{t("environments.surveys.responses.browser")}: {response.meta.userAgent.browser}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.userAgent?.os && (
|
||||
<p className="truncate" title={`OS: ${response.meta.userAgent.os}`}>
|
||||
{t("environments.surveys.responses.os")}: {response.meta.userAgent.os}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.userAgent && (
|
||||
<p
|
||||
className="truncate"
|
||||
title={`Device: ${response.meta.userAgent.device ? response.meta.userAgent.device : "PC / Generic device"}`}>
|
||||
{t("environments.surveys.responses.device")}:{" "}
|
||||
{response.meta.userAgent.device ? response.meta.userAgent.device : "PC / Generic device"}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.url && (
|
||||
<p className="break-all" title={`URL: ${response.meta.url}`}>
|
||||
{t("common.url")}: {response.meta.url}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.action && (
|
||||
<p className="truncate" title={`Action: ${response.meta.action}`}>
|
||||
{t("common.action")}: {response.meta.action}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.source && (
|
||||
<p className="truncate" title={`Source: ${response.meta.source}`}>
|
||||
{t("environments.surveys.responses.source")}: {response.meta.source}
|
||||
</p>
|
||||
)}
|
||||
{response.meta.country && (
|
||||
<p className="truncate" title={`Country: ${response.meta.country}`}>
|
||||
{t("environments.surveys.responses.country")}: {response.meta.country}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
const languageTooltipContent =
|
||||
hasLanguage && response.language ? (
|
||||
<div>
|
||||
<p className="font-semibold text-slate-700">{t("common.language")}</p>
|
||||
<p>{getLanguageLabel(response.language, locale)}</p>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
{hasContactAttributes && contactAttributesTooltipContent && (
|
||||
<InfoIconButton
|
||||
icon={Tag}
|
||||
tooltipContent={contactAttributesTooltipContent}
|
||||
ariaLabel={t("environments.surveys.responses.person_attributes")}
|
||||
/>
|
||||
)}
|
||||
{hasUserAgent && userAgentTooltipContent && (
|
||||
<InfoIconButton
|
||||
icon={userAgentDeviceIcon}
|
||||
tooltipContent={userAgentTooltipContent}
|
||||
ariaLabel={t("environments.surveys.responses.device_info")}
|
||||
maxWidth="max-w-md"
|
||||
/>
|
||||
)}
|
||||
{hasLanguage && languageTooltipContent && (
|
||||
<InfoIconButton
|
||||
icon={LanguagesIcon}
|
||||
tooltipContent={languageTooltipContent}
|
||||
ariaLabel={t("common.language")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -143,6 +143,8 @@ export const SingleResponseCard = ({
|
||||
environmentTags={environmentTags}
|
||||
updateFetchedResponses={updateFetchedResponses}
|
||||
isReadOnly={isReadOnly}
|
||||
response={response}
|
||||
locale={locale}
|
||||
/>
|
||||
|
||||
<DeleteDialog
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TResponseWithQuotas } from "@formbricks/types/responses";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
@@ -12,7 +13,7 @@ import { replaceHeadlineRecall } from "@/lib/utils/recall";
|
||||
import { SingleResponseCard } from "@/modules/analysis/components/SingleResponseCard";
|
||||
import { TTeamPermission } from "@/modules/ee/teams/project-teams/types/team";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { EmptySpaceFiller } from "@/modules/ui/components/empty-space-filler";
|
||||
import { EmptyState } from "@/modules/ui/components/empty-state";
|
||||
|
||||
interface ResponseTimelineProps {
|
||||
surveys: TSurvey[];
|
||||
@@ -33,6 +34,7 @@ export const ResponseFeed = ({
|
||||
locale,
|
||||
projectPermission,
|
||||
}: ResponseTimelineProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [fetchedResponses, setFetchedResponses] = useState(responses);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -50,7 +52,7 @@ export const ResponseFeed = ({
|
||||
return (
|
||||
<>
|
||||
{fetchedResponses.length === 0 ? (
|
||||
<EmptySpaceFiller type="response" environment={environment} />
|
||||
<EmptyState text={t("environments.contacts.no_responses_found")} />
|
||||
) : (
|
||||
fetchedResponses.map((response) => (
|
||||
<ResponseSurveyCard
|
||||
|
||||
@@ -300,7 +300,7 @@ export const ContactsTable = ({
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{data && hasMore && data.length > 0 && (
|
||||
{data && hasMore && data.length > 0 && isDataLoaded && (
|
||||
<div className="mt-4 flex justify-center">
|
||||
<Button onClick={fetchNextPage}>{t("common.load_more")}</Button>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,7 @@ export const SegmentTable = async ({
|
||||
<>
|
||||
{segments.map((segment) => (
|
||||
<SegmentTableDataRowContainer
|
||||
key={segment.id}
|
||||
currentSegment={segment}
|
||||
segments={segments}
|
||||
contactAttributeKeys={contactAttributeKeys}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { WebhookModal } from "@/modules/integrations/webhooks/components/webhook-detail-modal";
|
||||
import { EmptySpaceFiller } from "@/modules/ui/components/empty-space-filler";
|
||||
import { EmptyState } from "@/modules/ui/components/empty-state";
|
||||
|
||||
interface WebhookTableProps {
|
||||
environment: TEnvironment;
|
||||
@@ -46,12 +46,7 @@ export const WebhookTable = ({
|
||||
return (
|
||||
<>
|
||||
{webhooks.length === 0 ? (
|
||||
<EmptySpaceFiller
|
||||
type="table"
|
||||
environment={environment}
|
||||
noWidgetRequired={true}
|
||||
emptyMessage={t("environments.integrations.webhooks.empty_webhook_message")}
|
||||
/>
|
||||
<EmptyState text={t("environments.integrations.webhooks.empty_webhook_message")} />
|
||||
) : (
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
{TableHeading}
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TTag, TTagsCount } from "@formbricks/types/tags";
|
||||
import { SingleTag } from "@/modules/projects/settings/tags/components/single-tag";
|
||||
import { EmptySpaceFiller } from "@/modules/ui/components/empty-space-filler";
|
||||
import { EmptyState } from "@/modules/ui/components/empty-state";
|
||||
|
||||
interface EditTagsWrapperProps {
|
||||
environment: TEnvironment;
|
||||
environmentTags: TTag[];
|
||||
environmentTagsCount: TTagsCount;
|
||||
isReadOnly: boolean;
|
||||
@@ -16,7 +14,12 @@ interface EditTagsWrapperProps {
|
||||
|
||||
export const EditTagsWrapper: React.FC<EditTagsWrapperProps> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const { environment, environmentTags, environmentTagsCount, isReadOnly } = props;
|
||||
const { environmentTags, environmentTagsCount, isReadOnly } = props;
|
||||
|
||||
if (!environmentTags?.length) {
|
||||
return <EmptyState text={t("environments.project.tags.no_tag_found")} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div className="grid grid-cols-4 content-center rounded-lg bg-white text-left text-sm font-semibold text-slate-900">
|
||||
@@ -27,11 +30,7 @@ export const EditTagsWrapper: React.FC<EditTagsWrapperProps> = (props) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!environmentTags?.length ? (
|
||||
<EmptySpaceFiller environment={environment} type="tag" noWidgetRequired />
|
||||
) : null}
|
||||
|
||||
{environmentTags?.map((tag) => (
|
||||
{environmentTags.map((tag) => (
|
||||
<SingleTag
|
||||
key={tag.id}
|
||||
tagId={tag.id}
|
||||
|
||||
@@ -12,7 +12,7 @@ export const TagsPage = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslate();
|
||||
|
||||
const { isReadOnly, environment } = await getEnvironmentAuth(params.environmentId);
|
||||
const { isReadOnly } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
const [tags, environmentTagsCount] = await Promise.all([
|
||||
getTagsByEnvironmentId(params.environmentId),
|
||||
@@ -28,7 +28,6 @@ export const TagsPage = async (props) => {
|
||||
title={t("environments.project.tags.manage_tags")}
|
||||
description={t("environments.project.tags.manage_tags_description")}>
|
||||
<EditTagsWrapper
|
||||
environment={environment}
|
||||
environmentTags={tags}
|
||||
environmentTagsCount={environmentTagsCount}
|
||||
isReadOnly={isReadOnly}
|
||||
|
||||
@@ -4,7 +4,7 @@ pre {
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar {
|
||||
width: 4px !important;
|
||||
width: 7px !important;
|
||||
border-radius: 99px;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,6 @@ pre::-webkit-scrollbar-track {
|
||||
|
||||
pre::-webkit-scrollbar-thumb {
|
||||
background-color: #cbd5e1;
|
||||
border: 3px solid #cbd5e1;
|
||||
border: 2px solid #cbd5e1;
|
||||
border-radius: 99px;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
pre::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
width: 10px;
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar-thumb {
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { Skeleton } from "@/modules/ui/components/skeleton";
|
||||
|
||||
type EmptySpaceFillerProps = {
|
||||
type: "table" | "response" | "event" | "linkResponse" | "tag" | "summary";
|
||||
environment: TEnvironment;
|
||||
noWidgetRequired?: boolean;
|
||||
emptyMessage?: string;
|
||||
};
|
||||
|
||||
export const EmptySpaceFiller = ({
|
||||
type,
|
||||
environment,
|
||||
noWidgetRequired,
|
||||
emptyMessage,
|
||||
}: EmptySpaceFillerProps) => {
|
||||
const { t } = useTranslation();
|
||||
if (type === "table") {
|
||||
return (
|
||||
<div className="shadow-xs group rounded-xl border border-slate-100 bg-white p-4">
|
||||
<div className="w-full space-y-3">
|
||||
<div className="h-16 w-full rounded-lg bg-slate-50"></div>
|
||||
<div className="flex h-16 w-full flex-col items-center justify-center rounded-lg bg-slate-50 text-slate-700 transition-all duration-300 ease-in-out hover:bg-slate-100">
|
||||
{!environment.appSetupCompleted && !noWidgetRequired && (
|
||||
<Link
|
||||
className="flex w-full items-center justify-center"
|
||||
href={`/environments/${environment.id}/project/app-connection`}>
|
||||
<span className="decoration-brand-dark underline transition-all duration-300 ease-in-out">
|
||||
{t("environments.surveys.summary.install_widget")}{" "}
|
||||
<strong>{t("environments.surveys.summary.go_to_setup_checklist")} </strong>
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{((environment.appSetupCompleted || noWidgetRequired) && emptyMessage) ||
|
||||
t("environments.surveys.summary.waiting_for_response")}
|
||||
</div>
|
||||
|
||||
<div className="h-16 w-full rounded-lg bg-slate-50"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === "response") {
|
||||
return (
|
||||
<div className="group space-y-4 rounded-lg bg-white p-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="h-12 w-12 flex-shrink-0 rounded-full bg-slate-100"></div>
|
||||
<div className="h-6 w-full rounded-full bg-slate-100"></div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="h-12 w-full rounded-full bg-slate-100"></div>
|
||||
<div className="flex h-12 w-full items-center justify-center rounded-full bg-slate-50 text-sm text-slate-500 hover:bg-slate-100">
|
||||
{!environment.appSetupCompleted && !noWidgetRequired && (
|
||||
<Link
|
||||
className="flex h-full w-full items-center justify-center"
|
||||
href={`/environments/${environment.id}/project/app-connection`}>
|
||||
<span className="decoration-brand-dark underline transition-all duration-300 ease-in-out">
|
||||
{t("environments.surveys.summary.install_widget")}{" "}
|
||||
<strong>{t("environments.surveys.summary.go_to_setup_checklist")} </strong>
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{(environment.appSetupCompleted || noWidgetRequired) && (
|
||||
<span className="bg-light-background-primary-500 text-center">
|
||||
{emptyMessage ?? t("environments.surveys.summary.waiting_for_response")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-12 w-full rounded-full bg-slate-50/50"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === "tag") {
|
||||
return (
|
||||
<div className="group space-y-4 rounded-lg bg-white p-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="h-12 w-12 flex-shrink-0 rounded-full bg-slate-100"></div>
|
||||
<div className="h-6 w-full rounded-full bg-slate-100"></div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="h-12 w-full rounded-full bg-slate-100"></div>
|
||||
<div className="flex h-12 w-full items-center justify-center rounded-full bg-slate-50 text-sm text-slate-500 hover:bg-slate-100">
|
||||
{!environment.appSetupCompleted && !noWidgetRequired && (
|
||||
<Link
|
||||
className="flex h-full w-full items-center justify-center"
|
||||
href={`/environments/${environment.id}/project/app-connection`}>
|
||||
<span className="decoration-brand-dark underline transition-all duration-300 ease-in-out">
|
||||
{t("environments.surveys.summary.install_widget")}{" "}
|
||||
<strong>{t("environments.surveys.summary.go_to_setup_checklist")} 👉</strong>
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{(environment.appSetupCompleted || noWidgetRequired) && (
|
||||
<span className="text-center">{t("environments.project.tags.empty_message")}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-12 w-full rounded-full bg-slate-50/50"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === "summary") {
|
||||
return (
|
||||
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
|
||||
<Skeleton className="group space-y-4 rounded-lg bg-white p-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="h-6 w-full rounded-full bg-slate-100"></div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-4">
|
||||
<div className="h-6 w-24 rounded-full bg-slate-100"></div>
|
||||
<div className="h-6 w-24 rounded-full bg-slate-100"></div>
|
||||
</div>
|
||||
<div className="flex h-12 w-full items-center justify-center rounded-full bg-slate-50 text-sm text-slate-500 hover:bg-slate-100"></div>
|
||||
<div className="h-12 w-full rounded-full bg-slate-50/50"></div>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="group space-y-4 rounded-lg bg-white p-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="h-12 w-12 flex-shrink-0 rounded-full bg-slate-100"></div>
|
||||
<div className="h-6 w-full rounded-full bg-slate-100"></div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="h-12 w-full rounded-full bg-slate-100"></div>
|
||||
<div className="flex h-12 w-full items-center justify-center rounded-full bg-slate-50 text-sm text-slate-500 hover:bg-slate-100">
|
||||
{!environment.appSetupCompleted && !noWidgetRequired && (
|
||||
<Link
|
||||
className="flex h-full w-full items-center justify-center"
|
||||
href={`/environments/${environment.id}/project/app-connection`}>
|
||||
<span className="decoration-brand-dark underline transition-all duration-300 ease-in-out">
|
||||
{t("environments.surveys.summary.install_widget")}{" "}
|
||||
<strong>{t("environments.surveys.summary.go_to_setup_checklist")} 👉</strong>
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{(environment.appSetupCompleted || noWidgetRequired) && (
|
||||
<span className="text-center">{t("environments.surveys.summary.waiting_for_response")}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-12 w-full rounded-full bg-slate-50/50"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
19
apps/web/modules/ui/components/empty-state/index.tsx
Normal file
19
apps/web/modules/ui/components/empty-state/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
interface EmptyStateProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const EmptyState = ({ text }: EmptyStateProps) => {
|
||||
return (
|
||||
<div className="shadow-xs rounded-xl border border-slate-100 bg-white p-4">
|
||||
<div className="w-full space-y-3">
|
||||
<div className="h-16 w-full rounded-lg bg-slate-50"></div>
|
||||
<div className="flex h-16 w-full flex-col items-center justify-center rounded-lg bg-slate-50 text-sm text-slate-500">
|
||||
{text}
|
||||
</div>
|
||||
<div className="h-16 w-full rounded-lg bg-slate-50"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -63,9 +63,7 @@ export const TagsCombobox = ({
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button size="sm" aria-expanded={open}>
|
||||
{t("environments.project.tags.add_tag")}
|
||||
</Button>
|
||||
<Button aria-expanded={open}>{t("environments.project.tags.add_tag")}</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="max-h-60 w-[200px] overflow-y-auto p-0">
|
||||
<Command
|
||||
|
||||
@@ -58,7 +58,8 @@
|
||||
|
||||
/* Chrome, Edge, and Safari */
|
||||
*::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
@@ -67,11 +68,11 @@
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: #cbd5e1;
|
||||
border: 3px solid #cbd5e1;
|
||||
border: 1px solid #cbd5e1;
|
||||
}
|
||||
|
||||
.filter-scrollbar::-webkit-scrollbar {
|
||||
height: 10px;
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
.filter-scrollbar::-webkit-scrollbar-thumb {
|
||||
|
||||
Reference in New Issue
Block a user