mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-25 07:50:19 -06:00
UI tweaks (#216)
* preview hint, warnings, survey status indicator * add event in survey builder, add surveystatusdropdown * update wording, add survey status toasts, update LP --------- Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Matthias Nannt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -13,16 +13,18 @@ interface EventDetailModalProps {
|
||||
|
||||
export default function AddNoCodeEventModalDummy({ open, setOpen }: EventDetailModalProps) {
|
||||
return (
|
||||
<Modal open={open} setOpen={setOpen} noPadding closeOnOutsideClick={false}>
|
||||
<div className="flex h-full flex-col rounded-lg">
|
||||
<div className="rounded-t-lg bg-slate-100">
|
||||
<Modal open={open} setOpen={setOpen} noPadding>
|
||||
<div className="flex h-full flex-col rounded-lg bg-slate-50 dark:bg-slate-800">
|
||||
<div className="rounded-t-lg bg-slate-100 dark:bg-slate-700">
|
||||
<div className="flex items-center justify-between p-6">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="mr-1.5 h-6 w-6 text-slate-500">
|
||||
<CursorArrowRaysIcon />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xl font-medium text-slate-700">Add No-Code Event</div>
|
||||
<div className="text-xl font-medium text-slate-700 dark:text-slate-300">
|
||||
Add No-Code Event
|
||||
</div>
|
||||
<div className="text-sm text-slate-500">
|
||||
Create a new no-code event to filter your user base with.
|
||||
</div>
|
||||
@@ -36,21 +38,21 @@ export default function AddNoCodeEventModalDummy({ open, setOpen }: EventDetailM
|
||||
<div>
|
||||
<Label>Select By</Label>
|
||||
<RadioGroup className="grid grid-cols-2 gap-1 md:grid-cols-3" defaultValue="pageUrl">
|
||||
<div className="flex items-center space-x-2 rounded-lg border border-slate-200 p-3">
|
||||
<div className="flex items-center space-x-2 rounded-lg border border-slate-200 p-3 dark:border-slate-500">
|
||||
<RadioGroupItem value="pageUrl" id="pageUrl" className="bg-slate-50" />
|
||||
<Label htmlFor="pageUrl" className="cursor-pointer">
|
||||
<Label htmlFor="pageUrl" className="cursor-pointer dark:text-slate-200">
|
||||
Page URL
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 rounded-lg bg-slate-50 p-3">
|
||||
<div className="flex items-center space-x-2 rounded-lg bg-slate-50 p-3 dark:bg-slate-600">
|
||||
<RadioGroupItem disabled value="innerHtml" id="innerHtml" className="bg-slate-50" />
|
||||
<Label
|
||||
htmlFor="innerHtml"
|
||||
className="flex cursor-not-allowed items-center text-slate-500">
|
||||
className="flex cursor-not-allowed items-center text-slate-500 dark:text-slate-400">
|
||||
Inner Text
|
||||
</Label>
|
||||
</div>
|
||||
<div className="hidden items-center space-x-2 rounded-lg bg-slate-50 p-3 md:flex">
|
||||
<div className="hidden items-center space-x-2 rounded-lg bg-slate-50 p-3 dark:bg-slate-600 md:flex">
|
||||
<RadioGroupItem disabled value="cssSelector" id="cssSelector" className="bg-slate-50" />
|
||||
<Label
|
||||
htmlFor="cssSelector"
|
||||
@@ -73,10 +75,9 @@ export default function AddNoCodeEventModalDummy({ open, setOpen }: EventDetailM
|
||||
<div className="grid w-full grid-cols-3 gap-x-8">
|
||||
<div className="col-span-1">
|
||||
<Label>URL</Label>
|
||||
|
||||
<Select defaultValue="endsWith">
|
||||
<SelectTrigger
|
||||
className="w-[110px] md:w-[180px]"
|
||||
className="w-[110px] dark:text-slate-200 md:w-[180px]"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
disabled>
|
||||
<SelectValue placeholder="Select match type" />
|
||||
@@ -98,7 +99,7 @@ export default function AddNoCodeEventModalDummy({ open, setOpen }: EventDetailM
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end border-t border-slate-200 p-6">
|
||||
<div className="flex justify-end border-t border-slate-200 p-6 dark:border-slate-700">
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
variant="minimal"
|
||||
|
||||
@@ -290,7 +290,7 @@ export const templates: Template[] = [
|
||||
id: createId(),
|
||||
type: "multipleChoiceSingle",
|
||||
headline: "What's on your mind, boss?",
|
||||
subheader: "Thanks for sharing feedback. We'll get back to you asap.",
|
||||
subheader: "Thanks for sharing. We'll get back to you asap.",
|
||||
required: true,
|
||||
choices: [
|
||||
{
|
||||
@@ -301,10 +301,6 @@ export const templates: Template[] = [
|
||||
id: createId(),
|
||||
label: "Feature Request 💡",
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
label: "Share some love 🤍",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function Hero({}: Props) {
|
||||
|
||||
<div className="mx-auto mt-5 max-w-lg items-center space-x-8 sm:flex sm:justify-center md:mt-8">
|
||||
<p className="hidden whitespace-nowrap text-sm text-slate-400 dark:text-slate-500 md:block">
|
||||
Used by
|
||||
Trusted by
|
||||
</p>
|
||||
<div className="grid grid-cols-3 gap-8 pt-2">
|
||||
<Image
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function Highlights({}) {
|
||||
<h2 className="xs:text-3xl text-2xl font-bold tracking-tight text-slate-800 dark:text-slate-100 sm:text-3xl">
|
||||
‘Spray and pray’ never worked.
|
||||
<br />
|
||||
<span className="font-light">Segment users, granularly.</span>
|
||||
<span className="font-light">Pre-segment users, granularly.</span>
|
||||
</h2>
|
||||
<p className="text-md mt-6 max-w-md leading-7 text-slate-500 dark:text-slate-400">
|
||||
Pre-segment who sees your survey based on custom attributes. Keep the signal, cancel out the
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function BreakerCTA({ inverted = false, teaser, headline, subhead
|
||||
inverted
|
||||
? "from-slate-800 via-slate-800 to-slate-700 dark:from-slate-200 dark:to-slate-300"
|
||||
: "from-slate-200 to-slate-300 dark:from-slate-800 dark:via-slate-800 dark:to-slate-700",
|
||||
"xs:mx-auto xs:w-full mx-4 my-4 max-w-6xl rounded-xl bg-gradient-to-br md:mb-0 "
|
||||
"xs:mx-auto xs:w-full mx-4 my-4 mt-28 max-w-6xl rounded-xl bg-gradient-to-br md:mb-0 "
|
||||
)}>
|
||||
<div className="relative px-4 py-8 sm:px-6 sm:pt-8 sm:pb-12 lg:px-8 lg:pt-12">
|
||||
<div className="xs:block xs:absolute xs:right-10 hidden md:top-1/2 md:-translate-y-1/2">
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function Header() {
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="#pricing"
|
||||
href="https://formbricks.com/#pricing"
|
||||
className="text-base font-medium text-slate-400 hover:text-slate-700 dark:hover:text-slate-300">
|
||||
Pricing
|
||||
</Link>
|
||||
|
||||
@@ -53,7 +53,7 @@ const Modal: React.FC<Modal> = ({
|
||||
<div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md bg-white text-slate-400 hover:text-slate-500 focus:outline-none focus:ring-0 focus:ring-offset-2"
|
||||
className="rounded-md bg-white text-slate-400 hover:text-slate-500 focus:outline-none focus:ring-0 focus:ring-offset-2 dark:bg-slate-900"
|
||||
onClick={() => setOpen(false)}>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
|
||||
@@ -4,8 +4,7 @@ import remarkGfm from "remark-gfm";
|
||||
import rehypePrism from "@mapbox/rehype-prism";
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
output: "standalone",
|
||||
transpilePackages: ["@formbricks/ui"],
|
||||
pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],
|
||||
async redirects() {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@docsearch/react": "^3.3.3",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"@headlessui/react": "^1.7.13",
|
||||
"@heroicons/react": "^2.0.16",
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@mapbox/rehype-prism": "^0.8.0",
|
||||
"@mdx-js/loader": "^2.3.0",
|
||||
"@mdx-js/react": "^2.3.0",
|
||||
@@ -23,29 +23,28 @@
|
||||
"lottie-web": "^5.10.2",
|
||||
"next": "13.2.4",
|
||||
"next-plausible": "^3.7.2",
|
||||
"next-sitemap": "^4.0.5",
|
||||
"next-sitemap": "^4.0.6",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.43.5",
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-responsive-embed": "^2.1.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.31.3"
|
||||
"sharp": "^0.32.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/node": "18.15.3",
|
||||
"@types/node": "18.15.11",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react": "18.0.33",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@types/react-responsive-embed": "^2.1.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "8.36.0",
|
||||
"eslint": "8.37.0",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"postcss": "^8.4.21",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"typescript": "4.9.5"
|
||||
"tailwindcss": "^3.3.1",
|
||||
"typescript": "5.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import Hero from "@/components/home/Hero";
|
||||
import Features from "@/components/home/Features";
|
||||
import Highlights from "@/components/home/Highlights"; /*
|
||||
import BreakerCTA from "@/components/shared/BreakerCTA"; */
|
||||
import Highlights from "@/components/home/Highlights";
|
||||
import BreakerCTA from "@/components/shared/BreakerCTA";
|
||||
import Steps from "@/components/home/Steps";
|
||||
import Pricing from "@/components/shared/Pricing";
|
||||
|
||||
@@ -12,24 +12,23 @@ const IndexPage = () => (
|
||||
description="Build qualitative user research into your product. Leverage Best practices to increase Product-Market Fit.">
|
||||
<Hero />
|
||||
<Highlights />
|
||||
{/* <BreakerCTA
|
||||
teaser="Curious?"
|
||||
headline="Find out more"
|
||||
subheadline="Dive into the project on GitHub."
|
||||
cta="View on GitHub"
|
||||
href="https://www.formbricks.com/github"
|
||||
/> */}
|
||||
|
||||
<BreakerCTA
|
||||
teaser="READY?"
|
||||
headline="It's free to get started."
|
||||
subheadline="Don’t take our word for it, try it yourself."
|
||||
cta="Start for free"
|
||||
href="https://app.formbricks.com/auth/signup"
|
||||
/>
|
||||
<Features />
|
||||
<Steps />
|
||||
{/* <BreakerCTA
|
||||
<BreakerCTA
|
||||
teaser="Curious?"
|
||||
headline="Get access now"
|
||||
subheadline="We’re onboarding design partners regularly. Sign up to get early access."
|
||||
cta="Get access"
|
||||
href="/waitlist"
|
||||
headline="Give it a squeeze 🍋"
|
||||
subheadline="Formbricks is free to get started. Give it a go!"
|
||||
cta="Get started"
|
||||
href="https://app.formbricks.com/auth/signup"
|
||||
inverted
|
||||
/> */}
|
||||
/>
|
||||
<Pricing />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { signOut } from "next-auth/react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import useSWR from "swr";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
|
||||
export function HomeRedirect() {
|
||||
const { data, error } = useSWR(`/api/v1/environments/find-first`, fetcher);
|
||||
@@ -24,5 +25,9 @@ export function HomeRedirect() {
|
||||
return <div>There was an error with your current Session. You are getting redirected to the login.</div>;
|
||||
}
|
||||
|
||||
return <div>Loading environment...</div>;
|
||||
return (
|
||||
<div>
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import Modal from "@/components/shared/Modal";
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { Input } from "@formbricks/ui";
|
||||
import { Label } from "@formbricks/ui";
|
||||
import { RadioGroup, RadioGroupItem } from "@formbricks/ui";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui";
|
||||
import { CursorArrowRaysIcon } from "@heroicons/react/24/solid";
|
||||
import { useState } from "react";
|
||||
import { testURLmatch } from "./testURLmatch";
|
||||
import clsx from "clsx";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { createEventClass } from "@/lib/eventClasses/eventClasses";
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@formbricks/ui";
|
||||
import { CursorArrowRaysIcon } from "@heroicons/react/24/solid";
|
||||
import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { testURLmatch } from "./testURLmatch";
|
||||
|
||||
interface EventDetailModalProps {
|
||||
environmentId: string;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import { ErrorComponent } from "@/../../packages/ui";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useEnvironment } from "@/lib/environments/environments";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export default function EnvironmentNotice({ environmentId }: { environmentId: string }) {
|
||||
const { environment, isErrorEnvironment, isLoadingEnvironment } = useEnvironment(environmentId);
|
||||
const router = useRouter();
|
||||
|
||||
const changeEnvironment = (environmentType: string) => {
|
||||
const newEnvironmentId = environment.product.environments.find((e) => e.type === environmentType)?.id;
|
||||
router.push(`/environments/${newEnvironmentId}/`);
|
||||
};
|
||||
|
||||
if (isLoadingEnvironment) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (isErrorEnvironment) {
|
||||
return <ErrorComponent />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{environment.type === "production" && !environment.widgetSetupCompleted && (
|
||||
<div className="flex items-center rounded-lg border border-amber-200 bg-amber-100 p-6 text-slate-900 shadow-sm">
|
||||
<ExclamationTriangleIcon className="mr-3 h-6 w-6 text-amber-400" />
|
||||
You're currently in the Production environment.
|
||||
<a
|
||||
onClick={() => changeEnvironment("development")}
|
||||
className="ml-1 cursor-pointer font-medium underline">
|
||||
Set up Development environment?
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export default function SetupInstructions({ environmentId }) {
|
||||
{activeTab === "npm" ? (
|
||||
<div className="prose prose-slate">
|
||||
<p className="text-lg font-semibold text-slate-800">Step 1: NPM Install</p>
|
||||
<CodeBlock language="sh">npm install @formbricks/js</CodeBlock>
|
||||
<CodeBlock language="sh">npm install @formbricks/js --save</CodeBlock>
|
||||
<p className="pt-4 text-lg font-semibold text-slate-800">Step 2: Initialize widget</p>
|
||||
<p>Import Formbricks and initialize the widget in your Component (e.g. App.tsx):</p>
|
||||
<CodeBlock language="js">{`import formbricks from "@formbricks/js";
|
||||
@@ -102,9 +102,11 @@ if (typeof window !== "undefined") {
|
||||
<p>
|
||||
Insert this code into the <code>{`<head>`}</code> tag of your website:
|
||||
</p>
|
||||
<CodeBlock language="js">{`<script type="text/javascript">
|
||||
<CodeBlock language="js">{`<!-- START Formbricks Surveys -->
|
||||
<script type="text/javascript">
|
||||
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@0.1.2/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks=window.js;window.formbricks.init({environmentId: "${environmentId}", apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
|
||||
</script>`}</CodeBlock>
|
||||
</script>
|
||||
<!-- END Formbricks Surveys -->`}</CodeBlock>
|
||||
<p className="text-lg font-semibold text-slate-800">You're done 🎉</p>
|
||||
<p>
|
||||
Your app now communicates with Formbricks - sending events, and loading surveys automatically!
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import WidgetStatusIndicator from "@/components/shared/WidgetStatusIndicator";
|
||||
import SettingsCard from "../SettingsCard";
|
||||
import SettingsTitle from "../SettingsTitle";
|
||||
import EnvironmentNotice from "./EnvironmentNotice";
|
||||
import SetupInstructions from "./SetupInstructions";
|
||||
|
||||
export default function ProfileSettingsPage({ params }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="space-y-4">
|
||||
<SettingsTitle title="Setup Checklist" />
|
||||
<SettingsCard title="Widget Status" description="Check if the Formbricks widget is alive and kicking.">
|
||||
<WidgetStatusIndicator environmentId={params.environmentId} type="large" />
|
||||
</SettingsCard>
|
||||
<div className="mt-10">
|
||||
<SettingsCard
|
||||
title="How to setup"
|
||||
description="Follow these steps to setup the Formbricks widget within your app"
|
||||
noPadding>
|
||||
<SetupInstructions environmentId={params.environmentId} />
|
||||
</SettingsCard>
|
||||
</div>
|
||||
|
||||
<EnvironmentNotice environmentId={params.environmentId} />
|
||||
<SettingsCard
|
||||
title="How to setup"
|
||||
description="Follow these steps to setup the Formbricks widget within your app"
|
||||
noPadding>
|
||||
<SetupInstructions environmentId={params.environmentId} />
|
||||
</SettingsCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { Badge } from "@formbricks/ui";
|
||||
import { Label } from "@formbricks/ui";
|
||||
import { RadioGroup, RadioGroupItem } from "@formbricks/ui";
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
LinkIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
|
||||
const options = [
|
||||
@@ -48,20 +48,23 @@ const options = [
|
||||
interface HowToSendCardProps {}
|
||||
|
||||
export default function HowToSendCard({}: HowToSendCardProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
className="w-full space-y-2 rounded-lg border border-slate-300 bg-white">
|
||||
className={cn(
|
||||
open ? "" : "hover:bg-slate-50",
|
||||
"w-full space-y-2 rounded-lg border border-slate-300 bg-white "
|
||||
)}>
|
||||
<Collapsible.CollapsibleTrigger asChild className="h-full w-full cursor-pointer">
|
||||
<div className="inline-flex px-4 py-6">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
<CheckCircleIcon className="h-8 w-8 text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">How to send</p>
|
||||
<p className="font-semibold text-slate-800">How to ask</p>
|
||||
<p className="mt-1 truncate text-sm text-slate-500">
|
||||
Choose how you want to reach your audience.
|
||||
</p>
|
||||
@@ -76,7 +79,7 @@ export default function HowToSendCard({}: HowToSendCardProps) {
|
||||
<Label
|
||||
key={option.id}
|
||||
htmlFor={option.id}
|
||||
className={clsx(
|
||||
className={cn(
|
||||
"flex w-full items-center rounded-lg border bg-slate-50 p-4",
|
||||
option.comingSoon
|
||||
? "border-slate-200 bg-slate-50/50"
|
||||
@@ -93,7 +96,7 @@ export default function HowToSendCard({}: HowToSendCardProps) {
|
||||
<div>
|
||||
<div className="inline-flex items-center">
|
||||
<p
|
||||
className={clsx(
|
||||
className={cn(
|
||||
"font-semibold",
|
||||
option.comingSoon ? "text-slate-500" : "text-slate-800"
|
||||
)}>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { CheckCircleIcon } from "@heroicons/react/24/solid";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
|
||||
interface DisplayOption {
|
||||
id: "displayOnce" | "displayMultiple" | "respondMultiple";
|
||||
@@ -19,18 +20,18 @@ interface DisplayOption {
|
||||
const displayOptions: DisplayOption[] = [
|
||||
{
|
||||
id: "displayOnce",
|
||||
name: "Show once",
|
||||
description: "If person doesn't respond the survey won't be shown again.",
|
||||
name: "Only once, even if they do not respond",
|
||||
description: "The survey won't be shown again, if person doesn't respond.",
|
||||
},
|
||||
{
|
||||
id: "displayMultiple",
|
||||
name: "Display until responded",
|
||||
name: "Until they submit a response",
|
||||
description: "If you really want that answer, ask until you get it.",
|
||||
},
|
||||
{
|
||||
id: "respondMultiple",
|
||||
name: "Always display when conditions match",
|
||||
description: "This is useful for e.g. Feedback Boxes. Can cause survey fatigue.",
|
||||
name: "Always, when the conditions match",
|
||||
description: "Even after they submitted a response (e.g. Feedback Box)",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -73,7 +74,10 @@ export default function RecontactOptionsCard({
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
className=" w-full space-y-2 rounded-lg border border-slate-300 bg-white">
|
||||
className={cn(
|
||||
open ? "" : "hover:bg-slate-50",
|
||||
"w-full space-y-2 rounded-lg border border-slate-300 bg-white "
|
||||
)}>
|
||||
<Collapsible.CollapsibleTrigger asChild className="h-full w-full cursor-pointer">
|
||||
<div className="inline-flex px-4 py-6">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
|
||||
@@ -4,9 +4,8 @@ import { Label } from "@formbricks/ui";
|
||||
import { RadioGroup, RadioGroupItem } from "@formbricks/ui";
|
||||
import { CheckCircleIcon } from "@heroicons/react/24/solid";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
|
||||
import { Badge } from "@formbricks/ui";
|
||||
import clsx from "clsx";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { useState } from "react";
|
||||
|
||||
const options = [
|
||||
@@ -33,7 +32,10 @@ export default function ResponseOptionsCard({}: ResponseOptionsCardProps) {
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
className="w-full space-y-2 rounded-lg border border-slate-300 bg-white">
|
||||
className={cn(
|
||||
open ? "" : "hover:bg-slate-50",
|
||||
"w-full space-y-2 rounded-lg border border-slate-300 bg-white "
|
||||
)}>
|
||||
<Collapsible.CollapsibleTrigger asChild className="h-full w-full cursor-pointer">
|
||||
<div className="inline-flex px-4 py-6">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
@@ -59,7 +61,7 @@ export default function ResponseOptionsCard({}: ResponseOptionsCardProps) {
|
||||
<Label
|
||||
key={option.id}
|
||||
htmlFor={option.id}
|
||||
className={clsx(
|
||||
className={cn(
|
||||
"flex w-full items-center rounded-lg border bg-slate-50 p-4",
|
||||
option.disabled
|
||||
? "border-slate-200 bg-slate-50/50"
|
||||
@@ -73,11 +75,7 @@ export default function ResponseOptionsCard({}: ResponseOptionsCardProps) {
|
||||
/>
|
||||
<div>
|
||||
<div className="inline-flex items-center">
|
||||
<p
|
||||
className={clsx(
|
||||
"font-semibold",
|
||||
option.disabled ? "text-slate-500" : "text-slate-800"
|
||||
)}>
|
||||
<p className={cn("font-semibold", option.disabled ? "text-slate-500" : "text-slate-800")}>
|
||||
{option.name}
|
||||
</p>
|
||||
{option.disabled && <Badge text="coming soon" size="normal" type="warning" />}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { ErrorComponent } from "@formbricks/ui";
|
||||
import { useProduct } from "@/lib/products/products";
|
||||
import { useSurvey } from "@/lib/surveys/surveys";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { ErrorComponent } from "@formbricks/ui";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
||||
import { useEffect, useState } from "react";
|
||||
import PreviewSurvey from "../../PreviewSurvey";
|
||||
import AudienceView from "./AudienceView";
|
||||
@@ -56,6 +57,13 @@ export default function SurveyEditor({ environmentId, surveyId }: SurveyEditorPr
|
||||
/>
|
||||
<div className="relative z-0 flex flex-1 overflow-hidden">
|
||||
<main className="relative z-0 flex-1 overflow-y-auto focus:outline-none">
|
||||
{survey.status !== "draft" && (
|
||||
<div className="flex items-center border border-red-200 bg-red-100 p-2 text-sm text-slate-700 shadow-sm">
|
||||
<ExclamationTriangleIcon className="mr-3 h-6 w-6 text-red-400" />
|
||||
You're editing a published survey. Be cautious when making changes, they might mess up the
|
||||
data.
|
||||
</div>
|
||||
)}
|
||||
<QuestionsAudienceTabs activeId={activeView} setActiveId={setActiveView} />
|
||||
{activeView === "questions" ? (
|
||||
<QuestionsView
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { Input } from "@formbricks/ui";
|
||||
import SurveyStatusDropdown from "@/components/shared/SurveyStatusDropdown";
|
||||
import { useSurveyMutation } from "@/lib/surveys/mutateSurveys";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { Button, Input } from "@formbricks/ui";
|
||||
import { UserGroupIcon } from "@heroicons/react/24/solid";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface SurveyMenuBarProps {
|
||||
localSurvey: Survey;
|
||||
@@ -33,18 +34,33 @@ export default function SurveyMenuBar({
|
||||
}
|
||||
}, [activeId, audiencePrompt]);
|
||||
|
||||
// write a function which updates the local survey status
|
||||
const updateLocalSurveyStatus = (status: Survey["status"]) => {
|
||||
const updatedSurvey = { ...localSurvey, status };
|
||||
setLocalSurvey(updatedSurvey);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border-b border-slate-200 bg-white py-3 px-5 sm:flex sm:items-center sm:justify-between">
|
||||
<Input
|
||||
defaultValue={localSurvey.name}
|
||||
onChange={(e) => {
|
||||
const updatedSurvey = { ...localSurvey, name: e.target.value };
|
||||
setLocalSurvey(updatedSurvey);
|
||||
}}
|
||||
className="max-w-md"
|
||||
/>
|
||||
<div className="flex space-x-2 whitespace-nowrap">
|
||||
<Input
|
||||
defaultValue={localSurvey.name}
|
||||
onChange={(e) => {
|
||||
const updatedSurvey = { ...localSurvey, name: e.target.value };
|
||||
setLocalSurvey(updatedSurvey);
|
||||
}}
|
||||
className="min-w-sm max-w-md"
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
<SurveyStatusDropdown
|
||||
surveyId={localSurvey.id}
|
||||
environmentId={environmentId}
|
||||
updateLocalSurveyStatus={updateLocalSurveyStatus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 flex sm:mt-0 sm:ml-4">
|
||||
<Button variant="minimal" className="mr-3" href={`/environments/${environmentId}/surveys/`}>
|
||||
<Button variant="minimal" className="mr-3" onClick={() => router.back()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
@@ -52,12 +68,18 @@ export default function SurveyMenuBar({
|
||||
className="mr-3"
|
||||
loading={isMutatingSurvey}
|
||||
onClick={() => {
|
||||
triggerSurveyMutate({ ...localSurvey });
|
||||
triggerSurveyMutate({ ...localSurvey })
|
||||
.then(() => {
|
||||
toast.success("Changes saved.");
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`Error: ${error.message}`);
|
||||
});
|
||||
if (localSurvey.status !== "draft") {
|
||||
router.push(`/environments/${environmentId}/surveys/${localSurvey.id}/summary?success=true`);
|
||||
router.push(`/environments/${environmentId}/surveys/${localSurvey.id}/summary`);
|
||||
}
|
||||
}}>
|
||||
Save changes
|
||||
Save Changes
|
||||
</Button>
|
||||
{localSurvey.status === "draft" && audiencePrompt && (
|
||||
<Button
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui";
|
||||
import { useEventClasses } from "@/lib/eventClasses/eventClasses";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui";
|
||||
import { CheckCircleIcon, PlusIcon, TrashIcon } from "@heroicons/react/24/solid";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { useState } from "react";
|
||||
import AddNoCodeEventModal from "../../../events/AddNoCodeEventModal";
|
||||
|
||||
interface WhenToSendCardProps {
|
||||
localSurvey: Survey;
|
||||
@@ -17,7 +18,9 @@ interface WhenToSendCardProps {
|
||||
|
||||
export default function WhenToSendCard({ environmentId, localSurvey, setLocalSurvey }: WhenToSendCardProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { eventClasses, isLoadingEventClasses, isErrorEventClasses } = useEventClasses(environmentId);
|
||||
const { eventClasses, isLoadingEventClasses, isErrorEventClasses, mutateEventClasses } =
|
||||
useEventClasses(environmentId);
|
||||
const [isAddEventModalOpen, setAddEventModalOpen] = useState(false);
|
||||
|
||||
if (isLoadingEventClasses) {
|
||||
return <LoadingSpinner />;
|
||||
@@ -45,64 +48,89 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
|
||||
setLocalSurvey(updatedSurvey);
|
||||
};
|
||||
|
||||
return (
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
className="w-full space-y-2 rounded-lg border border-slate-300 bg-white">
|
||||
<Collapsible.CollapsibleTrigger asChild className="h-full w-full cursor-pointer">
|
||||
<div className="inline-flex px-4 py-6">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
{localSurvey.triggers.length === 0 || !localSurvey.triggers[0] ? (
|
||||
<div className="h-7 w-7 rounded-full border border-slate-400" />
|
||||
) : (
|
||||
<CheckCircleIcon className="h-8 w-8 text-green-400" />
|
||||
)}
|
||||
</div>
|
||||
/* // If there are no trigger events, set default to first event class in the eventClasses object
|
||||
if (localSurvey.triggers.length === 0 && eventClasses.length > 0) {
|
||||
setTriggerEvent(0, eventClasses[0].id);
|
||||
} */
|
||||
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">When to send</p>
|
||||
<p className="mt-1 truncate text-sm text-slate-500">
|
||||
Choose the events when you want the survey to trigger.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="">
|
||||
<hr className="py-1 text-slate-600" />
|
||||
{localSurvey.triggers.map((triggerEventClassId, idx) => (
|
||||
<div className="mt-2" key={idx}>
|
||||
<div className="inline-flex items-center">
|
||||
<p className="mr-2 w-14 text-right text-sm">{idx === 0 ? "When" : "or"}</p>
|
||||
<Select
|
||||
value={triggerEventClassId}
|
||||
onValueChange={(eventClassId) => setTriggerEvent(idx, eventClassId)}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{eventClasses.map((eventClass) => (
|
||||
<SelectItem value={eventClass.id}>{eventClass.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<button onClick={() => removeTriggerEvent(idx)}>
|
||||
<TrashIcon className="ml-3 h-4 w-4 text-slate-400" />
|
||||
</button>
|
||||
return (
|
||||
<>
|
||||
{" "}
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
className={cn(
|
||||
open ? "" : "hover:bg-slate-50",
|
||||
"w-full space-y-2 rounded-lg border border-slate-300 bg-white "
|
||||
)}>
|
||||
<Collapsible.CollapsibleTrigger asChild className="h-full w-full cursor-pointer">
|
||||
<div className="inline-flex px-4 py-6">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
{localSurvey.triggers.length === 0 || !localSurvey.triggers[0] ? (
|
||||
<div className="h-7 w-7 rounded-full border border-slate-400" />
|
||||
) : (
|
||||
<CheckCircleIcon className="h-8 w-8 text-green-400" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">When to ask</p>
|
||||
<p className="mt-1 truncate text-sm text-slate-500">
|
||||
Choose the events which trigger the survey.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="p-3">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
addTriggerEvent();
|
||||
}}>
|
||||
<PlusIcon className="mr-2 h-4 w-4" />
|
||||
Add event
|
||||
</Button>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="">
|
||||
<hr className="py-1 text-slate-600" />
|
||||
{localSurvey.triggers.map((triggerEventClassId, idx) => (
|
||||
<div className="mt-2" key={idx}>
|
||||
<div className="inline-flex items-center">
|
||||
<p className="mr-2 w-14 text-right text-sm">{idx === 0 ? "When" : "or"}</p>
|
||||
<Select
|
||||
value={triggerEventClassId}
|
||||
onValueChange={(eventClassId) => setTriggerEvent(idx, eventClassId)}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{eventClasses.map((eventClass) => (
|
||||
<SelectItem value={eventClass.id}>{eventClass.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="mx-2 text-sm">event is triggered</p>
|
||||
<button onClick={() => removeTriggerEvent(idx)}>
|
||||
<TrashIcon className="ml-3 h-4 w-4 text-slate-400" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="p-3">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
addTriggerEvent();
|
||||
}}>
|
||||
<PlusIcon className="mr-2 h-4 w-4" />
|
||||
Add condition
|
||||
</Button>
|
||||
<Button
|
||||
variant="minimal"
|
||||
onClick={() => {
|
||||
setAddEventModalOpen(true);
|
||||
}}>
|
||||
Create event
|
||||
</Button>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
<AddNoCodeEventModal
|
||||
environmentId={environmentId}
|
||||
open={isAddEventModalOpen}
|
||||
setOpen={setAddEventModalOpen}
|
||||
mutateEventClasses={mutateEventClasses}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Label } from "@formbricks/ui";
|
||||
import { RadioGroup, RadioGroupItem } from "@formbricks/ui";
|
||||
import { CheckCircleIcon } from "@heroicons/react/24/solid";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import clsx from "clsx";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { useState } from "react";
|
||||
|
||||
const options = [
|
||||
@@ -32,14 +32,17 @@ export default function WhoToSendToCard({}: WhoToSendToCardProps) {
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
className="w-full space-y-2 rounded-lg border border-slate-300 bg-white">
|
||||
className={cn(
|
||||
open ? "" : "hover:bg-slate-50",
|
||||
"w-full space-y-2 rounded-lg border border-slate-300 bg-white "
|
||||
)}>
|
||||
<Collapsible.CollapsibleTrigger asChild className="h-full w-full cursor-pointer">
|
||||
<div className="inline-flex px-4 py-6">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
<CheckCircleIcon className="h-8 w-8 text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">Who to send to</p>
|
||||
<p className="font-semibold text-slate-800">Who to ask</p>
|
||||
<p className="mt-1 truncate text-sm text-slate-500">
|
||||
Decide which group of you users can be surveyed.
|
||||
</p>
|
||||
@@ -54,7 +57,7 @@ export default function WhoToSendToCard({}: WhoToSendToCardProps) {
|
||||
<Label
|
||||
key={option.id}
|
||||
htmlFor={option.id}
|
||||
className={clsx(
|
||||
className={cn(
|
||||
"flex w-full items-center rounded-lg border bg-slate-50 p-4",
|
||||
option.disabled
|
||||
? "border-slate-200 bg-slate-50/50"
|
||||
@@ -68,11 +71,7 @@ export default function WhoToSendToCard({}: WhoToSendToCardProps) {
|
||||
/>
|
||||
<div>
|
||||
<div className="inline-flex items-center">
|
||||
<p
|
||||
className={clsx(
|
||||
"font-semibold",
|
||||
option.disabled ? "text-slate-500" : "text-slate-800"
|
||||
)}>
|
||||
<p className={cn("font-semibold", option.disabled ? "text-slate-500" : "text-slate-800")}>
|
||||
{option.name}
|
||||
</p>
|
||||
{option.disabled && <Badge text="coming soon" size="normal" type="warning" />}
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator";
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { ErrorComponent } from "@formbricks/ui";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui";
|
||||
import { useEnvironment } from "@/lib/environments/environments";
|
||||
import { useResponses } from "@/lib/responses/responses";
|
||||
import { useSurveyMutation } from "@/lib/surveys/mutateSurveys";
|
||||
import { useSurvey } from "@/lib/surveys/surveys";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
PauseCircleIcon,
|
||||
PencilSquareIcon,
|
||||
PlayCircleIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
Button,
|
||||
ErrorComponent,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@formbricks/ui";
|
||||
import { PencilSquareIcon } from "@heroicons/react/24/solid";
|
||||
import { useMemo } from "react";
|
||||
import SurveyStatusDropdown from "@/components/shared/SurveyStatusDropdown";
|
||||
|
||||
export default function SummaryMetadata({ surveyId, environmentId }) {
|
||||
const { responses, isLoadingResponses, isErrorResponses } = useResponses(environmentId, surveyId);
|
||||
const { survey, isLoadingSurvey, isErrorSurvey } = useSurvey(environmentId, surveyId);
|
||||
const { triggerSurveyMutate } = useSurveyMutation(environmentId, surveyId);
|
||||
const { environment, isLoadingEnvironment, isErrorEnvironment } = useEnvironment(environmentId);
|
||||
|
||||
const completionRate = useMemo(() => {
|
||||
@@ -95,48 +92,7 @@ export default function SummaryMetadata({ surveyId, environmentId }) {
|
||||
<div className=""></div>
|
||||
<div className="flex justify-end">
|
||||
{environment.widgetSetupCompleted && (
|
||||
<>
|
||||
{survey.status === "draft" || survey.status === "archived" ? (
|
||||
<div className="flex items-center">
|
||||
<SurveyStatusIndicator status={survey.status} environmentId={environmentId} />
|
||||
<span className="mr-3 italic text-slate-500">
|
||||
{survey.status === "draft" && "Survey drafted"}
|
||||
{survey.status === "archived" && "Survey archived"}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<Select onValueChange={(value) => triggerSurveyMutate({ status: value })}>
|
||||
<SelectTrigger className="w-[200px] bg-white py-1.5">
|
||||
<SelectValue>
|
||||
<div className="flex items-center">
|
||||
<SurveyStatusIndicator status={survey.status} environmentId={environmentId} />
|
||||
<span className="ml-2 text-sm text-slate-700">
|
||||
{survey.status === "draft" && "Survey drafted"}
|
||||
{survey.status === "inProgress" && "Collecting insights"}
|
||||
{survey.status === "paused" && "Survey paused"}
|
||||
{survey.status === "completed" && "Survey complete"}
|
||||
{survey.status === "archived" && "Survey archived"}
|
||||
</span>
|
||||
</div>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-white">
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="inProgress">
|
||||
<PlayCircleIcon className="mr-1 -mt-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
Collect insights
|
||||
</SelectItem>
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="paused">
|
||||
<PauseCircleIcon className="mr-1 -mt-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
Pause Survey
|
||||
</SelectItem>
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="completed">
|
||||
<CheckCircleIcon className="mr-1 -mt-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
End Survey
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</>
|
||||
<SurveyStatusDropdown surveyId={surveyId} environmentId={environmentId} />
|
||||
)}
|
||||
<Button className="ml-1.5 h-full" href={`/environments/${environmentId}/surveys/${surveyId}/edit`}>
|
||||
<PencilSquareIcon className="mr-2 h-5 w-5 text-white" /> Edit Survey
|
||||
|
||||
@@ -603,7 +603,7 @@ export const templates: Template[] = [
|
||||
id: createId(),
|
||||
type: "multipleChoiceSingle",
|
||||
headline: "What's on your mind, boss?",
|
||||
subheader: "Thanks for sharing feedback. We'll get back to you asap.",
|
||||
subheader: "Thanks for sharing. We'll get back to you asap.",
|
||||
required: true,
|
||||
choices: [
|
||||
{
|
||||
@@ -614,10 +614,6 @@ export const templates: Template[] = [
|
||||
id: createId(),
|
||||
label: "Feature Request 💡",
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
label: "Share some love 🤍",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default function Headline({ headline, questionId }: { headline: string; questionId: string }) {
|
||||
return (
|
||||
<label htmlFor={questionId} className="block text-base font-semibold leading-6 text-slate-900">
|
||||
<label htmlFor={questionId} className="mb-1.5 block text-base font-semibold leading-6 text-slate-900">
|
||||
{headline}
|
||||
</label>
|
||||
);
|
||||
|
||||
@@ -11,11 +11,12 @@ export default function Modal({ children, isOpen }: { children: ReactNode; isOpe
|
||||
<div
|
||||
aria-live="assertive"
|
||||
className="pointer-events-none absolute inset-0 flex items-end px-4 py-6 sm:p-6">
|
||||
<div className="flex w-full flex-col items-center space-y-4 sm:items-end">
|
||||
<div className="flex w-full flex-col items-center sm:items-end">
|
||||
<div className="mr-6 rounded-t bg-amber-400 px-3 text-sm font-semibold text-white">Preview</div>
|
||||
<div
|
||||
className={cn(
|
||||
show ? "translate-x-0 opacity-100" : "translate-x-28 opacity-0",
|
||||
"pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white px-4 py-6 shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-500 ease-in-out sm:p-6"
|
||||
"pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg border-2 border-amber-400 bg-white px-4 py-6 shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-500 ease-in-out sm:p-6"
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default function Subheader({ subheader, questionId }: { subheader?: string; questionId: string }) {
|
||||
return (
|
||||
<label htmlFor={questionId} className="block text-sm font-normal leading-6 text-slate-600">
|
||||
<label htmlFor={questionId} className="block text-sm font-normal leading-6 text-slate-500">
|
||||
{subheader}
|
||||
</label>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// components/ui/CodeBlock.tsx
|
||||
import React, { useEffect } from "react";
|
||||
import { DocumentDuplicateIcon } from "@heroicons/react/24/outline";
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/themes/prism.css";
|
||||
import { DocumentDuplicateIcon } from "@heroicons/react/24/outline";
|
||||
import React, { useEffect } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface CodeBlockProps {
|
||||
@@ -16,7 +16,7 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, language }) => {
|
||||
}, [children]);
|
||||
|
||||
return (
|
||||
<div className="group relative mt-4 rounded-md font-light text-slate-200">
|
||||
<div className="group relative mt-4 rounded-md text-sm text-slate-200">
|
||||
<DocumentDuplicateIcon
|
||||
className="absolute top-4 right-4 z-20 h-5 w-5 cursor-pointer text-slate-600 opacity-0 transition-all duration-150 group-hover:opacity-60"
|
||||
onClick={() => {
|
||||
|
||||
102
apps/web/components/shared/SurveyStatusDropdown.tsx
Normal file
102
apps/web/components/shared/SurveyStatusDropdown.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
"use client";
|
||||
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator";
|
||||
import { useSurveyMutation } from "@/lib/surveys/mutateSurveys";
|
||||
import { useSurvey } from "@/lib/surveys/surveys";
|
||||
import {
|
||||
ErrorComponent,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@formbricks/ui";
|
||||
import { CheckCircleIcon, PauseCircleIcon, PlayCircleIcon } from "@heroicons/react/24/solid";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
export default function SurveyStatusDropdown({
|
||||
surveyId,
|
||||
environmentId,
|
||||
updateLocalSurveyStatus,
|
||||
}: {
|
||||
surveyId: string;
|
||||
environmentId: string;
|
||||
updateLocalSurveyStatus?: (status: "draft" | "inProgress" | "paused" | "completed" | "archived") => void;
|
||||
}) {
|
||||
const { survey, isLoadingSurvey, isErrorSurvey } = useSurvey(environmentId, surveyId);
|
||||
const { triggerSurveyMutate } = useSurveyMutation(environmentId, surveyId);
|
||||
|
||||
if (isLoadingSurvey) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (isErrorSurvey) {
|
||||
return <ErrorComponent />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{survey.status === "draft" || survey.status === "archived" ? (
|
||||
<div className="flex items-center">
|
||||
<SurveyStatusIndicator status={survey.status} environmentId={environmentId} />
|
||||
<span className="mr-3 italic text-slate-500">
|
||||
{survey.status === "draft" && "Survey drafted"}
|
||||
{survey.status === "archived" && "Survey archived"}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
triggerSurveyMutate({ status: value })
|
||||
.then(() => {
|
||||
toast.success(
|
||||
value === "inProgress"
|
||||
? "Survey live"
|
||||
: value === "paused"
|
||||
? "Survey paused"
|
||||
: value === "completed"
|
||||
? "Survey completed"
|
||||
: ""
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`Error: ${error.message}`);
|
||||
});
|
||||
|
||||
if (updateLocalSurveyStatus)
|
||||
updateLocalSurveyStatus(value as "draft" | "inProgress" | "paused" | "completed" | "archived");
|
||||
}}>
|
||||
<SelectTrigger className="w-[200px] bg-white py-1.5">
|
||||
<SelectValue>
|
||||
<div className="flex items-center">
|
||||
<SurveyStatusIndicator status={survey.status} environmentId={environmentId} />
|
||||
<span className="ml-2 text-sm text-slate-700">
|
||||
{survey.status === "draft" && "Survey drafted"}
|
||||
{survey.status === "inProgress" && "Collecting insights"}
|
||||
{survey.status === "paused" && "Survey paused"}
|
||||
{survey.status === "completed" && "Survey complete"}
|
||||
{survey.status === "archived" && "Survey archived"}
|
||||
</span>
|
||||
</div>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-white">
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="inProgress">
|
||||
<PlayCircleIcon className="mr-1 -mt-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
Collect insights
|
||||
</SelectItem>
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="paused">
|
||||
<PauseCircleIcon className="mr-1 -mt-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
Pause Survey
|
||||
</SelectItem>
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="completed">
|
||||
<CheckCircleIcon className="mr-1 -mt-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
Complete Survey
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,6 @@ const { createId } = require("@paralleldrive/cuid2");
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
appDir: true,
|
||||
serverComponentsExternalPackages: ["@tremor/react"],
|
||||
},
|
||||
output: "standalone",
|
||||
transpilePackages: ["@formbricks/database", "@formbricks/ee", "@formbricks/ui", "@formbricks/lib"],
|
||||
|
||||
@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, ...pr
|
||||
return (
|
||||
<input
|
||||
className={cn(
|
||||
"focus:border-brand flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"focus:border-brand flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-500 dark:text-slate-300",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -12,7 +12,7 @@ const Label = React.forwardRef<
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-sm font-medium leading-none text-slate-800 disabled:opacity-70 peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
"text-sm font-medium leading-none text-slate-800 disabled:opacity-70 peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-slate-400 dark:peer-disabled:opacity-70",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -37,7 +37,7 @@ const SelectContent = React.forwardRef<
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"animate-in fade-in-80 relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-slate-50 text-slate-700 shadow-md ",
|
||||
"animate-in fade-in-80 relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-slate-50 text-slate-700 shadow-md dark:text-slate-300 ",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
@@ -53,7 +53,7 @@ const SelectLabel = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pr-2 pl-8 text-sm font-semibold text-slate-900 ", className)}
|
||||
className={cn("py-1.5 pr-2 pl-8 text-sm font-semibold text-slate-900 dark:text-slate-200 ", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
@@ -66,7 +66,7 @@ const SelectItem = React.forwardRef<
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm font-medium outline-none hover:text-slate-500 focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 ",
|
||||
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pr-2 pl-2 text-sm font-medium outline-none hover:text-slate-500 focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 ",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
|
||||
231
pnpm-lock.yaml
generated
231
pnpm-lock.yaml
generated
@@ -70,7 +70,7 @@ importers:
|
||||
dependencies:
|
||||
'@docsearch/react':
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3(@algolia/client-search@4.14.2)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
|
||||
version: 3.3.3(@algolia/client-search@4.14.2)(@types/react@18.0.33)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@formbricks/ui':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/ui
|
||||
@@ -78,8 +78,8 @@ importers:
|
||||
specifier: ^1.7.13
|
||||
version: 1.7.13(react-dom@18.2.0)(react@18.2.0)
|
||||
'@heroicons/react':
|
||||
specifier: ^2.0.16
|
||||
version: 2.0.16(react@18.2.0)
|
||||
specifier: ^2.0.17
|
||||
version: 2.0.17(react@18.2.0)
|
||||
'@mapbox/rehype-prism':
|
||||
specifier: ^0.8.0
|
||||
version: 0.8.0
|
||||
@@ -108,8 +108,8 @@ importers:
|
||||
specifier: ^3.7.2
|
||||
version: 3.7.2(next@13.2.4)(react-dom@18.2.0)(react@18.2.0)
|
||||
next-sitemap:
|
||||
specifier: ^4.0.5
|
||||
version: 4.0.5(@next/env@13.2.4)(next@13.2.4)
|
||||
specifier: ^4.0.6
|
||||
version: 4.0.6(@next/env@13.2.4)(next@13.2.4)
|
||||
prism-react-renderer:
|
||||
specifier: ^1.3.5
|
||||
version: 1.3.5(react@18.2.0)
|
||||
@@ -123,8 +123,8 @@ importers:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
react-hook-form:
|
||||
specifier: ^7.43.5
|
||||
version: 7.43.5(react@18.2.0)
|
||||
specifier: ^7.43.9
|
||||
version: 7.43.9(react@18.2.0)
|
||||
react-responsive-embed:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0(prop-types@15.8.1)(react@18.2.0)
|
||||
@@ -132,36 +132,33 @@ importers:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
sharp:
|
||||
specifier: ^0.31.3
|
||||
version: 0.31.3
|
||||
specifier: ^0.32.0
|
||||
version: 0.32.0
|
||||
devDependencies:
|
||||
'@tailwindcss/forms':
|
||||
specifier: ^0.5.3
|
||||
version: 0.5.3(tailwindcss@3.2.7)
|
||||
version: 0.5.3(tailwindcss@3.3.1)
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.9
|
||||
version: 0.5.9(tailwindcss@3.2.7)
|
||||
version: 0.5.9(tailwindcss@3.3.1)
|
||||
'@types/node':
|
||||
specifier: 18.15.3
|
||||
version: 18.15.3
|
||||
specifier: 18.15.11
|
||||
version: 18.15.11
|
||||
'@types/prismjs':
|
||||
specifier: ^1.26.0
|
||||
version: 1.26.0
|
||||
'@types/react':
|
||||
specifier: 18.0.28
|
||||
version: 18.0.28
|
||||
specifier: 18.0.33
|
||||
version: 18.0.33
|
||||
'@types/react-dom':
|
||||
specifier: 18.0.11
|
||||
version: 18.0.11
|
||||
'@types/react-responsive-embed':
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
autoprefixer:
|
||||
specifier: ^10.4.14
|
||||
version: 10.4.14(postcss@8.4.21)
|
||||
eslint:
|
||||
specifier: 8.36.0
|
||||
version: 8.36.0
|
||||
specifier: 8.37.0
|
||||
version: 8.37.0
|
||||
eslint-config-formbricks:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/eslint-config-formbricks
|
||||
@@ -169,11 +166,11 @@ importers:
|
||||
specifier: ^8.4.21
|
||||
version: 8.4.21
|
||||
tailwindcss:
|
||||
specifier: ^3.2.7
|
||||
version: 3.2.7(postcss@8.4.21)
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1(postcss@8.4.21)
|
||||
typescript:
|
||||
specifier: 4.9.5
|
||||
version: 4.9.5
|
||||
specifier: 5.0.3
|
||||
version: 5.0.3
|
||||
|
||||
apps/web:
|
||||
dependencies:
|
||||
@@ -2310,7 +2307,7 @@ packages:
|
||||
resolution: {integrity: sha512-6SCwI7P8ao+se1TUsdZ7B4XzL+gqeQZnBc+2EONZlcVa0dVrk0NjETxozFKgMv0eEGH8QzP1fkN+A1rH61l4eg==}
|
||||
dev: false
|
||||
|
||||
/@docsearch/react@3.3.3(@algolia/client-search@4.14.2)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
|
||||
/@docsearch/react@3.3.3(@algolia/client-search@4.14.2)(@types/react@18.0.33)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-pLa0cxnl+G0FuIDuYlW+EBK6Rw2jwLw9B1RHIeS4N4s2VhsfJ/wzeCi3CWcs5yVfxLd5ZK50t//TMA5e79YT7Q==}
|
||||
peerDependencies:
|
||||
'@types/react': '>= 16.8.0 < 19.0.0'
|
||||
@@ -2327,7 +2324,7 @@ packages:
|
||||
'@algolia/autocomplete-core': 1.7.4
|
||||
'@algolia/autocomplete-preset-algolia': 1.7.4(@algolia/client-search@4.14.2)(algoliasearch@4.14.2)
|
||||
'@docsearch/css': 3.3.3
|
||||
'@types/react': 18.0.28
|
||||
'@types/react': 18.0.33
|
||||
algoliasearch: 4.14.2
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
@@ -2579,7 +2576,7 @@ packages:
|
||||
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||
dependencies:
|
||||
eslint: 8.36.0
|
||||
eslint-visitor-keys: 3.3.0
|
||||
eslint-visitor-keys: 3.4.0
|
||||
|
||||
/@eslint-community/eslint-utils@4.2.0(eslint@8.37.0):
|
||||
resolution: {integrity: sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==}
|
||||
@@ -2617,7 +2614,7 @@ packages:
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.3.4
|
||||
espree: 9.5.0
|
||||
espree: 9.5.1
|
||||
globals: 13.19.0
|
||||
ignore: 5.2.1
|
||||
import-fresh: 3.3.0
|
||||
@@ -2634,7 +2631,7 @@ packages:
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.3.4
|
||||
espree: 9.5.0
|
||||
espree: 9.5.1
|
||||
globals: 13.19.0
|
||||
ignore: 5.2.1
|
||||
import-fresh: 3.3.0
|
||||
@@ -2645,22 +2642,6 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@eslint/eslintrc@2.0.1:
|
||||
resolution: {integrity: sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.3.4
|
||||
espree: 9.5.0
|
||||
globals: 13.19.0
|
||||
ignore: 5.2.1
|
||||
import-fresh: 3.3.0
|
||||
js-yaml: 4.1.0
|
||||
minimatch: 3.1.2
|
||||
strip-json-comments: 3.1.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
/@eslint/eslintrc@2.0.2:
|
||||
resolution: {integrity: sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
@@ -2733,6 +2714,14 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@heroicons/react@2.0.17(react@18.2.0):
|
||||
resolution: {integrity: sha512-90GMZktkA53YbNzHp6asVEDevUQCMtxWH+2UK2S8OpnLEu7qckTJPhNxNQG52xIR1WFTwFqtH6bt7a60ZNcLLA==}
|
||||
peerDependencies:
|
||||
react: '>= 16'
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@humanwhocodes/config-array@0.11.7:
|
||||
resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==}
|
||||
engines: {node: '>=10.10.0'}
|
||||
@@ -3102,7 +3091,7 @@ packages:
|
||||
react: '>=16'
|
||||
dependencies:
|
||||
'@types/mdx': 2.0.3
|
||||
'@types/react': 18.0.32
|
||||
'@types/react': 18.0.33
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
@@ -4399,16 +4388,13 @@ packages:
|
||||
tailwindcss: 3.3.0(postcss@8.4.21)
|
||||
dev: true
|
||||
|
||||
/@tailwindcss/typography@0.5.9(tailwindcss@3.2.7):
|
||||
resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
|
||||
/@tailwindcss/forms@0.5.3(tailwindcss@3.3.1):
|
||||
resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders'
|
||||
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
|
||||
dependencies:
|
||||
lodash.castarray: 4.4.0
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.merge: 4.6.2
|
||||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 3.2.7(postcss@8.4.21)
|
||||
mini-svg-data-uri: 1.4.4
|
||||
tailwindcss: 3.3.1(postcss@8.4.21)
|
||||
dev: true
|
||||
|
||||
/@tailwindcss/typography@0.5.9(tailwindcss@3.3.0):
|
||||
@@ -4423,6 +4409,18 @@ packages:
|
||||
tailwindcss: 3.3.0(postcss@8.4.21)
|
||||
dev: true
|
||||
|
||||
/@tailwindcss/typography@0.5.9(tailwindcss@3.3.1):
|
||||
resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders'
|
||||
dependencies:
|
||||
lodash.castarray: 4.4.0
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.merge: 4.6.2
|
||||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 3.3.1(postcss@8.4.21)
|
||||
dev: true
|
||||
|
||||
/@tootallnate/once@1.1.2:
|
||||
resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -4524,7 +4522,7 @@ packages:
|
||||
resolution: {integrity: sha512-xryQlOEIe1TduDWAOphR0ihfebKFSWOXpIsk+70JskCfRfW+xALdnJ0r1ZOTo85F9Qsjk6vtlU7edTYHbls9tA==}
|
||||
dependencies:
|
||||
'@types/cheerio': 0.22.31
|
||||
'@types/react': 18.0.32
|
||||
'@types/react': 18.0.33
|
||||
dev: true
|
||||
|
||||
/@types/eslint-scope@3.7.4:
|
||||
@@ -4590,7 +4588,7 @@ packages:
|
||||
/@types/hoist-non-react-statics@3.3.1:
|
||||
resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==}
|
||||
dependencies:
|
||||
'@types/react': 18.0.32
|
||||
'@types/react': 18.0.33
|
||||
hoist-non-react-statics: 3.3.2
|
||||
dev: false
|
||||
|
||||
@@ -4677,10 +4675,6 @@ packages:
|
||||
/@types/node@18.15.11:
|
||||
resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
|
||||
|
||||
/@types/node@18.15.3:
|
||||
resolution: {integrity: sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==}
|
||||
dev: true
|
||||
|
||||
/@types/normalize-package-data@2.4.1:
|
||||
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
|
||||
dev: true
|
||||
@@ -4715,29 +4709,23 @@ packages:
|
||||
/@types/react-dom@18.0.10:
|
||||
resolution: {integrity: sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==}
|
||||
dependencies:
|
||||
'@types/react': 18.0.32
|
||||
'@types/react': 18.0.33
|
||||
dev: true
|
||||
|
||||
/@types/react-dom@18.0.11:
|
||||
resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==}
|
||||
dependencies:
|
||||
'@types/react': 18.0.32
|
||||
'@types/react': 18.0.33
|
||||
|
||||
/@types/react-redux@7.1.25:
|
||||
resolution: {integrity: sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==}
|
||||
dependencies:
|
||||
'@types/hoist-non-react-statics': 3.3.1
|
||||
'@types/react': 18.0.32
|
||||
'@types/react': 18.0.33
|
||||
hoist-non-react-statics: 3.3.2
|
||||
redux: 4.2.1
|
||||
dev: false
|
||||
|
||||
/@types/react-responsive-embed@2.1.0:
|
||||
resolution: {integrity: sha512-VZ921rT7Kf5Zq2GyUxaFXWXQd/YqSMwpwOO5BRJJyzcZoH34YbeX442Dk/nDwXL5QQkz1Zp7hmD80Ris1OHPCA==}
|
||||
dependencies:
|
||||
'@types/react': 18.0.32
|
||||
dev: true
|
||||
|
||||
/@types/react@18.0.27:
|
||||
resolution: {integrity: sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==}
|
||||
dependencies:
|
||||
@@ -4746,13 +4734,6 @@ packages:
|
||||
csstype: 3.1.1
|
||||
dev: true
|
||||
|
||||
/@types/react@18.0.28:
|
||||
resolution: {integrity: sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==}
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.5
|
||||
'@types/scheduler': 0.16.2
|
||||
csstype: 3.1.1
|
||||
|
||||
/@types/react@18.0.29:
|
||||
resolution: {integrity: sha512-wXHktgUABxplw1+UnljseDq4+uztQyp2tlWZRIxHlpchsCFqiYkvaDS8JR7eKOQm8wziTH/el5qL7D6gYNkYcw==}
|
||||
dependencies:
|
||||
@@ -4768,8 +4749,8 @@ packages:
|
||||
'@types/scheduler': 0.16.2
|
||||
csstype: 3.1.1
|
||||
|
||||
/@types/react@18.0.32:
|
||||
resolution: {integrity: sha512-gYGXdtPQ9Cj0w2Fwqg5/ak6BcK3Z15YgjSqtyDizWUfx7mQ8drs0NBUzRRsAdoFVTO8kJ8L2TL8Skm7OFPnLUw==}
|
||||
/@types/react@18.0.33:
|
||||
resolution: {integrity: sha512-sHxzVxeanvQyQ1lr8NSHaj0kDzcNiGpILEVt69g9S31/7PfMvNCKLKcsHw4lYKjs3cGNJjXSP4mYzX43QlnjNA==}
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.5
|
||||
'@types/scheduler': 0.16.2
|
||||
@@ -6662,10 +6643,6 @@ packages:
|
||||
lodash.uniq: 4.5.0
|
||||
dev: true
|
||||
|
||||
/caniuse-lite@1.0.30001435:
|
||||
resolution: {integrity: sha512-kdCkUTjR+v4YAJelyiDTqiu82BDr4W4CP5sgTA0ZBmqn30XfS2ZghPLMowik9TPhS+psWJiUNxsqLyurDbmutA==}
|
||||
dev: false
|
||||
|
||||
/caniuse-lite@1.0.30001466:
|
||||
resolution: {integrity: sha512-ewtFBSfWjEmxUgNBSZItFSmVtvk9zkwkl1OfRZlKA8slltRN+/C/tuGVrF9styXkN36Yu3+SeJ1qkXxDEyNZ5w==}
|
||||
|
||||
@@ -9271,6 +9248,7 @@ packages:
|
||||
/eslint-visitor-keys@3.3.0:
|
||||
resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dev: false
|
||||
|
||||
/eslint-visitor-keys@3.4.0:
|
||||
resolution: {integrity: sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==}
|
||||
@@ -9390,9 +9368,9 @@ packages:
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 7.1.1
|
||||
eslint-utils: 3.0.0(eslint@8.33.0)
|
||||
eslint-visitor-keys: 3.3.0
|
||||
espree: 9.4.1
|
||||
esquery: 1.4.0
|
||||
eslint-visitor-keys: 3.4.0
|
||||
espree: 9.5.1
|
||||
esquery: 1.5.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
file-entry-cache: 6.0.1
|
||||
@@ -9428,7 +9406,7 @@ packages:
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.2.0(eslint@8.36.0)
|
||||
'@eslint-community/regexpp': 4.4.0
|
||||
'@eslint/eslintrc': 2.0.1
|
||||
'@eslint/eslintrc': 2.0.2
|
||||
'@eslint/js': 8.36.0
|
||||
'@humanwhocodes/config-array': 0.11.8
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
@@ -9440,8 +9418,8 @@ packages:
|
||||
doctrine: 3.0.0
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 7.1.1
|
||||
eslint-visitor-keys: 3.3.0
|
||||
espree: 9.5.0
|
||||
eslint-visitor-keys: 3.4.0
|
||||
espree: 9.5.1
|
||||
esquery: 1.5.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
@@ -9537,15 +9515,8 @@ packages:
|
||||
dependencies:
|
||||
acorn: 8.8.1
|
||||
acorn-jsx: 5.3.2(acorn@8.8.1)
|
||||
eslint-visitor-keys: 3.3.0
|
||||
|
||||
/espree@9.5.0:
|
||||
resolution: {integrity: sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
acorn: 8.8.1
|
||||
acorn-jsx: 5.3.2(acorn@8.8.1)
|
||||
eslint-visitor-keys: 3.3.0
|
||||
eslint-visitor-keys: 3.4.0
|
||||
dev: false
|
||||
|
||||
/espree@9.5.1:
|
||||
resolution: {integrity: sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==}
|
||||
@@ -9565,6 +9536,7 @@ packages:
|
||||
engines: {node: '>=0.10'}
|
||||
dependencies:
|
||||
estraverse: 5.3.0
|
||||
dev: false
|
||||
|
||||
/esquery@1.5.0:
|
||||
resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
|
||||
@@ -13996,8 +13968,8 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/next-sitemap@4.0.5(@next/env@13.2.4)(next@13.2.4):
|
||||
resolution: {integrity: sha512-kIABX4n8wJqY2sttRjivBF2r1MgQ1pg12y87qGT+5meippq0ivxaNSRldMDOj8pIGhXW6uFAsCxyH/JftqkwWQ==}
|
||||
/next-sitemap@4.0.6(@next/env@13.2.4)(next@13.2.4):
|
||||
resolution: {integrity: sha512-pZ9tynYe6mRR189qZqcOlWVgM1Gxo07BJQW0AjerKmLwQOt+6FQMdaDgifgCt6jDT3Y3EG/+NUDDZRcd0gbPkA==}
|
||||
engines: {node: '>=14.18'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -14030,7 +14002,7 @@ packages:
|
||||
dependencies:
|
||||
'@next/env': 13.1.6
|
||||
'@swc/helpers': 0.4.14
|
||||
caniuse-lite: 1.0.30001435
|
||||
caniuse-lite: 1.0.30001466
|
||||
postcss: 8.4.14
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
@@ -14114,8 +14086,8 @@ packages:
|
||||
semver: 7.3.8
|
||||
dev: false
|
||||
|
||||
/node-addon-api@5.1.0:
|
||||
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
|
||||
/node-addon-api@6.0.0:
|
||||
resolution: {integrity: sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA==}
|
||||
dev: false
|
||||
|
||||
/node-fetch@2.6.7:
|
||||
@@ -16199,8 +16171,8 @@ packages:
|
||||
scheduler: 0.23.0
|
||||
dev: false
|
||||
|
||||
/react-hook-form@7.43.5(react@18.2.0):
|
||||
resolution: {integrity: sha512-YcaXhuFHoOPipu5pC7ckxrLrialiOcU91pKu8P+isAcXZyMgByUK9PkI9j5fENO4+6XU5PwWXRGMIFlk9u9UBQ==}
|
||||
/react-hook-form@7.43.8(react@18.2.0):
|
||||
resolution: {integrity: sha512-BQm+Ge5KjTk1EchDBRhdP8Pkb7MArO2jFF+UWYr3rtvh6197khi22uloLqlWeuY02ItlCzPunPsFt1/q9wQKnw==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18
|
||||
@@ -16208,8 +16180,8 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/react-hook-form@7.43.8(react@18.2.0):
|
||||
resolution: {integrity: sha512-BQm+Ge5KjTk1EchDBRhdP8Pkb7MArO2jFF+UWYr3rtvh6197khi22uloLqlWeuY02ItlCzPunPsFt1/q9wQKnw==}
|
||||
/react-hook-form@7.43.9(react@18.2.0):
|
||||
resolution: {integrity: sha512-AUDN3Pz2NSeoxQ7Hs6OhQhDr6gtF9YRuutGDwPQqhSUAHJSgGl2VeY3qN19MG0SucpjgDiuMJ4iC5T5uB+eaNQ==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18
|
||||
@@ -17232,14 +17204,14 @@ packages:
|
||||
kind-of: 6.0.3
|
||||
dev: true
|
||||
|
||||
/sharp@0.31.3:
|
||||
resolution: {integrity: sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==}
|
||||
/sharp@0.32.0:
|
||||
resolution: {integrity: sha512-yLAypVcqj1toSAqRSwbs86nEzfyZVDYqjuUX8grhFpeij0DDNagKJXELS/auegDBRDg1XBtELdOGfo2X1cCpeA==}
|
||||
engines: {node: '>=14.15.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
color: 4.2.3
|
||||
detect-libc: 2.0.1
|
||||
node-addon-api: 5.1.0
|
||||
node-addon-api: 6.0.0
|
||||
prebuild-install: 7.1.1
|
||||
semver: 7.3.8
|
||||
simple-get: 4.0.1
|
||||
@@ -18134,6 +18106,41 @@ packages:
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/tailwindcss@3.3.1(postcss@8.4.21):
|
||||
resolution: {integrity: sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
postcss: ^8.0.9
|
||||
dependencies:
|
||||
arg: 5.0.2
|
||||
chokidar: 3.5.3
|
||||
color-name: 1.1.4
|
||||
didyoumean: 1.2.2
|
||||
dlv: 1.1.3
|
||||
fast-glob: 3.2.12
|
||||
glob-parent: 6.0.2
|
||||
is-glob: 4.0.3
|
||||
jiti: 1.18.2
|
||||
lilconfig: 2.0.6
|
||||
micromatch: 4.0.5
|
||||
normalize-path: 3.0.0
|
||||
object-hash: 3.0.0
|
||||
picocolors: 1.0.0
|
||||
postcss: 8.4.21
|
||||
postcss-import: 14.1.0(postcss@8.4.21)
|
||||
postcss-js: 4.0.0(postcss@8.4.21)
|
||||
postcss-load-config: 3.1.4(postcss@8.4.21)
|
||||
postcss-nested: 6.0.0(postcss@8.4.21)
|
||||
postcss-selector-parser: 6.0.11
|
||||
postcss-value-parser: 4.2.0
|
||||
quick-lru: 5.1.1
|
||||
resolve: 1.22.1
|
||||
sucrase: 3.29.0
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/tapable@1.1.3:
|
||||
resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -18888,6 +18895,12 @@ packages:
|
||||
engines: {node: '>=12.20'}
|
||||
hasBin: true
|
||||
|
||||
/typescript@5.0.3:
|
||||
resolution: {integrity: sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==}
|
||||
engines: {node: '>=12.20'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/uglify-js@3.4.10:
|
||||
resolution: {integrity: sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
Reference in New Issue
Block a user