mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-06 05:40:02 -06:00
Merge branch 'main' of github.com:formbricks/formbricks into more-formbricks-in-formbricks
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
NEXT_PUBLIC_FORMBRICKS_COM_API_HOST=http://localhost:3000
|
||||
NEXT_PUBLIC_FORMBRICKS_COM_ENVIRONMENT_ID=
|
||||
NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID=
|
||||
NEXT_PUBLIC_FORMBRICKS_COM_DOCS_FEEDBACK_SURVEY_ID=
|
||||
|
||||
# Strapi API Key
|
||||
STRAPI_API_KEY=
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect } from "react";
|
||||
import Footer from "./Footer";
|
||||
import Header from "./Header";
|
||||
import MetaInformation from "./MetaInformation";
|
||||
import { Prose } from "./Prose";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const useExternalLinks = (selector: string) => {
|
||||
useEffect(() => {
|
||||
|
||||
@@ -4,6 +4,7 @@ interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
publishedTime?: string;
|
||||
updatedTime?: string;
|
||||
authors?: string[];
|
||||
section?: string;
|
||||
tags?: string[];
|
||||
@@ -13,6 +14,7 @@ export default function MetaInformation({
|
||||
title,
|
||||
description,
|
||||
publishedTime,
|
||||
updatedTime,
|
||||
authors,
|
||||
section,
|
||||
tags,
|
||||
@@ -31,9 +33,10 @@ export default function MetaInformation({
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="Open Source Experience Management, Privacy-first" />
|
||||
<meta property="article:publisher" content="Formbricks" />
|
||||
<meta property="og:site_name" content="Formbricks Privacy-first Experience Management Solution" />
|
||||
<meta property="article:publisher" content="Formbricks GmbH" />
|
||||
{publishedTime && <meta property="article:published_time" content={publishedTime} />}
|
||||
{updatedTime && <meta property="article:updated_time" content={updatedTime} />}
|
||||
{authors && <meta property="article:author" content={authors.join(", ")} />}
|
||||
{section && <meta property="article:section" content={section} />}
|
||||
{tags && <meta property="article:tag" content={tags.join(", ")} />}
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
import rehypePrism from "@mapbox/rehype-prism";
|
||||
import nextMDX from "@next/mdx";
|
||||
import { withPlausibleProxy } from "next-plausible";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypePrism from "@mapbox/rehype-prism";
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],
|
||||
transpilePackages: ["@formbricks/ui", "@formbricks/lib"],
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "seo-strapi-aws-s3.s3.eu-central-1.amazonaws.com",
|
||||
port: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -27,12 +27,15 @@
|
||||
"lottie-web": "^5.12.2",
|
||||
"next": "13.4.12",
|
||||
"next-plausible": "^3.10.1",
|
||||
"next-seo": "^6.1.0",
|
||||
"next-sitemap": "^4.1.8",
|
||||
"node-fetch": "^3.3.2",
|
||||
"prism-react-renderer": "^2.0.6",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.10.1",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-responsive-embed": "^2.1.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.32.4"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import Image from "next/image";
|
||||
import LayoutMdx from "@/components/shared/LayoutMdx";
|
||||
import AuthorBox from "@/components/shared/AuthorBox";
|
||||
import Formbricks from "./open-source-survey-software-free-2023-formbricks-typeform-alternative.png";
|
||||
import Typebot from "./typebot-open-source-free-conversational-form-builder-survey-software-opensource.jpg";
|
||||
import LimeSurvey from "./free-survey-tool-limesurvey-open-source-software-opensource.png";
|
||||
import OpnForm from "./opnform-free-open-source-form-survey-tools-builder-2023-self-hostign.jpg";
|
||||
import HeaderImage from "./2023-title-best-open-source-survey-software-tools-and-alternatives.png";
|
||||
import SurveyJS from "./surveyjs-free-opensource-form-survey-tool-software-to-make-surveys-2023.png";
|
||||
import AuthorBox from "@/components/shared/AuthorBox";
|
||||
|
||||
export const meta = {
|
||||
title: "5 Open Source Survey and Form Tools maintained in 2023",
|
||||
|
||||
141
apps/formbricks-com/pages/learn/[slug].tsx
Normal file
141
apps/formbricks-com/pages/learn/[slug].tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import LayoutMdx from "@/components/shared/LayoutMdx";
|
||||
import { FAQPageJsonLd } from "next-seo";
|
||||
import Image from "next/image";
|
||||
import fetch from "node-fetch";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
type Article = {
|
||||
id?: number;
|
||||
attributes?: {
|
||||
author?: string;
|
||||
title?: string;
|
||||
text?: string;
|
||||
slug?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
publishedAt?: string;
|
||||
meta?: {
|
||||
id?: number;
|
||||
description?: string;
|
||||
title?: string;
|
||||
publisher?: string;
|
||||
section?: string;
|
||||
tags?: {
|
||||
id?: number;
|
||||
tag?: string;
|
||||
}[];
|
||||
};
|
||||
faq?: {
|
||||
id?: number;
|
||||
question?: string;
|
||||
answer?: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
type ArticlePageProps = {
|
||||
article?: Article;
|
||||
};
|
||||
|
||||
interface ArticleResponse {
|
||||
data: Article[];
|
||||
meta: {
|
||||
pagination: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
pageCount: number;
|
||||
total: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const response = await fetch(
|
||||
"https://strapi.formbricks.com/api/articles?populate[meta][populate]=*&filters[category][name][$eq]=learn",
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.STRAPI_API_KEY}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const articles = (await response.json()) as ArticleResponse;
|
||||
|
||||
const paths = articles.data.map((article) => ({
|
||||
params: { slug: article.attributes.slug },
|
||||
}));
|
||||
|
||||
return { paths, fallback: true };
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params }) {
|
||||
const res = await fetch(
|
||||
`https://strapi.formbricks.com/api/articles?populate[meta][populate]=*&populate[faq][populate]=*&filters[slug][$eq]=${params.slug}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.STRAPI_API_KEY}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error("Something went wrong");
|
||||
}
|
||||
const resData = (await res.json()) as ArticleResponse;
|
||||
const article = resData.data[0];
|
||||
return { props: { article } };
|
||||
}
|
||||
|
||||
export default function ArticlePage({ article = {} }: ArticlePageProps) {
|
||||
if (!article || !article.attributes) return <div>Loading...</div>;
|
||||
|
||||
// Use next/image to render images in markdown
|
||||
const renderers = {
|
||||
img: (image) => {
|
||||
return <Image src={image.src} alt={image.alt} width={1000} height={500} />;
|
||||
},
|
||||
};
|
||||
|
||||
const {
|
||||
attributes: {
|
||||
author,
|
||||
publishedAt,
|
||||
text,
|
||||
faq,
|
||||
meta: {
|
||||
title,
|
||||
description,
|
||||
section,
|
||||
tags = [], // default empty array if tags are not provided
|
||||
} = {}, // default empty object if meta is not provided
|
||||
} = {}, // default empty object if attributes are not provided
|
||||
} = article;
|
||||
|
||||
const metaTags = tags.map((tag) => tag.tag);
|
||||
|
||||
const meta = {
|
||||
title,
|
||||
description,
|
||||
publishedTime: publishedAt,
|
||||
authors: [author],
|
||||
section,
|
||||
tags: metaTags,
|
||||
};
|
||||
|
||||
// Convert the FAQ details into the desired format for FAQPageJsonLd
|
||||
const faqEntities = faq.map(({ question, answer }) => ({
|
||||
questionName: question,
|
||||
acceptedAnswerText: answer,
|
||||
}));
|
||||
|
||||
return (
|
||||
<LayoutMdx meta={meta}>
|
||||
<>
|
||||
<ReactMarkdown components={renderers}>{text}</ReactMarkdown>
|
||||
<FAQPageJsonLd mainEntity={faqEntities} />
|
||||
</>
|
||||
</LayoutMdx>
|
||||
);
|
||||
}
|
||||
@@ -42,6 +42,7 @@ export default function ActionSettingsTab({ environmentId, actionClass, setOpen
|
||||
},
|
||||
});
|
||||
const [isUpdatingAction, setIsUpdatingAction] = useState(false);
|
||||
const [isDeletingAction, setIsDeletingAction] = useState(false);
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const filteredNoCodeConfig = filterNoCodeConfig(data.noCodeConfig as NoCodeConfig);
|
||||
@@ -81,6 +82,20 @@ export default function ActionSettingsTab({ environmentId, actionClass, setOpen
|
||||
if (match === "no") toast.error("Your survey would not be shown.");
|
||||
};
|
||||
|
||||
const handleDeleteAction = async () => {
|
||||
try {
|
||||
setIsDeletingAction(true);
|
||||
await deleteActionClass(environmentId, actionClass.id);
|
||||
router.refresh();
|
||||
toast.success("Action deleted successfully");
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
toast.error("Something went wrong. Please try again.");
|
||||
} finally {
|
||||
setIsDeletingAction(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form className="space-y-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
@@ -288,18 +303,10 @@ export default function ActionSettingsTab({ environmentId, actionClass, setOpen
|
||||
<DeleteDialog
|
||||
open={openDeleteDialog}
|
||||
setOpen={setOpenDeleteDialog}
|
||||
isDeleting={isDeletingAction}
|
||||
deleteWhat={"Action"}
|
||||
text="Are you sure you want to delete this action? This also removes this action as a trigger from all your surveys."
|
||||
onDelete={async () => {
|
||||
setOpen(false);
|
||||
try {
|
||||
await deleteActionClass(environmentId, actionClass.id);
|
||||
router.refresh();
|
||||
toast.success("Action deleted successfully");
|
||||
} catch (error) {
|
||||
toast.error("Something went wrong. Please try again.");
|
||||
}
|
||||
}}
|
||||
onDelete={handleDeleteAction}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,10 +19,19 @@ export default function HeadingSection({
|
||||
const router = useRouter();
|
||||
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [isDeletingPerson, setIsDeletingPerson] = useState(false);
|
||||
|
||||
const handleDeletePerson = async () => {
|
||||
await deletePersonAction(person.id);
|
||||
router.push(`/environments/${environmentId}/people`);
|
||||
toast.success("Person deleted successfully.");
|
||||
try {
|
||||
setIsDeletingPerson(true);
|
||||
await deletePersonAction(person.id);
|
||||
router.push(`/environments/${environmentId}/people`);
|
||||
toast.success("Person deleted successfully.");
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
} finally {
|
||||
setIsDeletingPerson(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -46,6 +55,7 @@ export default function HeadingSection({
|
||||
setOpen={setDeleteDialogOpen}
|
||||
deleteWhat="person"
|
||||
onDelete={handleDeletePerson}
|
||||
isDeleting={isDeletingPerson}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import toast from "react-hot-toast";
|
||||
import DeleteDialog from "@/components/shared/DeleteDialog";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useState, Dispatch, SetStateAction } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMembers } from "@/lib/members";
|
||||
import { useProfile } from "@/lib/profile";
|
||||
import { Button, ErrorComponent, Input } from "@formbricks/ui";
|
||||
import { useTeam, deleteTeam } from "@/lib/teams/teams";
|
||||
import { useMemberships } from "@/lib/memberships";
|
||||
import { useProfile } from "@/lib/profile";
|
||||
import { deleteTeam, useTeam } from "@/lib/teams/teams";
|
||||
import { Button, ErrorComponent, Input } from "@formbricks/ui";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
export default function DeleteTeam({ environmentId }) {
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import DeleteDialog from "@/components/shared/DeleteDialog";
|
||||
import AvatarPlaceholder from "@/images/avatar-placeholder.png";
|
||||
import { formbricksLogout } from "@/lib/formbricks";
|
||||
import { TProfile } from "@formbricks/types/v1/profile";
|
||||
import { Button, Input, ProfileAvatar } from "@formbricks/ui";
|
||||
import { Session } from "next-auth";
|
||||
import { signOut } from "next-auth/react";
|
||||
@@ -10,7 +11,6 @@ import Image from "next/image";
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { profileDeleteAction } from "./actions";
|
||||
import { TProfile } from "@formbricks/types/v1/profile";
|
||||
|
||||
export function EditAvatar({ session }) {
|
||||
return (
|
||||
|
||||
@@ -451,7 +451,7 @@ export default function PreviewSurvey({
|
||||
</Modal>
|
||||
) : (
|
||||
<div className="flex flex-grow flex-col overflow-y-auto" ref={ContentRef}>
|
||||
<div className="flex w-full flex-grow flex-col items-center justify-center bg-white py-6">
|
||||
<div className="flex w-full flex-grow p-4 flex-col items-center justify-center bg-white py-6">
|
||||
<div className="w-full max-w-md">
|
||||
<QuestionRenderer
|
||||
activeQuestionId={activeQuestionId}
|
||||
|
||||
@@ -207,9 +207,6 @@ export default function LogicEditor({
|
||||
updateQuestion(questionIdx, { logic: updatedLogic });
|
||||
};
|
||||
|
||||
const truncate = (str: string, n: number) =>
|
||||
str && str.length > n ? str.substring(0, n - 1) + "..." : str;
|
||||
|
||||
if (!(question.type in conditions)) {
|
||||
return <></>;
|
||||
}
|
||||
@@ -221,19 +218,23 @@ export default function LogicEditor({
|
||||
{question?.logic && question?.logic?.length !== 0 && (
|
||||
<div className="mt-2 space-y-3">
|
||||
{question?.logic?.map((logic, logicIdx) => (
|
||||
<div key={logicIdx} className="flex items-center space-x-2 space-y-1 text-sm">
|
||||
<div key={logicIdx} className="flex items-center space-x-2 space-y-1 text-xs xl:text-sm">
|
||||
<BsArrowReturnRight className="h-4 w-4" />
|
||||
<p className="text-slate-700">If this answer</p>
|
||||
<p className="text-slate-800">If this answer</p>
|
||||
|
||||
<Select value={logic.condition} onValueChange={(e) => updateLogic(logicIdx, { condition: e })}>
|
||||
<SelectTrigger className="min-w-fit flex-1">
|
||||
<SelectValue placeholder="Select condition" />
|
||||
<SelectTrigger className=" min-w-fit flex-1">
|
||||
<SelectValue placeholder="Select condition" className="text-xs lg:text-sm" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{conditions[question.type].map(
|
||||
(condition) =>
|
||||
!(question.required && condition === "skipped") && (
|
||||
<SelectItem key={condition} value={condition}>
|
||||
<SelectItem
|
||||
key={condition}
|
||||
value={condition}
|
||||
title={logicConditions[condition].label}
|
||||
className="text-xs lg:text-sm">
|
||||
{logicConditions[condition].label}
|
||||
</SelectItem>
|
||||
)
|
||||
@@ -251,7 +252,7 @@ export default function LogicEditor({
|
||||
<SelectContent>
|
||||
{logicConditions[logic.condition].values?.map((value) => (
|
||||
<SelectItem key={value} value={value} title={value}>
|
||||
{truncate(value, 20)}
|
||||
{value}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -259,11 +260,15 @@ export default function LogicEditor({
|
||||
) : (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="z-10 cursor-pointer" asChild>
|
||||
<div className="flex h-10 w-full items-center justify-between rounded-md border border-slate-300 bg-transparent px-3 py-2 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 ">
|
||||
<div className="flex h-10 w-40 items-center justify-between rounded-md border border-slate-300 bg-transparent px-3 py-2 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 ">
|
||||
{logic.value?.length === 0 ? (
|
||||
<p className="text-slate-400">Select match type</p>
|
||||
<p className="truncate text-slate-400" title="Select match type">
|
||||
Select match type
|
||||
</p>
|
||||
) : (
|
||||
<p>{logic.value.join(", ")}</p>
|
||||
<p className="truncate" title={logic.value.join(", ")}>
|
||||
{logic.value.join(", ")}
|
||||
</p>
|
||||
)}
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</div>
|
||||
@@ -275,6 +280,7 @@ export default function LogicEditor({
|
||||
{logicConditions[logic.condition].values?.map((value) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={value}
|
||||
title={value}
|
||||
checked={logic.value?.includes(value)}
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
onCheckedChange={(e) => updateMultiSelectLogic(logicIdx, e, value)}>
|
||||
@@ -287,7 +293,7 @@ export default function LogicEditor({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-slate-700">skip to</p>
|
||||
<p className="text-slate-800">jump to</p>
|
||||
|
||||
<Select
|
||||
value={logic.destination}
|
||||
@@ -300,7 +306,11 @@ export default function LogicEditor({
|
||||
(question, idx) =>
|
||||
idx !== questionIdx && (
|
||||
<SelectItem key={question.id} value={question.id} title={question.headline}>
|
||||
{idx + 1} - {truncate(question.headline, 14)}
|
||||
<div className="w-40">
|
||||
<p className="truncate">
|
||||
{idx + 1} - {question.headline}
|
||||
</p>
|
||||
</div>
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -169,7 +169,7 @@ export default function SurveyMenuBar({
|
||||
}}>
|
||||
Back
|
||||
</Button>
|
||||
<p className="pl-4 font-semibold hidden md:block">{product.name} / </p>
|
||||
<p className="hidden pl-4 font-semibold md:block">{product.name} / </p>
|
||||
<Input
|
||||
defaultValue={localSurvey.name}
|
||||
onChange={(e) => {
|
||||
@@ -180,9 +180,11 @@ export default function SurveyMenuBar({
|
||||
/>
|
||||
</div>
|
||||
{!!localSurvey?.responseRate && (
|
||||
<div className="mx-auto flex items-center rounded-full border border-amber-200 bg-amber-100 p-2 text-sm text-amber-700 shadow-sm">
|
||||
<ExclamationTriangleIcon className="mr-2 h-5 w-5 text-amber-400" />
|
||||
This survey received responses. To keep the data consistent, make changes with caution.
|
||||
<div className="mx-auto flex items-center rounded-full border border-amber-200 bg-amber-100 p-2 text-amber-700 shadow-sm">
|
||||
<ExclamationTriangleIcon className=" h-5 w-5 text-amber-400" />
|
||||
<p className="max-w-[90%] pl-1 text-xs lg:text-sm">
|
||||
This survey received responses. To keep the data consistent, make changes with caution.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-3 flex sm:ml-4 sm:mt-0">
|
||||
|
||||
@@ -5,24 +5,21 @@ import Progress from "@/components/preview/Progress";
|
||||
import QuestionConditional from "@/components/preview/QuestionConditional";
|
||||
import ThankYouCard from "@/components/preview/ThankYouCard";
|
||||
import ContentWrapper from "@/components/shared/ContentWrapper";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useLinkSurveyUtils } from "@/lib/linkSurvey/linkSurvey";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { Confetti } from "@formbricks/ui";
|
||||
import { ArrowPathIcon } from "@heroicons/react/24/solid";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
type EnhancedSurvey = Survey & {
|
||||
brandColor: string;
|
||||
formbricksSignature: boolean;
|
||||
};
|
||||
import { TSurvey } from "@formbricks/types/v1/surveys";
|
||||
import Loading from "@/app/s/[surveyId]/loading";
|
||||
import { TProduct } from "@formbricks/types/v1/product";
|
||||
|
||||
interface LinkSurveyProps {
|
||||
survey: EnhancedSurvey;
|
||||
survey: TSurvey;
|
||||
product: TProduct;
|
||||
}
|
||||
|
||||
export default function LinkSurvey({ survey }: LinkSurveyProps) {
|
||||
export default function LinkSurvey({ survey, product }: LinkSurveyProps) {
|
||||
const {
|
||||
currentQuestion,
|
||||
finished,
|
||||
@@ -61,7 +58,7 @@ export default function LinkSurvey({ survey }: LinkSurveyProps) {
|
||||
if (!currentQuestion || prefilling) {
|
||||
return (
|
||||
<div className="flex h-full flex-1 items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -88,18 +85,18 @@ export default function LinkSurvey({ survey }: LinkSurveyProps) {
|
||||
)}
|
||||
{finished ? (
|
||||
<div>
|
||||
<Confetti colors={[survey.brandColor, "#eee"]} />
|
||||
<Confetti colors={[product.brandColor, "#eee"]} />
|
||||
<ThankYouCard
|
||||
headline={survey.thankYouCard.headline || "Thank you!"}
|
||||
subheader={survey.thankYouCard.subheader || "Your response has been recorded."}
|
||||
brandColor={survey.brandColor}
|
||||
brandColor={product.brandColor}
|
||||
initiateCountdown={initiateCountdown}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<QuestionConditional
|
||||
question={currentQuestion}
|
||||
brandColor={survey.brandColor}
|
||||
brandColor={product.brandColor}
|
||||
lastQuestion={lastQuestion}
|
||||
onSubmit={submitResponse}
|
||||
storedResponseValue={storedResponseValue}
|
||||
@@ -112,8 +109,8 @@ export default function LinkSurvey({ survey }: LinkSurveyProps) {
|
||||
</div>
|
||||
<div className="top-0 z-10 w-full border-b bg-white">
|
||||
<div className="mx-auto max-w-md space-y-6 p-6">
|
||||
<Progress progress={progress} brandColor={survey.brandColor} />
|
||||
{survey.formbricksSignature && <FormbricksSignature />}
|
||||
<Progress progress={progress} brandColor={product.brandColor} />
|
||||
{product.formbricksSignature && <FormbricksSignature />}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -10,7 +10,7 @@ const SurveyInactive = ({
|
||||
surveyClosedMessage,
|
||||
}: {
|
||||
status: string;
|
||||
surveyClosedMessage?: { heading: string; subheading: string };
|
||||
surveyClosedMessage?: { heading?: string | undefined; subheading?: string | undefined };
|
||||
}) => {
|
||||
const icons = {
|
||||
"not found": <QuestionMarkCircleIcon className="h-20 w-20" />,
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import LinkSurvey from "@/app/s/[surveyId]/LinkSurvey";
|
||||
import SurveyInactive from "@/app/s/[surveyId]/SurveyInactive";
|
||||
import LegalFooter from "@/components/shared/LegalFooter";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useLinkSurvey } from "@/lib/linkSurvey/linkSurvey";
|
||||
|
||||
interface SurveyPageProps {
|
||||
surveyId: string;
|
||||
}
|
||||
|
||||
export default function SurveyPage({ surveyId }: SurveyPageProps) {
|
||||
const { survey, isLoadingSurvey, isErrorSurvey } = useLinkSurvey(surveyId);
|
||||
|
||||
if (isLoadingSurvey) {
|
||||
return (
|
||||
<div className="flex h-full flex-1 items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorSurvey && isErrorSurvey.status === 404) {
|
||||
return <SurveyInactive status="not found" />;
|
||||
}
|
||||
|
||||
if (isErrorSurvey && isErrorSurvey.status === 403) {
|
||||
return (
|
||||
<SurveyInactive
|
||||
status={isErrorSurvey.info.reason}
|
||||
surveyClosedMessage={isErrorSurvey.info?.surveyClosedMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<LinkSurvey survey={survey} />
|
||||
<LegalFooter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
10
apps/web/app/s/[surveyId]/loading.tsx
Normal file
10
apps/web/app/s/[surveyId]/loading.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="flex h-1/2 w-1/4 flex-col">
|
||||
<div className="ph-no-capture h-16 w-1/3 animate-pulse rounded-lg bg-gray-200 font-medium text-slate-900"></div>
|
||||
<div className="ph-no-capture mt-4 h-full animate-pulse rounded-lg bg-gray-200 text-slate-900"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,32 @@
|
||||
import SurveyPage from "./SurveyPage";
|
||||
export const revalidate = REVALIDATION_INTERVAL;
|
||||
|
||||
export default function LinkSurveyPage({ params }) {
|
||||
return <SurveyPage surveyId={params.surveyId} />;
|
||||
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
|
||||
import LinkSurvey from "@/app/s/[surveyId]/LinkSurvey";
|
||||
import LegalFooter from "@/app/s/[surveyId]/LegalFooter";
|
||||
import { getSurvey } from "@formbricks/lib/services/survey";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
|
||||
import SurveyInactive from "@/app/s/[surveyId]/SurveyInactive";
|
||||
|
||||
export default async function LinkSurveyPage({ params }) {
|
||||
|
||||
const [survey, product] = await Promise.all([getSurvey(params.surveyId), getProductByEnvironmentId(params.environmentId)]);
|
||||
|
||||
if (survey && survey.status !== "inProgress") {
|
||||
return <SurveyInactive status={survey.status} surveyClosedMessage={survey.surveyClosedMessage} />;
|
||||
}
|
||||
|
||||
if (survey === null) {
|
||||
return <SurveyInactive status="not found" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{survey && (
|
||||
<>
|
||||
<LinkSurvey survey={survey} product={product} />
|
||||
<LegalFooter />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@ import { cn } from "@/../../packages/lib/cn";
|
||||
import { isLight } from "@/lib/utils";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { BackButton } from "@/components/preview/BackButton";
|
||||
import { TSurveyCTAQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface CTAQuestionProps {
|
||||
question: CTAQuestion;
|
||||
question: CTAQuestion | TSurveyCTAQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -6,9 +6,10 @@ import type { ConsentQuestion } from "@formbricks/types/questions";
|
||||
import { useEffect, useState } from "react";
|
||||
import Headline from "./Headline";
|
||||
import HtmlBody from "./HtmlBody";
|
||||
import { TSurveyConsentQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface ConsentQuestionProps {
|
||||
question: ConsentQuestion;
|
||||
question: ConsentQuestion | TSurveyConsentQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -4,14 +4,15 @@ import { shuffleArray } from "@/lib/utils";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { symmetricDifference } from "@formbricks/lib/utils/array";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import type { Choice, MultipleChoiceMultiQuestion } from "@formbricks/types/questions";
|
||||
import type { MultipleChoiceMultiQuestion } from "@formbricks/types/questions";
|
||||
import { TSurveyChoice, TSurveyMultipleChoiceMultiQuestion } from "@formbricks/types/v1/surveys";
|
||||
import { Input } from "@formbricks/ui";
|
||||
import { useEffect, useState } from "react";
|
||||
import Headline from "./Headline";
|
||||
import Subheader from "./Subheader";
|
||||
|
||||
interface MultipleChoiceMultiProps {
|
||||
question: MultipleChoiceMultiQuestion;
|
||||
question: MultipleChoiceMultiQuestion | TSurveyMultipleChoiceMultiQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
@@ -57,7 +58,7 @@ export default function MultipleChoiceMultiQuestion({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [storedResponseValue, question.id]);
|
||||
|
||||
const [questionChoices, setQuestionChoices] = useState<Choice[]>(
|
||||
const [questionChoices, setQuestionChoices] = useState<TSurveyChoice[]>(
|
||||
question.choices
|
||||
? question.shuffleOption !== "none"
|
||||
? shuffleArray(question.choices, question.shuffleOption)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { shuffleArray } from "@/lib/utils";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { MultipleChoiceSingleQuestion } from "@formbricks/types/questions";
|
||||
import { TSurveyChoice } from "@formbricks/types/v1/surveys";
|
||||
import { TSurveyChoice, TSurveyMultipleChoiceSingleQuestion } from "@formbricks/types/v1/surveys";
|
||||
import { Input } from "@formbricks/ui";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Headline from "./Headline";
|
||||
@@ -11,7 +11,7 @@ import Subheader from "./Subheader";
|
||||
import { BackButton } from "@/components/preview/BackButton";
|
||||
|
||||
interface MultipleChoiceSingleProps {
|
||||
question: MultipleChoiceSingleQuestion;
|
||||
question: MultipleChoiceSingleQuestion | TSurveyMultipleChoiceSingleQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -6,9 +6,10 @@ import Subheader from "./Subheader";
|
||||
import SubmitButton from "@/components/preview/SubmitButton";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { BackButton } from "@/components/preview/BackButton";
|
||||
import { TSurveyNPSQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface NPSQuestionProps {
|
||||
question: NPSQuestion;
|
||||
question: NPSQuestion | TSurveyNPSQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -5,9 +5,10 @@ import Subheader from "./Subheader";
|
||||
import SubmitButton from "@/components/preview/SubmitButton";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { BackButton } from "@/components/preview/BackButton";
|
||||
import { TSurveyOpenTextQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface OpenTextQuestionProps {
|
||||
question: OpenTextQuestion;
|
||||
question: TSurveyOpenTextQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -6,9 +6,10 @@ import NPSQuestion from "./NPSQuestion";
|
||||
import CTAQuestion from "./CTAQuestion";
|
||||
import RatingQuestion from "./RatingQuestion";
|
||||
import ConsentQuestion from "./ConsentQuestion";
|
||||
import { TSurveyQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface QuestionConditionalProps {
|
||||
question: Question;
|
||||
question: Question | TSurveyQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -21,9 +21,10 @@ import SubmitButton from "@/components/preview/SubmitButton";
|
||||
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { BackButton } from "@/components/preview/BackButton";
|
||||
import { TSurveyRatingQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface RatingQuestionProps {
|
||||
question: RatingQuestion;
|
||||
question: RatingQuestion | TSurveyRatingQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { cn } from "@/../../packages/lib/cn";
|
||||
import { isLight } from "@/lib/utils";
|
||||
import { Question } from "@formbricks/types/questions";
|
||||
import { TSurveyQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
type SubmitButtonProps = {
|
||||
question: Question;
|
||||
question: Question | TSurveyQuestion;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
};
|
||||
|
||||
@@ -2,13 +2,13 @@ import { createDisplay, markDisplayResponded } from "@formbricks/lib/client/disp
|
||||
import { createResponse, updateResponse } from "@formbricks/lib/client/response";
|
||||
import { fetcher } from "@formbricks/lib/fetcher";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { QuestionType, type Logic, type Question } from "@formbricks/types/questions";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { Question, QuestionType } from "@formbricks/types/questions";
|
||||
import { TResponseInput } from "@formbricks/types/v1/responses";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import { useGetOrCreatePerson } from "../people/people";
|
||||
import { TSurvey, TSurveyLogic, TSurveyQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface StoredResponse {
|
||||
id: string | null;
|
||||
@@ -27,8 +27,8 @@ export const useLinkSurvey = (surveyId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useLinkSurveyUtils = (survey: Survey) => {
|
||||
const [currentQuestion, setCurrentQuestion] = useState<Question | null>(null);
|
||||
export const useLinkSurveyUtils = (survey: TSurvey) => {
|
||||
const [currentQuestion, setCurrentQuestion] = useState<TSurveyQuestion | Question | null>(null);
|
||||
const [prefilling, setPrefilling] = useState(true);
|
||||
const [progress, setProgress] = useState(0); // [0, 1]
|
||||
const [finished, setFinished] = useState(false);
|
||||
@@ -39,7 +39,7 @@ export const useLinkSurveyUtils = (survey: Survey) => {
|
||||
const [initiateCountdown, setinitiateCountdown] = useState<boolean>(false);
|
||||
const [storedResponseValue, setStoredResponseValue] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
const URLParams = new URLSearchParams(window.location.search);
|
||||
const URLParams = new URLSearchParams(typeof window !== "undefined" ? window.location.search : "");
|
||||
const isPreview = URLParams.get("preview") === "true";
|
||||
const hasFirstQuestionPrefill = URLParams.has(survey.questions[0].id);
|
||||
const firstQuestionPrefill = hasFirstQuestionPrefill ? URLParams.get(survey.questions[0].id) : null;
|
||||
@@ -331,7 +331,7 @@ const clearStoredResponses = (surveyId: string) => {
|
||||
localStorage.removeItem(`formbricks-${surveyId}-response`);
|
||||
};
|
||||
|
||||
const checkValidity = (question: Question, answer: any): boolean => {
|
||||
const checkValidity = (question: TSurveyQuestion | Question, answer: any): boolean => {
|
||||
if (question.required && (!answer || answer === "")) return false;
|
||||
try {
|
||||
switch (question.type) {
|
||||
@@ -388,7 +388,7 @@ const checkValidity = (question: Question, answer: any): boolean => {
|
||||
}
|
||||
};
|
||||
|
||||
const createAnswer = (question: Question, answer: string): string | number | string[] => {
|
||||
const createAnswer = (question: TSurveyQuestion | Question, answer: string): string | number | string[] => {
|
||||
switch (question.type) {
|
||||
case QuestionType.OpenText:
|
||||
case QuestionType.MultipleChoiceSingle:
|
||||
@@ -421,7 +421,7 @@ const createAnswer = (question: Question, answer: string): string | number | str
|
||||
}
|
||||
};
|
||||
|
||||
const evaluateCondition = (logic: Logic, responseValue: any): boolean => {
|
||||
const evaluateCondition = (logic: TSurveyLogic, responseValue: any): boolean => {
|
||||
switch (logic.condition) {
|
||||
case "equals":
|
||||
return (
|
||||
|
||||
@@ -53,7 +53,7 @@ const shuffle = (array: any[]) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const shuffleArray = (array: any[], shuffleOption: string) => {
|
||||
export const shuffleArray = (array: any[], shuffleOption: string | undefined) => {
|
||||
const arrayCopy = [...array];
|
||||
const otherIndex = arrayCopy.findIndex((element) => element.id === "other");
|
||||
const otherElement = otherIndex !== -1 ? arrayCopy.splice(otherIndex, 1)[0] : null;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-next": "^13.4.12",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-react": "7.33.1",
|
||||
"eslint-config-turbo": "latest"
|
||||
"eslint-config-turbo": "latest",
|
||||
"eslint-plugin-react": "7.33.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ import { TJsState } from "@formbricks/types/v1/js";
|
||||
import { trackAction } from "./actions";
|
||||
import { Config } from "./config";
|
||||
import { NetworkError, Result, err, ok } from "./errors";
|
||||
import { Logger } from "./logger";
|
||||
|
||||
const config = Config.getInstance();
|
||||
const logger = Logger.getInstance();
|
||||
|
||||
const syncWithBackend = async (): Promise<Result<TJsState, NetworkError>> => {
|
||||
const url = `${config.get().apiHost}/api/v1/js/sync`;
|
||||
@@ -42,6 +44,9 @@ export const sync = async (): Promise<void> => {
|
||||
const state = syncResult.value;
|
||||
const oldState = config.get().state;
|
||||
config.update({ state });
|
||||
const surveyNames = state.surveys.map((s) => s.name);
|
||||
logger.debug("Fetched " + surveyNames.length + " surveys during sync: " + surveyNames);
|
||||
|
||||
// if session is new, track action
|
||||
if (!oldState?.session || oldState.session.id !== state.session.id) {
|
||||
const trackActionResult = await trackAction("New Session");
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
"@formbricks/database": "*",
|
||||
"@formbricks/errors": "*",
|
||||
"@formbricks/types": "*",
|
||||
"@paralleldrive/cuid2": "^2.2.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"markdown-it": "^13.0.1",
|
||||
"posthog-node": "^3.1.1",
|
||||
"server-only": "^0.0.1",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"@paralleldrive/cuid2": "^2.2.1"
|
||||
"tailwind-merge": "^1.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "*",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ZPipelineTrigger } from "./pipelines";
|
||||
|
||||
export const ZWebhook = z.object({
|
||||
id: z.string().cuid2(),
|
||||
name: z.string().nullable(),
|
||||
name: z.string().nullish(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
url: z.string().url(),
|
||||
@@ -16,7 +16,7 @@ export type TWebhook = z.infer<typeof ZWebhook>;
|
||||
|
||||
export const ZWebhookInput = z.object({
|
||||
url: z.string().url(),
|
||||
name: z.string().nullable(),
|
||||
name: z.string().nullish(),
|
||||
triggers: z.array(ZPipelineTrigger),
|
||||
surveyIds: z.array(z.string().cuid2()).optional(),
|
||||
});
|
||||
|
||||
112
pnpm-lock.yaml
generated
112
pnpm-lock.yaml
generated
@@ -50,7 +50,7 @@ importers:
|
||||
version: 1.3.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@docsearch/react':
|
||||
specifier: ^3.5.1
|
||||
version: 3.5.1(@algolia/client-search@4.14.2)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.6.0)
|
||||
version: 3.5.1(@algolia/client-search@4.14.2)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.6.0)
|
||||
'@formbricks/lib':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/lib
|
||||
@@ -93,9 +93,15 @@ importers:
|
||||
next-plausible:
|
||||
specifier: ^3.10.1
|
||||
version: 3.10.1(next@13.4.12)(react-dom@18.2.0)(react@18.2.0)
|
||||
next-seo:
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0(next@13.4.12)(react-dom@18.2.0)(react@18.2.0)
|
||||
next-sitemap:
|
||||
specifier: ^4.1.8
|
||||
version: 4.1.8(next@13.4.12)
|
||||
node-fetch:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
prism-react-renderer:
|
||||
specifier: ^2.0.6
|
||||
version: 2.0.6(react@18.2.0)
|
||||
@@ -111,6 +117,9 @@ importers:
|
||||
react-icons:
|
||||
specifier: ^4.10.1
|
||||
version: 4.10.1(react@18.2.0)
|
||||
react-markdown:
|
||||
specifier: ^8.0.7
|
||||
version: 8.0.7(@types/react@18.2.18)(react@18.2.0)
|
||||
react-responsive-embed:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0(prop-types@15.8.1)(react@18.2.0)
|
||||
@@ -2586,7 +2595,7 @@ packages:
|
||||
resolution: {integrity: sha512-2Pu9HDg/uP/IT10rbQ+4OrTQuxIWdKVUEdcw9/w7kZJv9NeHS6skJx1xuRiFyoGKwAzcHXnLp7csE99sj+O1YA==}
|
||||
dev: false
|
||||
|
||||
/@docsearch/react@3.5.1(@algolia/client-search@4.14.2)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.6.0):
|
||||
/@docsearch/react@3.5.1(@algolia/client-search@4.14.2)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.6.0):
|
||||
resolution: {integrity: sha512-t5mEODdLzZq4PTFAm/dvqcvZFdPDMdfPE5rJS5SC8OUq9mPzxEy6b+9THIqNM9P0ocCb4UC5jqBrxKclnuIbzQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '>= 16.8.0 < 19.0.0'
|
||||
@@ -2603,6 +2612,7 @@ packages:
|
||||
'@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2)(search-insights@2.6.0)
|
||||
'@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2)
|
||||
'@docsearch/css': 3.5.1
|
||||
'@types/react': 18.2.18
|
||||
algoliasearch: 4.14.2
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
@@ -6617,6 +6627,7 @@ packages:
|
||||
|
||||
/anymatch@2.0.0:
|
||||
resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
micromatch: 3.1.10
|
||||
normalize-path: 2.1.1
|
||||
@@ -6835,6 +6846,7 @@ packages:
|
||||
|
||||
/async-each@1.0.3:
|
||||
resolution: {integrity: sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
@@ -7154,6 +7166,7 @@ packages:
|
||||
/binary-extensions@1.13.1:
|
||||
resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
@@ -7754,6 +7767,7 @@ packages:
|
||||
/chokidar@2.1.8:
|
||||
resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==}
|
||||
deprecated: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
anymatch: 2.0.0
|
||||
async-each: 1.0.3
|
||||
@@ -8852,6 +8866,11 @@ packages:
|
||||
assert-plus: 1.0.0
|
||||
dev: true
|
||||
|
||||
/data-uri-to-buffer@4.0.1:
|
||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
||||
engines: {node: '>= 12'}
|
||||
dev: false
|
||||
|
||||
/data-urls@1.1.0:
|
||||
resolution: {integrity: sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==}
|
||||
dependencies:
|
||||
@@ -10457,6 +10476,14 @@ packages:
|
||||
bser: 2.1.1
|
||||
dev: true
|
||||
|
||||
/fetch-blob@3.2.0:
|
||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 3.2.1
|
||||
dev: false
|
||||
|
||||
/fflate@0.4.8:
|
||||
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
|
||||
dev: false
|
||||
@@ -10678,6 +10705,13 @@ packages:
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
|
||||
/formdata-polyfill@4.0.10:
|
||||
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
dependencies:
|
||||
fetch-blob: 3.2.0
|
||||
dev: false
|
||||
|
||||
/forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -10938,6 +10972,7 @@ packages:
|
||||
|
||||
/glob-parent@3.1.0:
|
||||
resolution: {integrity: sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
is-glob: 3.1.0
|
||||
path-dirname: 1.0.2
|
||||
@@ -11801,6 +11836,7 @@ packages:
|
||||
/is-binary-path@1.0.1:
|
||||
resolution: {integrity: sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
binary-extensions: 1.13.1
|
||||
dev: true
|
||||
@@ -11956,6 +11992,7 @@ packages:
|
||||
/is-glob@3.1.0:
|
||||
resolution: {integrity: sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
dev: true
|
||||
@@ -12219,7 +12256,7 @@ packages:
|
||||
/isomorphic-fetch@3.0.0:
|
||||
resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==}
|
||||
dependencies:
|
||||
node-fetch: 2.6.7
|
||||
node-fetch: 2.6.12
|
||||
whatwg-fetch: 3.6.2
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
@@ -14648,6 +14685,18 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/next-seo@6.1.0(next@13.4.12)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-iMBpFoJsR5zWhguHJvsoBDxDSmdYTHtnVPB1ij+CD0NReQCP78ZxxbdL9qkKIf4oEuZEqZkrjAQLB0bkII7RYA==}
|
||||
peerDependencies:
|
||||
next: ^8.1.1-canary.54 || >=9.0.0
|
||||
react: '>=16.0.0'
|
||||
react-dom: '>=16.0.0'
|
||||
dependencies:
|
||||
next: 13.4.12(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/next-sitemap@4.1.8(next@13.4.12):
|
||||
resolution: {integrity: sha512-XAXpBHX4o89JfMgvrm0zimlZwpu2iBPXHpimJMUrqOZSc4C2oB1Lv89mxuVON9IE8HOezaM+w4GjJxcYCuGPTQ==}
|
||||
engines: {node: '>=14.18'}
|
||||
@@ -14765,6 +14814,11 @@ packages:
|
||||
resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
|
||||
dev: false
|
||||
|
||||
/node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
dev: false
|
||||
|
||||
/node-fetch@2.6.12:
|
||||
resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
@@ -14788,6 +14842,15 @@ packages:
|
||||
whatwg-url: 5.0.0
|
||||
dev: true
|
||||
|
||||
/node-fetch@3.3.2:
|
||||
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
data-uri-to-buffer: 4.0.1
|
||||
fetch-blob: 3.2.0
|
||||
formdata-polyfill: 4.0.10
|
||||
dev: false
|
||||
|
||||
/node-forge@1.3.1:
|
||||
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
|
||||
engines: {node: '>= 6.13.0'}
|
||||
@@ -14849,6 +14912,7 @@ packages:
|
||||
/normalize-path@2.1.1:
|
||||
resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
remove-trailing-separator: 1.1.0
|
||||
dev: true
|
||||
@@ -15396,6 +15460,7 @@ packages:
|
||||
|
||||
/path-dirname@1.0.2:
|
||||
resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
@@ -17331,7 +17396,33 @@ packages:
|
||||
|
||||
/react-is@18.2.0:
|
||||
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
|
||||
dev: true
|
||||
|
||||
/react-markdown@8.0.7(@types/react@18.2.18)(react@18.2.0):
|
||||
resolution: {integrity: sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '>=16'
|
||||
react: '>=16'
|
||||
dependencies:
|
||||
'@types/hast': 2.3.4
|
||||
'@types/prop-types': 15.7.5
|
||||
'@types/react': 18.2.18
|
||||
'@types/unist': 2.0.6
|
||||
comma-separated-tokens: 2.0.3
|
||||
hast-util-whitespace: 2.0.0
|
||||
prop-types: 15.8.1
|
||||
property-information: 6.2.0
|
||||
react: 18.2.0
|
||||
react-is: 18.2.0
|
||||
remark-parse: 10.0.1
|
||||
remark-rehype: 10.1.0
|
||||
space-separated-tokens: 2.0.2
|
||||
style-to-object: 0.4.2
|
||||
unified: 10.1.2
|
||||
unist-util-visit: 4.1.1
|
||||
vfile: 5.3.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/react-radio-group@3.0.3(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-MUNRRjZqQ2y+1K6rBuH0zO+gLVmCnWIcc5GnNwr9WNoUwZ9FUAKJ1UfsKXwYS93whR6/qrZKoVgiOltRkbzezw==}
|
||||
@@ -17553,6 +17644,7 @@ packages:
|
||||
/readdirp@2.2.1:
|
||||
resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==}
|
||||
engines: {node: '>=0.10'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
graceful-fs: 4.2.10
|
||||
micromatch: 3.1.10
|
||||
@@ -17731,6 +17823,7 @@ packages:
|
||||
|
||||
/remove-trailing-separator@1.1.0:
|
||||
resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
@@ -19070,6 +19163,12 @@ packages:
|
||||
inline-style-parser: 0.1.1
|
||||
dev: false
|
||||
|
||||
/style-to-object@0.4.2:
|
||||
resolution: {integrity: sha512-1JGpfPB3lo42ZX8cuPrheZbfQ6kqPPnPHlKMyeRYtfKD+0jG+QsXgXN57O/dvJlzlB2elI6dGmrPnl5VPQFPaA==}
|
||||
dependencies:
|
||||
inline-style-parser: 0.1.1
|
||||
dev: false
|
||||
|
||||
/styled-jsx@5.1.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -20588,6 +20687,11 @@ packages:
|
||||
defaults: 1.0.4
|
||||
dev: true
|
||||
|
||||
/web-streams-polyfill@3.2.1:
|
||||
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
|
||||
engines: {node: '>= 8'}
|
||||
dev: false
|
||||
|
||||
/webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"RAILWAY_STATIC_URL",
|
||||
"RENDER_EXTERNAL_URL",
|
||||
"SENTRY_DSN",
|
||||
"STRAPI_API_KEY",
|
||||
"STRIPE_SECRET_KEY",
|
||||
"STRIPE_WEBHOOK_SECRET",
|
||||
"TELEMETRY_DISABLED",
|
||||
|
||||
Reference in New Issue
Block a user