Add new Consent Question Type (#342)

* feat: add consent to questionTypes and types

* feat: add default values to consent question

* feat: add consent question form

* feat: add consent question to preview / link survey

* fix: clean consent question html

* feat: add consent question to js package

* feat: add consent to summary list

* fix build errors

* fix: remove skip button, add button label input

* feat: add checked logic option

* fix: add accepted option

* update consent form to match new advanced settings layout

* remove console.log

* hide accepted condition if consent is required

* fix build errors

* update consent question return values

* remove console.log

* renamed submitted to clicked in CTA logic, removed submitted condition for consent questions

* remove logs display from demo;

* remove logs display from demo;

---------

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Moritz Rengert
2023-06-29 16:31:05 +02:00
committed by GitHub
parent fa785fcafb
commit 2205d98aeb
19 changed files with 393 additions and 266 deletions
-29
View File
@@ -1,29 +0,0 @@
// @ts-nocheck
import React, { useState, useEffect, useRef } from "react";
import { Console, Hook, Unhook } from "console-feed";
const LogsContainer = () => {
const [logs, setLogs] = useState([]);
const messagesEndRef = useRef<HTMLDivElement | null>(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
// run once!
useEffect(() => {
const hookedConsole = Hook(window.console, (log) => setLogs((currLogs) => [...currLogs, log]), false);
return () => Unhook(hookedConsole);
}, []);
useEffect(scrollToBottom, [logs]);
return (
<>
<Console logs={logs} variant="light" />
<div ref={messagesEndRef} />
</>
);
};
export { LogsContainer };
+1 -2
View File
@@ -12,7 +12,6 @@
"dependencies": {
"@formbricks/js": "workspace:*",
"@heroicons/react": "^2.0.17",
"console-feed": "^3.5.0",
"eslint-config-formbricks": "workspace:*",
"next": "13.2.4",
"react": "18.2.0",
@@ -21,8 +20,8 @@
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"@types/node": "18.15.11",
"@types/react-dom": "18.0.11",
"@types/react": "18.0.33",
"@types/react-dom": "18.0.11",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"rimraf": "^5.0.0",
+2 -3
View File
@@ -1,7 +1,6 @@
import fbsetup from "../../public/fb-setup.png";
import formbricks from "@formbricks/js";
import Image from "next/image";
import { LogsContainer } from "../../components/ConsoleFeed";
export default function AppPage({}) {
return (
@@ -22,13 +21,13 @@ export default function AppPage({}) {
</p>
<Image src={fbsetup} alt="fb setup" className="mt-4 rounded" priority />
</div>
<div className="mt-4 rounded-lg border border-slate-300 bg-slate-100 p-6">
{/* <div className="mt-4 rounded-lg border border-slate-300 bg-slate-100 p-6">
<h3 className="text-lg font-semibold">Console</h3>
<p className="text-slate-700">You can also open your browser console to logs:</p>
<div className="max-h-[40vh] overflow-y-auto py-4">
<LogsContainer />
</div>
</div>
</div> */}
</div>
<div className="md:grid md:grid-cols-3">
@@ -157,6 +157,10 @@ export default function PreviewSurvey({
Array.isArray(logic.value) &&
logic.value.some((v) => answerValue.includes(v))
);
case "accepted":
return answerValue === "accepted";
case "clicked":
return answerValue === "clicked";
case "submitted":
if (typeof answerValue === "string") {
return answerValue !== "dismissed" && answerValue !== "" && answerValue !== null;
@@ -0,0 +1,80 @@
"use client";
import { md } from "@formbricks/lib/markdownIt";
import type { ConsentQuestion } from "@formbricks/types/questions";
import { Survey } from "@formbricks/types/surveys";
import { Editor, Input, Label } from "@formbricks/ui";
import { useState } from "react";
interface ConsentQuestionFormProps {
localSurvey: Survey;
question: ConsentQuestion;
questionIdx: number;
updateQuestion: (questionIdx: number, updatedAttributes: any) => void;
}
export default function ConsentQuestionForm({
question,
questionIdx,
updateQuestion,
}: ConsentQuestionFormProps): JSX.Element {
const [firstRender, setFirstRender] = useState(true);
return (
<form>
<div className="mt-3">
<Label htmlFor="headline">Question</Label>
<div className="mt-2">
<Input
id="headline"
name="headline"
value={question.headline}
onChange={(e) => updateQuestion(questionIdx, { headline: e.target.value })}
/>
</div>
</div>
<div className="mt-3">
<Label htmlFor="subheader">Description</Label>
<div className="mt-2">
<Editor
getText={() =>
md.render(
question.html || "We would love to talk to you and learn more about how you use our product."
)
}
setText={(value: string) => {
updateQuestion(questionIdx, { html: value });
}}
excludedToolbarItems={["blockType"]}
disableLists
firstRender={firstRender}
setFirstRender={setFirstRender}
/>
</div>
</div>
<div className="mt-3">
<Label htmlFor="label">Checkbox Label</Label>
<Input
id="label"
name="label"
className="mt-2"
value={question.label}
placeholder="I agree to the terms and conditions"
onChange={(e) => updateQuestion(questionIdx, { label: e.target.value })}
/>
</div>
{/* <div className="mt-3">
<Label htmlFor="buttonLabel">Button Label</Label>
<Input
id="buttonLabel"
name="buttonLabel"
className="mt-2"
value={question.buttonLabel}
placeholder={lastQuestion ? "Finish" : "Next"}
onChange={(e) => updateQuestion(questionIdx, { buttonLabel: e.target.value })}
/>
</div> */}
</form>
);
}
@@ -79,7 +79,8 @@ export default function LogicEditor({
"submitted",
"skipped",
],
cta: ["submitted", "skipped"],
cta: ["clicked", "skipped"],
consent: ["skipped", "accepted"],
};
const logicConditions: LogicConditions = {
submitted: {
@@ -92,6 +93,16 @@ export default function LogicEditor({
values: null,
unique: true,
},
accepted: {
label: "is accepted",
values: null,
unique: true,
},
clicked: {
label: "is clicked",
values: null,
unique: true,
},
equals: {
label: "equals",
values: questionValues,
@@ -7,6 +7,7 @@ import type { Survey } from "@formbricks/types/surveys";
import { Input, Label, Switch } from "@formbricks/ui";
import {
ChatBubbleBottomCenterTextIcon,
CheckIcon,
ChevronDownIcon,
ChevronRightIcon,
CursorArrowRippleIcon,
@@ -25,6 +26,7 @@ import NPSQuestionForm from "./NPSQuestionForm";
import OpenQuestionForm from "./OpenQuestionForm";
import QuestionDropdown from "./QuestionMenu";
import RatingQuestionForm from "./RatingQuestionForm";
import ConsentQuestionForm from "./ConsentQuestionForm";
import AdvancedSettings from "@/app/environments/[environmentId]/surveys/[surveyId]/edit/AdvancedSettings";
interface QuestionCardProps {
@@ -100,6 +102,8 @@ export default function QuestionCard({
<CursorArrowRippleIcon />
) : question.type === QuestionType.Rating ? (
<StarIcon />
) : question.type === "consent" ? (
<CheckIcon />
) : null}
</div>
<div>
@@ -174,6 +178,13 @@ export default function QuestionCard({
updateQuestion={updateQuestion}
lastQuestion={lastQuestion}
/>
) : question.type === "consent" ? (
<ConsentQuestionForm
localSurvey={localSurvey}
question={question}
questionIdx={questionIdx}
updateQuestion={updateQuestion}
/>
) : null}
<div className="mt-4">
<Collapsible.Root open={openAdvanced} onOpenChange={setOpenAdvanced} className="mt-5">
@@ -0,0 +1,83 @@
import { ConsentQuestion } from "@formbricks/types/questions";
import type { QuestionSummary } from "@formbricks/types/responses";
import { ProgressBar } from "@formbricks/ui";
import { InboxStackIcon } from "@heroicons/react/24/solid";
import { useMemo } from "react";
interface ConsentSummaryProps {
questionSummary: QuestionSummary<ConsentQuestion>;
}
interface ChoiceResult {
count: number;
acceptedCount: number;
acceptedPercentage: number;
dismissedCount: number;
dismissedPercentage: number;
}
export default function ConsentSummary({ questionSummary }: ConsentSummaryProps) {
const ctr: ChoiceResult = useMemo(() => {
const total = questionSummary.responses.length;
const clickedAbs = questionSummary.responses.filter((response) => response.value !== "skipped").length;
return {
count: total,
acceptedCount: clickedAbs,
acceptedPercentage: clickedAbs / total,
dismissedCount: total - clickedAbs,
dismissedPercentage: 1 - clickedAbs / total,
};
}, [questionSummary]);
return (
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm">
<div className="space-y-2 px-6 pb-5 pt-6">
<div>
<h3 className="pb-1 text-xl font-semibold text-slate-900">{questionSummary.question.headline}</h3>
</div>
<div className="flex space-x-2 font-semibold text-slate-600">
<div className="rounded-lg bg-slate-100 p-2 text-sm">Consent</div>
<div className=" flex items-center rounded-lg bg-slate-100 p-2 text-sm">
<InboxStackIcon className="mr-2 h-4 w-4 " />
{ctr.count} responses
</div>
</div>
</div>
<div className="space-y-5 rounded-b-lg bg-white px-6 pb-6 pt-4">
<div>
<div className="text flex justify-between px-2 pb-2">
<div className="mr-8 flex space-x-1">
<p className="font-semibold text-slate-700">Accepted</p>
<div>
<p className="rounded-lg bg-slate-100 px-2 text-slate-700">
{Math.round(ctr.acceptedPercentage * 100)}%
</p>
</div>
</div>
<p className="flex w-32 items-end justify-end text-slate-600">
{ctr.acceptedCount} {ctr.acceptedCount === 1 ? "response" : "responses"}
</p>
</div>
<ProgressBar barColor="bg-brand" progress={ctr.acceptedPercentage} />
</div>
<div>
<div className="text flex justify-between px-2 pb-2">
<div className="mr-8 flex space-x-1">
<p className="font-semibold text-slate-700">Skipped</p>
<div>
<p className="rounded-lg bg-slate-100 px-2 text-slate-700">
{Math.round(ctr.dismissedPercentage * 100)}%
</p>
</div>
</div>
<p className="flex w-32 items-end justify-end text-slate-600">
{ctr.dismissedCount} {ctr.dismissedCount === 1 ? "response" : "responses"}
</p>
</div>
<ProgressBar barColor="bg-brand" progress={ctr.dismissedPercentage} />
</div>
</div>
</div>
);
}
@@ -252,7 +252,7 @@ export const templates: Template[] = [
id: "mao94214zoo6c1at5rpuz7io",
html: '<p class="fb-editor-paragraph" dir="ltr"><span>We\'d love to keep you as a customer. Happy to offer a 30% discount for the next year.</span></p>',
type: QuestionType.CTA,
logic: [{ condition: "submitted", destination: "end" }],
logic: [{ condition: "clicked", destination: "end" }],
headline: "Get 30% off for the next year!",
required: true,
buttonUrl: "https://formbricks.com",
@@ -273,7 +273,7 @@ export const templates: Template[] = [
id: "hdftsos1odzjllr7flj4m3j9",
html: '<p class="fb-editor-paragraph" dir="ltr"><span>We aim to provide the best possible customer service. Please email our CEO and she will personally handle your issue.</span></p>',
type: QuestionType.CTA,
logic: [{ condition: "submitted", destination: "end" }],
logic: [{ condition: "clicked", destination: "end" }],
headline: "So sorry to hear 😔 Talk to our CEO directly!",
required: true,
buttonUrl: "mailto:ceo@company.com",
@@ -409,7 +409,7 @@ export const templates: Template[] = [
id: "x760wga1fhtr1i80cpssr7af",
html: '<p class="fb-editor-paragraph" dir="ltr"><span>We\'re happy to offer you a 20% discount on a yearly plan.</span></p>',
type: QuestionType.CTA,
logic: [{ condition: "submitted", destination: "end" }],
logic: [{ condition: "clicked", destination: "end" }],
headline: "Sorry to hear! Get 20% off the first year.",
required: true,
buttonUrl: "https://formbricks.com/github",
@@ -466,7 +466,7 @@ export const templates: Template[] = [
id: createId(),
html: '<p class="fb-editor-paragraph" dir="ltr"><span>This helps us a lot.</span></p>',
type: QuestionType.CTA,
logic: [{ condition: "submitted", destination: "end" }],
logic: [{ condition: "clicked", destination: "end" }],
headline: "Happy to hear 🙏 Please write a review for us!",
required: true,
buttonUrl: "https://formbricks.com/github",
@@ -945,7 +945,7 @@ export const templates: Template[] = [
html: '<p class="fb-editor-paragraph" dir="ltr"><span>We will fix this as soon as possible. Do you want to be notified when we did?</span></p>',
type: QuestionType.CTA,
logic: [
{ condition: "submitted", destination: "end" },
{ condition: "clicked", destination: "end" },
{ condition: "skipped", destination: "end" },
],
headline: "Want to stay in the loop?",
@@ -0,0 +1,62 @@
import type { ConsentQuestion } from "@formbricks/types/questions";
import Headline from "./Headline";
import HtmlBody from "./HtmlBody";
import { cn } from "@/../../packages/lib/cn";
import { isLight } from "@/lib/utils";
interface ConsentQuestionProps {
question: ConsentQuestion;
onSubmit: (data: { [x: string]: any }) => void;
lastQuestion: boolean;
brandColor: string;
}
export default function ConsentQuestion({
question,
onSubmit,
lastQuestion,
brandColor,
}: ConsentQuestionProps) {
return (
<div>
<Headline headline={question.headline} questionId={question.id} />
<HtmlBody htmlString={question.html || ""} questionId={question.id} />
<form
onSubmit={(e) => {
e.preventDefault();
const checkbox = document.getElementById(question.id) as HTMLInputElement;
onSubmit({ [question.id]: checkbox.checked ? "accepted" : "dismissed" });
}}>
<label className="relative z-10 mt-4 flex w-full cursor-pointer items-center rounded-md border border-gray-200 bg-slate-50 p-4 text-sm focus:outline-none">
<input
type="checkbox"
id={question.id}
name={question.id}
value={question.label}
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
aria-labelledby={`${question.id}-label`}
style={{ borderColor: brandColor, color: brandColor }}
required={question.required}
/>
<span id={`${question.id}-label`} className="ml-3 font-medium">
{question.label}
</span>
</label>
<div className="mt-4 flex w-full justify-end">
<button
type="submit"
className={cn(
"flex items-center rounded-md border border-transparent px-3 py-3 text-base font-medium leading-4 shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2",
isLight(brandColor) ? "text-black" : "text-white"
)}
style={{ backgroundColor: brandColor }}>
{question.buttonLabel || (lastQuestion ? "Finish" : "Next")}
</button>
</div>
</form>
</div>
);
}
@@ -5,6 +5,7 @@ import MultipleChoiceMultiQuestion from "./MultipleChoiceMultiQuestion";
import NPSQuestion from "./NPSQuestion";
import CTAQuestion from "./CTAQuestion";
import RatingQuestion from "./RatingQuestion";
import ConsentQuestion from "./ConsentQuestion";
interface QuestionConditionalProps {
question: Question;
@@ -61,5 +62,12 @@ export default function QuestionConditional({
lastQuestion={lastQuestion}
brandColor={brandColor}
/>
) : question.type === "consent" ? (
<ConsentQuestion
question={question}
onSubmit={onSubmit}
lastQuestion={lastQuestion}
brandColor={brandColor}
/>
) : null;
}
+4
View File
@@ -314,6 +314,10 @@ const evaluateCondition = (logic: Logic, answerValue: any): boolean => {
Array.isArray(logic.value) &&
logic.value.some((v) => answerValue.includes(v))
);
case "accepted":
return answerValue === "accepted";
case "clicked":
return answerValue === "clicked";
case "submitted":
if (typeof answerValue === "string") {
return answerValue !== "dismissed" && answerValue !== "" && answerValue !== null;
+12
View File
@@ -5,6 +5,7 @@ import {
PresentationChartBarIcon,
QueueListIcon,
StarIcon,
CheckIcon,
} from "@heroicons/react/24/solid";
import { createId } from "@paralleldrive/cuid2";
import { replaceQuestionPresetPlaceholders } from "./templates";
@@ -95,6 +96,17 @@ export const questionTypes: QuestionType[] = [
upperLabel: "Very good",
},
},
{
id: "consent",
label: "Consent",
description: "Ask your users to accept something",
icon: CheckIcon,
preset: {
headline: "Terms and Conditions",
label: "I agree to the terms and conditions",
dismissButtonLabel: "Skip",
},
},
];
export const universalQuestionPresets = {
@@ -0,0 +1,59 @@
import type { ConsentQuestion } from "../../../types/questions";
import { h } from "preact";
import Headline from "./Headline";
import HtmlBody from "./HtmlBody";
import SubmitButton from "./SubmitButton";
interface ConsentQuestionProps {
question: ConsentQuestion;
onSubmit: (data: { [x: string]: any }) => void;
lastQuestion: boolean;
brandColor: string;
}
export default function ConsentQuestion({
question,
onSubmit,
lastQuestion,
brandColor,
}: ConsentQuestionProps) {
return (
<div>
<Headline headline={question.headline} questionId={question.id} />
<HtmlBody htmlString={question.html || ""} questionId={question.id} />
<form
onSubmit={(e) => {
e.preventDefault();
const checkbox = document.getElementById(question.id) as HTMLInputElement;
onSubmit({ [question.id]: checkbox.checked ? "accepted" : "dismissed" });
}}>
<label className="fb-relative fb-z-10 fb-mt-4 fb-flex fb-w-full fb-cursor-pointer fb-items-center fb-rounded-md fb-border fb-border-gray-200 fb-bg-slate-50 fb-p-4 fb-text-sm focus:fb-outline-none">
<input
type="checkbox"
id={question.id}
name={question.id}
value={question.label}
className="fb-h-4 fb-w-4 fb-border fb-border-slate-300 focus:fb-ring-0 focus:fb-ring-offset-0"
aria-labelledby={`${question.id}-label`}
style={{ borderColor: brandColor, color: brandColor }}
required={question.required}
/>
<span id={`${question.id}-label`} className="fb-ml-3 fb-font-medium">
{question.label}
</span>
</label>
<div className="fb-mt-4 fb-flex fb-w-full fb-justify-end">
<SubmitButton
brandColor={brandColor}
question={question}
lastQuestion={lastQuestion}
onClick={() => {}}
/>
</div>
</form>
</div>
);
}
@@ -6,6 +6,7 @@ import MultipleChoiceMultiQuestion from "./MultipleChoiceMultiQuestion";
import NPSQuestion from "./NPSQuestion";
import CTAQuestion from "./CTAQuestion";
import RatingQuestion from "./RatingQuestion";
import ConsentQuestion from "./ConsentQuestion";
interface QuestionConditionalProps {
question: Question;
@@ -62,5 +63,12 @@ export default function QuestionConditional({
lastQuestion={lastQuestion}
brandColor={brandColor}
/>
) : question.type === "consent" ? (
<ConsentQuestion
question={question}
onSubmit={onSubmit}
lastQuestion={lastQuestion}
brandColor={brandColor}
/>
) : null;
}
@@ -121,6 +121,10 @@ export default function SurveyView({ config, survey, close, errorHandler }: Surv
Array.isArray(logic.value) &&
logic.value.some((v) => answerValue.includes(v))
);
case "accepted":
return answerValue === "accepted";
case "clicked":
return answerValue === "clicked";
case "submitted":
if (typeof answerValue === "string") {
return answerValue !== "dismissed" && answerValue !== "" && answerValue !== null;
+20 -3
View File
@@ -19,7 +19,8 @@ export type Question =
| MultipleChoiceMultiQuestion
| NPSQuestion
| CTAQuestion
| RatingQuestion;
| RatingQuestion
| ConsentQuestion;
export interface IQuestion<T extends Logic> {
id: string;
@@ -68,9 +69,18 @@ export interface RatingQuestion extends IQuestion<RatingLogic> {
upperLabel: string;
}
export interface ConsentQuestion extends IQuestion<CTALogic> {
type: "consent";
html?: string;
label: string;
dismissButtonLabel?: string;
}
export type LogicCondition =
| "submitted"
| "skipped"
| "accepted"
| "clicked"
| "equals"
| "notEquals"
| "lessThan"
@@ -112,7 +122,7 @@ export interface NPSLogic extends LogicBase {
value?: number;
}
export interface CTALogic extends LogicBase {
condition: "submitted" | "skipped" | undefined;
condition: "clicked" | "skipped" | undefined;
value?: undefined;
}
export interface RatingLogic extends LogicBase {
@@ -128,10 +138,17 @@ export interface RatingLogic extends LogicBase {
| undefined;
value?: number | string;
}
export interface ConsentLogic extends LogicBase {
condition: "submitted" | "skipped" | "accepted" | undefined;
value: undefined;
}
export type Logic =
| OpenTextLogic
| MultipleChoiceSingleLogic
| MultipleChoiceMultiLogic
| NPSLogic
| CTALogic
| RatingLogic;
| RatingLogic
| ConsentLogic;
+3 -2
View File
@@ -40,7 +40,7 @@ export const ZSurveyOpenTextLogic = ZSurveyLogicBase.extend({
});
export const ZSurveyConsentLogic = ZSurveyLogicBase.extend({
condition: z.enum(["submitted", "skipped", "accepted"]).optional(),
condition: z.enum(["skipped", "accepted"]).optional(),
value: z.undefined(),
});
@@ -71,7 +71,8 @@ export const ZSurveyNPSLogic = ZSurveyLogicBase.extend({
});
const ZSurveyCTALogic = ZSurveyLogicBase.extend({
condition: z.enum(["submitted", "skipped"]).optional(),
// "submitted" condition is legacy and should be removed later
condition: z.enum(["clicked", "submitted", "skipped"]).optional(),
value: z.undefined(),
});
+15 -221
View File
@@ -32,9 +32,6 @@ importers:
'@heroicons/react':
specifier: ^2.0.17
version: 2.0.18(react@18.2.0)
console-feed:
specifier: ^3.5.0
version: 3.5.0(jquery@3.7.0)(react-dom@18.2.0)(react@18.2.0)
eslint-config-formbricks:
specifier: workspace:*
version: link:../../packages/eslint-config-formbricks
@@ -912,6 +909,7 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/highlight': 7.18.6
dev: true
/@babel/compat-data@7.20.5:
resolution: {integrity: sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==}
@@ -1077,6 +1075,7 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.20.7
dev: true
/@babel/helper-module-transforms@7.20.11:
resolution: {integrity: sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==}
@@ -1158,10 +1157,12 @@ packages:
/@babel/helper-string-parser@7.19.4:
resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-validator-identifier@7.19.1:
resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-validator-option@7.18.6:
resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==}
@@ -1198,6 +1199,7 @@ packages:
'@babel/helper-validator-identifier': 7.19.1
chalk: 2.4.2
js-tokens: 4.0.0
dev: true
/@babel/parser@7.20.13:
resolution: {integrity: sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==}
@@ -2266,6 +2268,7 @@ packages:
'@babel/helper-string-parser': 7.19.4
'@babel/helper-validator-identifier': 7.19.1
to-fast-properties: 2.0.0
dev: true
/@bcoe/v8-coverage@0.2.3:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
@@ -2516,107 +2519,6 @@ packages:
- search-insights
dev: false
/@emotion/cache@10.0.29:
resolution: {integrity: sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==}
dependencies:
'@emotion/sheet': 0.9.4
'@emotion/stylis': 0.8.5
'@emotion/utils': 0.11.3
'@emotion/weak-memoize': 0.2.5
dev: false
/@emotion/core@10.3.1(react@18.2.0):
resolution: {integrity: sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==}
peerDependencies:
react: '>=16.3.0'
dependencies:
'@babel/runtime': 7.21.0
'@emotion/cache': 10.0.29
'@emotion/css': 10.0.27
'@emotion/serialize': 0.11.16
'@emotion/sheet': 0.9.4
'@emotion/utils': 0.11.3
react: 18.2.0
dev: false
/@emotion/css@10.0.27:
resolution: {integrity: sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==}
dependencies:
'@emotion/serialize': 0.11.16
'@emotion/utils': 0.11.3
babel-plugin-emotion: 10.2.2
dev: false
/@emotion/hash@0.8.0:
resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
dev: false
/@emotion/is-prop-valid@0.8.8:
resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
dependencies:
'@emotion/memoize': 0.7.4
dev: false
/@emotion/memoize@0.7.4:
resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
dev: false
/@emotion/serialize@0.11.16:
resolution: {integrity: sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==}
dependencies:
'@emotion/hash': 0.8.0
'@emotion/memoize': 0.7.4
'@emotion/unitless': 0.7.5
'@emotion/utils': 0.11.3
csstype: 2.6.21
dev: false
/@emotion/sheet@0.9.4:
resolution: {integrity: sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==}
dev: false
/@emotion/styled-base@10.3.0(@emotion/core@10.3.1)(react@18.2.0):
resolution: {integrity: sha512-PBRqsVKR7QRNkmfH78hTSSwHWcwDpecH9W6heujWAcyp2wdz/64PP73s7fWS1dIPm8/Exc8JAzYS8dEWXjv60w==}
peerDependencies:
'@emotion/core': ^10.0.28
react: '>=16.3.0'
dependencies:
'@babel/runtime': 7.21.0
'@emotion/core': 10.3.1(react@18.2.0)
'@emotion/is-prop-valid': 0.8.8
'@emotion/serialize': 0.11.16
'@emotion/utils': 0.11.3
react: 18.2.0
dev: false
/@emotion/styled@10.3.0(@emotion/core@10.3.1)(react@18.2.0):
resolution: {integrity: sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ==}
peerDependencies:
'@emotion/core': ^10.0.27
react: '>=16.3.0'
dependencies:
'@emotion/core': 10.3.1(react@18.2.0)
'@emotion/styled-base': 10.3.0(@emotion/core@10.3.1)(react@18.2.0)
babel-plugin-emotion: 10.2.2
react: 18.2.0
dev: false
/@emotion/stylis@0.8.5:
resolution: {integrity: sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==}
dev: false
/@emotion/unitless@0.7.5:
resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==}
dev: false
/@emotion/utils@0.11.3:
resolution: {integrity: sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==}
dev: false
/@emotion/weak-memoize@0.2.5:
resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==}
dev: false
/@esbuild-kit/cjs-loader@2.4.2:
resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==}
dependencies:
@@ -6291,6 +6193,7 @@ packages:
/@types/parse-json@4.0.0:
resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
dev: true
/@types/prettier@2.7.2:
resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==}
@@ -7672,21 +7575,6 @@ packages:
webpack: 4.46.0
dev: true
/babel-plugin-emotion@10.2.2:
resolution: {integrity: sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==}
dependencies:
'@babel/helper-module-imports': 7.18.6
'@emotion/hash': 0.8.0
'@emotion/memoize': 0.7.4
'@emotion/serialize': 0.11.16
babel-plugin-macros: 2.8.0
babel-plugin-syntax-jsx: 6.18.0
convert-source-map: 1.9.0
escape-string-regexp: 1.0.5
find-root: 1.1.0
source-map: 0.5.7
dev: false
/babel-plugin-istanbul@6.1.1:
resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==}
engines: {node: '>=8'}
@@ -7720,14 +7608,6 @@ packages:
'@types/babel__traverse': 7.18.3
dev: true
/babel-plugin-macros@2.8.0:
resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==}
dependencies:
'@babel/runtime': 7.21.0
cosmiconfig: 6.0.0
resolve: 1.22.2
dev: false
/babel-plugin-macros@3.1.0:
resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
engines: {node: '>=10', npm: '>=6'}
@@ -7773,10 +7653,6 @@ packages:
- supports-color
dev: true
/babel-plugin-syntax-jsx@6.18.0:
resolution: {integrity: sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==}
dev: false
/babel-plugin-transform-async-to-promises@0.8.18:
resolution: {integrity: sha512-WpOrF76nUHijnNn10eBGOHZmXQC8JYRME9rOLxStOga7Av2VO53ehVFvVNImMksVtQuL2/7ZNxEgxnx7oo/3Hw==}
dev: true
@@ -8970,22 +8846,6 @@ packages:
engines: {node: '>=4'}
dev: true
/console-feed@3.5.0(jquery@3.7.0)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-2N5b37yH0HeaqbBDsHBx0jEy3qvhTGA1dVdGyEM6C1NQhVmIX+ToU6Rt/uo86K0lLs4Lg1orC940YSn+Z3kk5g==}
peerDependencies:
react: ^15.x || ^16.x || ^17.x || ^18.x
dependencies:
'@emotion/core': 10.3.1(react@18.2.0)
'@emotion/styled': 10.3.0(@emotion/core@10.3.1)(react@18.2.0)
emotion-theming: 10.3.0(@emotion/core@10.3.1)(react@18.2.0)
linkifyjs: 2.1.9(jquery@3.7.0)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-inspector: 5.1.1(react@18.2.0)
transitivePeerDependencies:
- jquery
- react-dom
dev: false
/constants-browserify@1.0.0:
resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==}
dev: true
@@ -9004,6 +8864,7 @@ packages:
/convert-source-map@1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
dev: true
/convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@@ -9089,17 +8950,6 @@ packages:
parse-json: 4.0.0
dev: true
/cosmiconfig@6.0.0:
resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==}
engines: {node: '>=8'}
dependencies:
'@types/parse-json': 4.0.0
import-fresh: 3.3.0
parse-json: 5.2.0
path-type: 4.0.0
yaml: 1.10.2
dev: false
/cosmiconfig@7.1.0:
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
engines: {node: '>=10'}
@@ -9507,10 +9357,6 @@ packages:
cssom: 0.3.8
dev: true
/csstype@2.6.21:
resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==}
dev: false
/csstype@3.1.1:
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
@@ -10065,19 +9911,6 @@ packages:
engines: {node: '>= 4'}
dev: true
/emotion-theming@10.3.0(@emotion/core@10.3.1)(react@18.2.0):
resolution: {integrity: sha512-mXiD2Oj7N9b6+h/dC6oLf9hwxbtKHQjoIqtodEyL8CpkN4F3V4IK/BT4D0C7zSs4BBFOu4UlPJbvvBLa88SGEA==}
peerDependencies:
'@emotion/core': ^10.0.27
react: '>=16.3.0'
dependencies:
'@babel/runtime': 7.21.0
'@emotion/core': 10.3.1(react@18.2.0)
'@emotion/weak-memoize': 0.2.5
hoist-non-react-statics: 3.3.2
react: 18.2.0
dev: false
/encodeurl@1.0.2:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
@@ -10192,6 +10025,7 @@ packages:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
dependencies:
is-arrayish: 0.2.1
dev: true
/error-stack-parser@2.1.4:
resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==}
@@ -11618,10 +11452,6 @@ packages:
pkg-dir: 4.2.0
dev: true
/find-root@1.1.0:
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
dev: false
/find-up@3.0.0:
resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
engines: {node: '>=6'}
@@ -12861,6 +12691,7 @@ packages:
/is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
dev: true
/is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
@@ -12996,13 +12827,6 @@ packages:
engines: {node: '>=8'}
hasBin: true
/is-dom@1.1.0:
resolution: {integrity: sha512-u82f6mvhYxRPKpw8V1N0W8ce1xXwOrQtgGcxl6UCL5zBmZu3is/18K0rR7uFCnMDuAsS/3W54mGL4vsaFUQlEQ==}
dependencies:
is-object: 1.0.2
is-window: 1.0.2
dev: false
/is-extendable@0.1.1:
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
engines: {node: '>=0.10.0'}
@@ -13131,10 +12955,6 @@ packages:
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
engines: {node: '>=8'}
/is-object@1.0.2:
resolution: {integrity: sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==}
dev: false
/is-path-cwd@2.2.0:
resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}
engines: {node: '>=6'}
@@ -13262,10 +13082,6 @@ packages:
dependencies:
call-bind: 1.0.2
/is-window@1.0.2:
resolution: {integrity: sha512-uj00kdXyZb9t9RcAUAwMZAnkBUwdYGhYlt7djMXhfyhUCzwNba50tIiBKR7q0l7tdoBtFVw/3JmLY6fI3rmZmg==}
dev: false
/is-windows@1.0.2:
resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
engines: {node: '>=0.10.0'}
@@ -13940,10 +13756,6 @@ packages:
engines: {node: '>=10'}
dev: true
/jquery@3.7.0:
resolution: {integrity: sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ==}
dev: false
/js-cookie@2.2.1:
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
dev: false
@@ -14255,6 +14067,7 @@ packages:
/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true
/linkify-it@4.0.1:
resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==}
@@ -14262,18 +14075,6 @@ packages:
uc.micro: 1.0.6
dev: false
/linkifyjs@2.1.9(jquery@3.7.0)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-74ivurkK6WHvHFozVaGtQWV38FzBwSTGNmJolEgFp7QgR2bl6ArUWlvT4GcHKbPe1z3nWYi+VUdDZk16zDOVug==}
peerDependencies:
jquery: '>= 1.11.0'
react: '>= 0.14.0'
react-dom: '>= 0.14.0'
dependencies:
jquery: 3.7.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/listify@1.0.3:
resolution: {integrity: sha512-083swF7iH7bx8666zdzBColpgEuy46HjN3r1isD4zV6Ix7FuHfb/2/WVnl4CH8hjuoWeFF7P5KkKNXUnJCFEJg==}
engines: {node: '>= 0.4'}
@@ -16481,6 +16282,7 @@ packages:
error-ex: 1.3.2
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
dev: true
/parse-passwd@1.0.0:
resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==}
@@ -18105,17 +17907,6 @@ packages:
react: 18.2.0
dev: false
/react-inspector@5.1.1(react@18.2.0):
resolution: {integrity: sha512-GURDaYzoLbW8pMGXwYPDBIv6nqei4kK7LPRZ9q9HCZF54wqXz/dnylBp/kfE9XmekBhHvLDdcYeyIwSrvtOiWg==}
peerDependencies:
react: ^16.8.4 || ^17.0.0
dependencies:
'@babel/runtime': 7.21.0
is-dom: 1.1.0
prop-types: 15.8.1
react: 18.2.0
dev: false
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@@ -19407,6 +19198,7 @@ packages:
/source-map@0.5.7:
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
engines: {node: '>=0.10.0'}
dev: true
/source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
@@ -20335,6 +20127,7 @@ packages:
/to-fast-properties@2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'}
dev: true
/to-object-path@0.3.0:
resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==}
@@ -22024,6 +21817,7 @@ packages:
/yaml@1.10.2:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'}
dev: true
/yaml@2.1.3:
resolution: {integrity: sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==}