mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-31 20:39:11 -06:00
Compare commits
11 Commits
@formbrick
...
v1.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97cc6232c2 | ||
|
|
7331d1dd5a | ||
|
|
3f8bf4c34c | ||
|
|
91ceffba01 | ||
|
|
8c38495812 | ||
|
|
c8c98499ed | ||
|
|
af181eabdc | ||
|
|
822c48ff52 | ||
|
|
70d211a038 | ||
|
|
a77ce55a1d | ||
|
|
a376eb9b51 |
5
.github/workflows/release-docker-github.yml
vendored
5
.github/workflows/release-docker-github.yml
vendored
@@ -16,10 +16,8 @@ env:
|
||||
# github.repository as <account>/<repo>
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -38,7 +36,7 @@ jobs:
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1
|
||||
with:
|
||||
cosign-release: 'v2.1.1'
|
||||
cosign-release: "v2.1.1"
|
||||
|
||||
# Set up BuildKit Docker container builder to be able to build
|
||||
# multi-platform images and export cache
|
||||
@@ -71,6 +69,7 @@ jobs:
|
||||
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
|
||||
with:
|
||||
context: .
|
||||
file: ./apps/web/Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
import formbricks from "@formbricks/js";
|
||||
import { useRouter } from "next/router";
|
||||
import { FormEvent } from "react";
|
||||
|
||||
export default function SiginPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const submitAction = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
|
||||
formbricks.setEmail("matti@example.com");
|
||||
formbricks.setUserId("123456");
|
||||
formbricks.setAttribute("Plan", "Premium");
|
||||
}
|
||||
router.push("/app");
|
||||
};
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
|
||||
Sign in to your account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Or{" "}
|
||||
<a href="#" className="font-medium text-indigo-600 hover:text-indigo-500">
|
||||
start your 14-day free trial
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white px-4 py-8 shadow sm:rounded-lg sm:px-10">
|
||||
<form className="space-y-6" onSubmit={submitAction}>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium leading-6 text-gray-900">
|
||||
Email address
|
||||
</label>
|
||||
<div className="mt-2">
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium leading-6 text-gray-900">
|
||||
Password
|
||||
</label>
|
||||
<div className="mt-2">
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="remember-me"
|
||||
name="remember-me"
|
||||
type="checkbox"
|
||||
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
|
||||
/>
|
||||
<label htmlFor="remember-me" className="ml-2 block text-sm text-gray-900">
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="text-sm">
|
||||
<a href="#" className="font-medium text-indigo-600 hover:text-indigo-500">
|
||||
Forgot your password?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex w-full justify-center rounded-md bg-indigo-500 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
|
||||
Sign in
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="mt-6">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="bg-white px-2 text-gray-500">Or continue with</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex w-full justify-center rounded-md bg-white px-4 py-2 text-gray-500 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:outline-offset-0">
|
||||
<span className="sr-only">Sign in with Facebook</span>
|
||||
<svg className="h-5 w-5" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M20 10c0-5.523-4.477-10-10-10S0 4.477 0 10c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V10h2.54V7.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V10h2.773l-.443 2.89h-2.33v6.988C16.343 19.128 20 14.991 20 10z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex w-full justify-center rounded-md bg-white px-4 py-2 text-gray-500 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:outline-offset-0">
|
||||
<span className="sr-only">Sign in with Twitter</span>
|
||||
<svg className="h-5 w-5" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M6.29 18.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0020 3.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.073 4.073 0 01.8 7.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 010 16.407a11.616 11.616 0 006.29 1.84" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex w-full justify-center rounded-md bg-white px-4 py-2 text-gray-500 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:outline-offset-0">
|
||||
<span className="sr-only">Sign in with GitHub</span>
|
||||
<svg className="h-5 w-5" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
import formbricks from "@formbricks/js";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
import fbsetup from "../../public/fb-setup.png";
|
||||
|
||||
export default function AppPage({}) {
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (darkMode) {
|
||||
document.body.classList.add("dark");
|
||||
} else {
|
||||
document.body.classList.remove("dark");
|
||||
}
|
||||
}, [darkMode]);
|
||||
|
||||
return (
|
||||
<div className="h-full bg-white px-12 py-6 dark:bg-slate-800">
|
||||
<div className="flex flex-col justify-between md:flex-row">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">
|
||||
Formbricks In-product Survey Demo App
|
||||
</h1>
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
This app helps you test your in-app surveys. You can create and test user actions, create and
|
||||
update user attributes, etc.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
className="mt-2 rounded-lg bg-slate-200 px-6 py-1 dark:bg-slate-700 dark:text-slate-100"
|
||||
onClick={() => setDarkMode(!darkMode)}>
|
||||
{darkMode ? "Toggle Light Mode" : "Toggle Dark Mode"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="my-4 grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<div className="rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-900">
|
||||
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">1. Setup .env</h3>
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
Copy the environment ID of your Formbricks app to the env variable in demo/.env
|
||||
</p>
|
||||
<Image src={fbsetup} alt="fb setup" className="mt-4 rounded" priority />
|
||||
|
||||
<div className="mt-4 flex-col items-start text-sm text-slate-700 dark:text-slate-300 sm:flex sm:items-center sm:text-base">
|
||||
<p className="mb-1 sm:mb-0 sm:mr-2">You're connected with env:</p>
|
||||
<div className="flex items-center">
|
||||
<strong className="w-32 truncate sm:w-auto">
|
||||
{process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID}
|
||||
</strong>
|
||||
<span className="relative ml-2 flex h-3 w-3">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75"></span>
|
||||
<span className="relative inline-flex h-3 w-3 rounded-full bg-green-500"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-900">
|
||||
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">2. Widget Logs</h3>
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
Look at the logs to understand how the widget works.{" "}
|
||||
<strong className="dark:text-white">Open your browser console</strong> to see the logs.
|
||||
</p>
|
||||
{/* <div className="max-h-[40vh] overflow-y-auto py-4">
|
||||
<LogsContainer />
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:grid md:grid-cols-3">
|
||||
<div className="col-span-3 rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-gray-600 dark:bg-gray-800">
|
||||
<h3 className="text-lg font-semibold dark:text-white">
|
||||
Reset person / pull data from Formbricks app
|
||||
</h3>
|
||||
<p className="text-slate-700 dark:text-gray-300">
|
||||
On formbricks.reset() a few things happen: <strong>New person is created</strong> and{" "}
|
||||
<strong>surveys & no-code actions are pulled from Formbricks:</strong>.
|
||||
</p>
|
||||
<button
|
||||
className="my-4 rounded-lg bg-slate-500 px-6 py-3 text-white hover:bg-slate-700 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
formbricks.reset();
|
||||
}}>
|
||||
Reset
|
||||
</button>
|
||||
<p className="text-xs text-slate-700 dark:text-gray-300">
|
||||
If you made a change in Formbricks app and it does not seem to work, hit 'Reset' and
|
||||
try again.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
console.log("Inner Text");
|
||||
}}>
|
||||
Inner Text
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-700 dark:text-gray-300">Inner Text only</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button
|
||||
id="css-id"
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
console.log("Inner Text + CSS ID");
|
||||
}}>
|
||||
Inner Text
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-700 dark:text-gray-300">Inner Text + Css ID</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button
|
||||
className="css-class mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
console.log("Inner Text + CSS Class");
|
||||
}}>
|
||||
Inner Text
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-700 dark:text-gray-300">Inner Text + CSS Class</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button
|
||||
id="css-id"
|
||||
className="css-class mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
console.log("ID + Class");
|
||||
}}>
|
||||
ID and Class
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-700 dark:text-gray-300">ID + Class</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button
|
||||
id="css-id"
|
||||
className="mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
console.log("ID + Class");
|
||||
}}>
|
||||
ID only
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-700 dark:text-gray-300">ID only</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button
|
||||
className="css-class mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
console.log("Class only");
|
||||
}}>
|
||||
Class only
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-700 dark:text-gray-300">Class only</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<div>
|
||||
<button
|
||||
className="css-1 css-2 mb-4 rounded-lg bg-slate-800 px-6 py-3 text-white hover:bg-slate-700 dark:bg-gray-700 dark:hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
console.log("Class + Class");
|
||||
}}>
|
||||
Class + Class
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-700 dark:text-gray-300">Class + Class</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -146,7 +146,7 @@ This set of API can be used to
|
||||
{
|
||||
"id": "lkjaxb73ulydzeumhd51sx9g",
|
||||
"type": "openText",
|
||||
"headline": "What is the main benefit your receive from My Product?",
|
||||
"headline": "What is the main benefit you receive from My Product?",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -134,7 +134,7 @@ export const templates: TTemplate[] = [
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionType.OpenText,
|
||||
headline: "What is the main benefit your receive from Formbricks?",
|
||||
headline: "What is the main benefit you receive from Formbricks?",
|
||||
inputType: "text",
|
||||
longAnswer: true,
|
||||
required: true,
|
||||
|
||||
@@ -9,7 +9,11 @@ interface WidgetStatusIndicatorProps {
|
||||
}
|
||||
|
||||
export default async function WidgetStatusIndicator({ environmentId, type }: WidgetStatusIndicatorProps) {
|
||||
const [environment] = await Promise.all([getEnvironment(environmentId)]);
|
||||
const environment = await getEnvironment(environmentId);
|
||||
|
||||
if (!environment) {
|
||||
throw new Error("Environment not found");
|
||||
}
|
||||
|
||||
const stati = {
|
||||
notImplemented: {
|
||||
|
||||
@@ -12,8 +12,8 @@ import { TSurvey } from "@formbricks/types/surveys";
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { TAttributeClass } from "@formbricks/types/attributeClasses";
|
||||
import { TActionClass } from "@formbricks/types/actionClasses";
|
||||
import { ErrorComponent } from "@formbricks/ui/ErrorComponent";
|
||||
import { TMembershipRole } from "@formbricks/types/memberships";
|
||||
import Loading from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/loading";
|
||||
|
||||
interface SurveyEditorProps {
|
||||
survey: TSurvey;
|
||||
@@ -41,6 +41,7 @@ export default function SurveyEditor({
|
||||
|
||||
useEffect(() => {
|
||||
if (survey) {
|
||||
if (localSurvey) return;
|
||||
setLocalSurvey(JSON.parse(JSON.stringify(survey)));
|
||||
|
||||
if (survey.questions.length > 0) {
|
||||
@@ -59,7 +60,7 @@ export default function SurveyEditor({
|
||||
}, [localSurvey?.type]);
|
||||
|
||||
if (!localSurvey) {
|
||||
return <ErrorComponent />;
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -97,6 +97,7 @@ export default function WhenToSendCard({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isAddEventModalOpen) return;
|
||||
if (activeIndex !== null) {
|
||||
const newActionClass = actionClassArray[actionClassArray.length - 1].name;
|
||||
const currentActionClass = localSurvey.triggers[activeIndex];
|
||||
|
||||
@@ -406,7 +406,7 @@ export const templates: TTemplate[] = [
|
||||
{
|
||||
id: createId(),
|
||||
type: TSurveyQuestionType.OpenText,
|
||||
headline: "What is the main benefit your receive from {{productName}}?",
|
||||
headline: "What is the main benefit you receive from {{productName}}?",
|
||||
required: true,
|
||||
inputType: "text",
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getUpdatedState } from "@/app/api/v1/(legacy)/js/sync/lib/sync";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { getOrCreatePersonByUserId } from "@formbricks/lib/person/service";
|
||||
import { createPerson, getPersonByUserId } from "@formbricks/lib/person/service";
|
||||
import { ZJsPeopleUserIdInput } from "@formbricks/types/js";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
@@ -26,9 +26,12 @@ export async function POST(req: Request): Promise<NextResponse> {
|
||||
|
||||
const { environmentId, userId } = inputValidation.data;
|
||||
|
||||
const personWithUserId = await getOrCreatePersonByUserId(userId, environmentId);
|
||||
let person = await getPersonByUserId(environmentId, userId);
|
||||
if (!person) {
|
||||
person = await createPerson(environmentId, userId);
|
||||
}
|
||||
|
||||
const state = await getUpdatedState(environmentId, personWithUserId.id);
|
||||
const state = await getUpdatedState(environmentId, person.id);
|
||||
return responses.successResponse({ ...state }, true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -4,13 +4,12 @@ import { getLatestActionByPersonId } from "@formbricks/lib/action/service";
|
||||
import { getActionClasses } from "@formbricks/lib/actionClass/service";
|
||||
import { IS_FORMBRICKS_CLOUD, PRICING_USERTARGETING_FREE_MTU } from "@formbricks/lib/constants";
|
||||
import { getEnvironment, updateEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getOrCreatePersonByUserId, getPersonByUserId } from "@formbricks/lib/person/service";
|
||||
import { createPerson, getPersonByUserId } from "@formbricks/lib/person/service";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { getSyncSurveys } from "@formbricks/lib/survey/service";
|
||||
import { getMonthlyActiveTeamPeopleCount, getTeamByEnvironmentId } from "@formbricks/lib/team/service";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TJsState, ZJsPeopleUserIdInput } from "@formbricks/types/js";
|
||||
import { TPerson } from "@formbricks/types/people";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function OPTIONS(): Promise<NextResponse> {
|
||||
@@ -72,13 +71,15 @@ export async function GET(
|
||||
isMauLimitReached = !hasUserTargetingSubscription && currentMau >= PRICING_USERTARGETING_FREE_MTU;
|
||||
}
|
||||
|
||||
let person: TPerson | null;
|
||||
let person = await getPersonByUserId(environmentId, userId);
|
||||
if (!isMauLimitReached) {
|
||||
person = await getOrCreatePersonByUserId(userId, environmentId);
|
||||
if (!person) {
|
||||
person = await createPerson(environmentId, userId);
|
||||
}
|
||||
} else {
|
||||
person = await getPersonByUserId(userId, environmentId);
|
||||
const errorMessage = `Monthly Active Users limit in the current plan is reached in ${environmentId}`;
|
||||
if (!person) {
|
||||
// if it's a new person and MAU limit is reached, throw an error
|
||||
throw new Error(errorMessage);
|
||||
} else {
|
||||
// check if person has been active this month
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { getOrCreatePersonByUserId, updatePerson } from "@formbricks/lib/person/service";
|
||||
import { getPersonByUserId, updatePerson } from "@formbricks/lib/person/service";
|
||||
import { ZPersonUpdateInput } from "@formbricks/types/people";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
@@ -31,7 +31,7 @@ export async function POST(req: Request, context: Context): Promise<NextResponse
|
||||
);
|
||||
}
|
||||
|
||||
const person = await getOrCreatePersonByUserId(userId, environmentId);
|
||||
const person = await getPersonByUserId(environmentId, userId);
|
||||
|
||||
if (!person) {
|
||||
return responses.notFoundResponse("PersonByUserId", userId, true);
|
||||
|
||||
@@ -6,7 +6,7 @@ import PinScreen from "@/app/s/[surveyId]/components/PinScreen";
|
||||
import SurveyInactive from "@/app/s/[surveyId]/components/SurveyInactive";
|
||||
import { checkValidity } from "@/app/s/[surveyId]/lib/prefilling";
|
||||
import { REVALIDATION_INTERVAL, WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { getOrCreatePersonByUserId } from "@formbricks/lib/person/service";
|
||||
import { createPerson, getPersonByUserId } from "@formbricks/lib/person/service";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { getResponseBySingleUseId } from "@formbricks/lib/response/service";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
@@ -147,7 +147,11 @@ export default async function LinkSurveyPage({ params, searchParams }: LinkSurve
|
||||
|
||||
const userId = searchParams.userId;
|
||||
if (userId) {
|
||||
await getOrCreatePersonByUserId(userId, survey.environmentId);
|
||||
// make sure the person exists or get's created
|
||||
const person = await getPersonByUserId(survey.environmentId, userId);
|
||||
if (!person) {
|
||||
await createPerson(survey.environmentId, userId);
|
||||
}
|
||||
}
|
||||
|
||||
const isSurveyPinProtected = Boolean(!!survey && survey.pin);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@formbricks/web",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf .turbo node_modules .next",
|
||||
@@ -11,7 +11,6 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/s3-presigned-post": "^3.451.0",
|
||||
"@formbricks/api": "workspace:*",
|
||||
"@formbricks/database": "workspace:*",
|
||||
"@formbricks/ee": "workspace:*",
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
"@changesets/cli": "^2.26.2",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^15.0.1",
|
||||
"lint-staged": "^15.1.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"tsx": "^3.13.0",
|
||||
"tsx": "^4.2.0",
|
||||
"turbo": "^1.10.16"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@formbricks/api",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Formbricks-api is an api wrapper for the Formbricks client API",
|
||||
"keywords": [
|
||||
"Formbricks",
|
||||
|
||||
18
packages/api/src/api/client/action.ts
Normal file
18
packages/api/src/api/client/action.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Result } from "@formbricks/types/errorHandlers";
|
||||
import { NetworkError } from "@formbricks/types/errors";
|
||||
import { TActionInput } from "@formbricks/types/actions";
|
||||
import { makeRequest } from "../../utils/makeRequest";
|
||||
|
||||
export class ActionAPI {
|
||||
private apiHost: string;
|
||||
private environmentId: string;
|
||||
|
||||
constructor(apiHost: string, environmentId: string) {
|
||||
this.apiHost = apiHost;
|
||||
this.environmentId = environmentId;
|
||||
}
|
||||
|
||||
async create(actionInput: Omit<TActionInput, "environmentId">): Promise<Result<{}, NetworkError | Error>> {
|
||||
return makeRequest(this.apiHost, `/api/v1/client/${this.environmentId}/actions`, "POST", actionInput);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,21 @@
|
||||
import { ResponseAPI } from "./response";
|
||||
import { DisplayAPI } from "./display";
|
||||
import { ApiConfig } from "../../types";
|
||||
import { ActionAPI } from "./action";
|
||||
import { PeopleAPI } from "./people";
|
||||
|
||||
export class Client {
|
||||
response: ResponseAPI;
|
||||
display: DisplayAPI;
|
||||
action: ActionAPI;
|
||||
people: PeopleAPI;
|
||||
|
||||
constructor(options: ApiConfig) {
|
||||
const { apiHost, environmentId } = options;
|
||||
|
||||
this.response = new ResponseAPI(apiHost, environmentId);
|
||||
this.display = new DisplayAPI(apiHost, environmentId);
|
||||
this.action = new ActionAPI(apiHost, environmentId);
|
||||
this.people = new PeopleAPI(apiHost, environmentId);
|
||||
}
|
||||
}
|
||||
|
||||
33
packages/api/src/api/client/people.ts
Normal file
33
packages/api/src/api/client/people.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Result } from "@formbricks/types/errorHandlers";
|
||||
import { NetworkError } from "@formbricks/types/errors";
|
||||
import { makeRequest } from "../../utils/makeRequest";
|
||||
import { TPerson, TPersonUpdateInput } from "@formbricks/types/people";
|
||||
|
||||
export class PeopleAPI {
|
||||
private apiHost: string;
|
||||
private environmentId: string;
|
||||
|
||||
constructor(apiHost: string, environmentId: string) {
|
||||
this.apiHost = apiHost;
|
||||
this.environmentId = environmentId;
|
||||
}
|
||||
|
||||
async create(userId: string): Promise<Result<TPerson, NetworkError | Error>> {
|
||||
return makeRequest(this.apiHost, `/api/v1/client/${this.environmentId}/people`, "POST", {
|
||||
environmentId: this.environmentId,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
async update(
|
||||
userId: string,
|
||||
personInput: TPersonUpdateInput
|
||||
): Promise<Result<TPerson, NetworkError | Error>> {
|
||||
return makeRequest(
|
||||
this.apiHost,
|
||||
`/api/v1/client/${this.environmentId}/people/${userId}`,
|
||||
"POST",
|
||||
personInput
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/lib": "workspace:*",
|
||||
"stripe": "^14.4.0"
|
||||
"stripe": "^14.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"clean": "rimraf node_modules .turbo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.53.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-next": "^14.0.3",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-config-turbo": "latest",
|
||||
|
||||
@@ -42,9 +42,9 @@
|
||||
"@formbricks/surveys": "workspace:*",
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@types/jest": "^29.5.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"@types/jest": "^29.5.9",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"babel-jest": "^29.7.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Config } from "./config";
|
||||
import { NetworkError, Result, err, okVoid } from "./errors";
|
||||
import { Logger } from "./logger";
|
||||
import { renderWidget } from "./widget";
|
||||
import { FormbricksAPI } from "@formbricks/api";
|
||||
const logger = Logger.getInstance();
|
||||
const config = Config.getInstance();
|
||||
|
||||
@@ -23,24 +24,23 @@ export const trackAction = async (
|
||||
// don't send actions to the backend if the person is not identified
|
||||
if (config.get().state?.person?.userId && !intentsToNotCreateOnApp.includes(name)) {
|
||||
logger.debug(`Sending action "${name}" to backend`);
|
||||
const res = await fetch(`${config.get().apiHost}/api/v1/client/${config.get().environmentId}/actions`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
|
||||
body: JSON.stringify(input),
|
||||
const api = new FormbricksAPI({
|
||||
apiHost: config.get().apiHost,
|
||||
environmentId: config.get().environmentId,
|
||||
});
|
||||
const res = await api.client.action.create({
|
||||
...input,
|
||||
userId: config.get().state.person!.userId,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await res.json();
|
||||
|
||||
return err({
|
||||
code: "network_error",
|
||||
message: `Error tracking action: ${JSON.stringify(error)}`,
|
||||
status: res.status,
|
||||
url: res.url,
|
||||
responseMessage: error.message,
|
||||
message: `Error tracking action ${name}`,
|
||||
status: 500,
|
||||
url: `${config.get().apiHost}/api/v1/client/${config.get().environmentId}/actions`,
|
||||
responseMessage: res.error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
import { TJsState } from "@formbricks/types/js";
|
||||
import { TPerson, TPersonUpdateInput } from "@formbricks/types/people";
|
||||
import { Config } from "./config";
|
||||
import {
|
||||
AttributeAlreadyExistsError,
|
||||
MissingPersonError,
|
||||
NetworkError,
|
||||
Result,
|
||||
err,
|
||||
ok,
|
||||
okVoid,
|
||||
} from "./errors";
|
||||
import { AttributeAlreadyExistsError, MissingPersonError, NetworkError, Result, err, okVoid } from "./errors";
|
||||
import { deinitalize, initialize } from "./initialize";
|
||||
import { Logger } from "./logger";
|
||||
import { sync } from "./sync";
|
||||
import { FormbricksAPI } from "@formbricks/api";
|
||||
|
||||
const config = Config.getInstance();
|
||||
const logger = Logger.getInstance();
|
||||
@@ -20,7 +12,7 @@ const logger = Logger.getInstance();
|
||||
export const updatePersonAttribute = async (
|
||||
key: string,
|
||||
value: string
|
||||
): Promise<Result<TJsState, NetworkError | MissingPersonError>> => {
|
||||
): Promise<Result<void, NetworkError | MissingPersonError>> => {
|
||||
if (!config.get().state.person || !config.get().state.person?.id) {
|
||||
return err({
|
||||
code: "missing_person",
|
||||
@@ -34,28 +26,21 @@ export const updatePersonAttribute = async (
|
||||
},
|
||||
};
|
||||
|
||||
const res = await fetch(
|
||||
`${config.get().apiHost}/api/v1/client/${config.get().environmentId}/people/${
|
||||
config.get().state.person?.userId
|
||||
}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
}
|
||||
);
|
||||
|
||||
const resJson = await res.json();
|
||||
const api = new FormbricksAPI({
|
||||
apiHost: config.get().apiHost,
|
||||
environmentId: config.get().environmentId,
|
||||
});
|
||||
const res = await api.client.people.update(config.get().state.person!.userId, input);
|
||||
|
||||
if (!res.ok) {
|
||||
return err({
|
||||
code: "network_error",
|
||||
status: res.status,
|
||||
message: "Error updating person",
|
||||
url: res.url,
|
||||
responseMessage: resJson.message,
|
||||
status: 500,
|
||||
message: `Error updating person with userId ${config.get().state.person?.userId}`,
|
||||
url: `${config.get().apiHost}/api/v1/client/${config.get().environmentId}/people/${
|
||||
config.get().state.person?.userId
|
||||
}`,
|
||||
responseMessage: res.error.message,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -67,7 +52,7 @@ export const updatePersonAttribute = async (
|
||||
userId: config.get().state.person?.userId,
|
||||
});
|
||||
|
||||
return ok(resJson.data as TJsState);
|
||||
return okVoid();
|
||||
};
|
||||
|
||||
export const hasAttributeValue = (key: string, value: string): boolean => {
|
||||
|
||||
@@ -240,7 +240,7 @@ export const createAction = async (data: TActionInput): Promise<TAction> => {
|
||||
actionType = "automatic";
|
||||
}
|
||||
|
||||
const person = await getPersonByUserId(userId, environmentId);
|
||||
const person = await getPersonByUserId(environmentId, userId);
|
||||
|
||||
if (!person) {
|
||||
throw new Error("Person not found");
|
||||
|
||||
@@ -70,7 +70,7 @@ export const updateDisplay = async (
|
||||
|
||||
let person: TPerson | null = null;
|
||||
if (displayInput.userId) {
|
||||
person = await getPersonByUserId(displayInput.userId, displayInput.environmentId);
|
||||
person = await getPersonByUserId(displayInput.environmentId, displayInput.userId);
|
||||
if (!person) {
|
||||
throw new ResourceNotFoundError("Person", displayInput.userId);
|
||||
}
|
||||
@@ -160,7 +160,7 @@ export const createDisplay = async (displayInput: TDisplayCreateInput): Promise<
|
||||
try {
|
||||
let person;
|
||||
if (displayInput.userId) {
|
||||
person = await getPersonByUserId(displayInput.userId, displayInput.environmentId);
|
||||
person = await getPersonByUserId(displayInput.environmentId, displayInput.userId);
|
||||
}
|
||||
const display = await prisma.display.create({
|
||||
data: {
|
||||
|
||||
@@ -22,14 +22,13 @@ import { validateInputs } from "../utils/validate";
|
||||
import { environmentCache } from "./cache";
|
||||
import { formatEnvironmentDateFields } from "./util";
|
||||
|
||||
export const getEnvironment = (environmentId: string) =>
|
||||
export const getEnvironment = (environmentId: string): Promise<TEnvironment | null> =>
|
||||
unstable_cache(
|
||||
async (): Promise<TEnvironment> => {
|
||||
async () => {
|
||||
validateInputs([environmentId, ZId]);
|
||||
let environmentPrisma;
|
||||
|
||||
try {
|
||||
environmentPrisma = await prisma.environment.findUnique({
|
||||
return await prisma.environment.findUnique({
|
||||
where: {
|
||||
id: environmentId,
|
||||
},
|
||||
@@ -42,16 +41,6 @@ export const getEnvironment = (environmentId: string) =>
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
const environment = ZEnvironment.parse(environmentPrisma);
|
||||
return environment;
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
console.error(JSON.stringify(error.errors, null, 2));
|
||||
}
|
||||
throw new ValidationError("Data validation of environment failed");
|
||||
}
|
||||
},
|
||||
[`getEnvironment-${environmentId}`],
|
||||
{
|
||||
|
||||
@@ -12,11 +12,12 @@
|
||||
"lint:report": "eslint . --format json --output-file ../../lint-results/app-store.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/api": "*",
|
||||
"@aws-sdk/client-s3": "3.451.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.451.0",
|
||||
"@aws-sdk/s3-presigned-post": "^3.454.0",
|
||||
"@aws-sdk/client-s3": "3.454.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.454.0",
|
||||
"@t3-oss/env-nextjs": "^0.7.1",
|
||||
"mime": "3.0.0",
|
||||
"@formbricks/api": "*",
|
||||
"@formbricks/database": "*",
|
||||
"@formbricks/types": "*",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
|
||||
@@ -25,6 +25,6 @@ export const canUserAccessPerson = async (userId: string, personId: string): Pro
|
||||
[`canUserAccessPerson-${userId}-people-${personId}`],
|
||||
{
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
tags: [personCache.tag.byId(personId), personCache.tag.byUserId(userId)],
|
||||
tags: [personCache.tag.byId(personId)],
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -14,11 +14,8 @@ export const personCache = {
|
||||
byEnvironmentId(environmentId: string): string {
|
||||
return `environments-${environmentId}-people`;
|
||||
},
|
||||
byUserId(userId: string): string {
|
||||
return `users-${userId}-people`;
|
||||
},
|
||||
byEnvironmentIdAndUserId(environmentId: string, userId: string): string {
|
||||
return `environments-${environmentId}-users-${userId}-people`;
|
||||
return `environments-${environmentId}-personByUserId-${userId}`;
|
||||
},
|
||||
},
|
||||
revalidate({ id, environmentId, userId }: RevalidateProps): void {
|
||||
@@ -26,16 +23,12 @@ export const personCache = {
|
||||
revalidateTag(this.tag.byId(id));
|
||||
}
|
||||
|
||||
if (environmentId) {
|
||||
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
revalidateTag(this.tag.byUserId(userId));
|
||||
}
|
||||
|
||||
if (environmentId && userId) {
|
||||
revalidateTag(this.tag.byEnvironmentIdAndUserId(environmentId, userId));
|
||||
}
|
||||
|
||||
if (environmentId) {
|
||||
revalidateTag(this.tag.byEnvironmentId(environmentId));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -181,7 +181,8 @@ export const createPerson = async (environmentId: string, userId: string): Promi
|
||||
|
||||
personCache.revalidate({
|
||||
id: transformedPerson.id,
|
||||
environmentId: transformedPerson.environmentId,
|
||||
environmentId,
|
||||
userId,
|
||||
});
|
||||
|
||||
return transformedPerson;
|
||||
@@ -290,10 +291,10 @@ export const updatePerson = async (personId: string, personInput: TPersonUpdateI
|
||||
}
|
||||
};
|
||||
|
||||
export const getPersonByUserId = async (userId: string, environmentId: string): Promise<TPerson | null> => {
|
||||
const personPrisma = await unstable_cache(
|
||||
export const getPersonByUserId = async (environmentId: string, userId: string): Promise<TPerson | null> =>
|
||||
await unstable_cache(
|
||||
async () => {
|
||||
validateInputs([userId, ZString], [environmentId, ZId]);
|
||||
validateInputs([environmentId, ZId], [userId, ZString]);
|
||||
|
||||
// check if userId exists as a column
|
||||
const personWithUserId = await prisma.person.findFirst({
|
||||
@@ -305,7 +306,7 @@ export const getPersonByUserId = async (userId: string, environmentId: string):
|
||||
});
|
||||
|
||||
if (personWithUserId) {
|
||||
return personWithUserId;
|
||||
return transformPrismaPerson(personWithUserId);
|
||||
}
|
||||
|
||||
// Check if a person with the userId attribute exists
|
||||
@@ -347,57 +348,13 @@ export const getPersonByUserId = async (userId: string, environmentId: string):
|
||||
|
||||
personCache.revalidate({
|
||||
id: personWithUserIdAttribute.id,
|
||||
environmentId: personWithUserIdAttribute.environmentId,
|
||||
environmentId,
|
||||
userId,
|
||||
});
|
||||
|
||||
return personWithUserIdAttribute;
|
||||
return transformPrismaPerson(personWithUserIdAttribute);
|
||||
},
|
||||
[`getPersonByUserId-${userId}-${environmentId}`],
|
||||
{
|
||||
tags: [personCache.tag.byEnvironmentIdAndUserId(environmentId, userId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
if (!personPrisma) {
|
||||
return null;
|
||||
}
|
||||
return transformPrismaPerson(personPrisma);
|
||||
};
|
||||
|
||||
export const getOrCreatePersonByUserId = async (userId: string, environmentId: string): Promise<TPerson> =>
|
||||
await unstable_cache(
|
||||
async () => {
|
||||
validateInputs([userId, ZString], [environmentId, ZId]);
|
||||
|
||||
let person = await getPersonByUserId(userId, environmentId);
|
||||
|
||||
if (person) {
|
||||
return person;
|
||||
}
|
||||
|
||||
// create a new person
|
||||
const personPrisma = await prisma.person.create({
|
||||
data: {
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
userId,
|
||||
},
|
||||
select: selectPerson,
|
||||
});
|
||||
|
||||
personCache.revalidate({
|
||||
id: personPrisma.id,
|
||||
environmentId: personPrisma.environmentId,
|
||||
userId,
|
||||
});
|
||||
|
||||
return transformPrismaPerson(personPrisma);
|
||||
},
|
||||
[`getOrCreatePersonByUserId-${userId}-${environmentId}`],
|
||||
[`getPersonByUserId-${environmentId}-${userId}`],
|
||||
{
|
||||
tags: [personCache.tag.byEnvironmentIdAndUserId(environmentId, userId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
|
||||
@@ -201,7 +201,7 @@ export const createResponse = async (responseInput: TResponseInput): Promise<TRe
|
||||
let person: TPerson | null = null;
|
||||
|
||||
if (responseInput.userId) {
|
||||
person = await getPersonByUserId(responseInput.userId, responseInput.environmentId);
|
||||
person = await getPersonByUserId(responseInput.environmentId, responseInput.userId);
|
||||
if (!person) {
|
||||
throw new ResourceNotFoundError("Person", responseInput.userId);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/request-promise-native": "~1.0.21",
|
||||
"@typescript-eslint/parser": "~6.11",
|
||||
"@typescript-eslint/parser": "~6.12",
|
||||
"eslint-plugin-n8n-nodes-base": "^1.16.1",
|
||||
"gulp": "^4.0.2",
|
||||
"n8n-core": "legacy",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@preact/preset-vite": "^2.6.0",
|
||||
"@preact/preset-vite": "^2.7.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"postcss": "^8.4.31",
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
interface HeadlineProps {
|
||||
headline?: string;
|
||||
questionId: string;
|
||||
style?: any;
|
||||
required?: boolean;
|
||||
alignTextCenter?: boolean;
|
||||
}
|
||||
|
||||
export default function Headline({ headline, questionId, style, required = true }: HeadlineProps) {
|
||||
export default function Headline({
|
||||
headline,
|
||||
questionId,
|
||||
required = true,
|
||||
alignTextCenter = false,
|
||||
}: HeadlineProps) {
|
||||
return (
|
||||
<label
|
||||
htmlFor={questionId}
|
||||
className="text-heading mb-1.5 block text-base font-semibold leading-6"
|
||||
style={style}>
|
||||
<div className={"mr-[3ch] flex items-center justify-between"} style={style}>
|
||||
<label htmlFor={questionId} className="text-heading mb-1.5 block text-base font-semibold leading-6">
|
||||
<div
|
||||
className={`flex items-center ${alignTextCenter ? "justify-center" : "mr-[3ch] justify-between"}`}>
|
||||
{headline}
|
||||
{!required && (
|
||||
<span className="text-info-text self-start text-sm font-normal leading-7" tabIndex={-1}>
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function ThankYouCard({
|
||||
<span className="bg-shadow mb-[10px] inline-block h-1 w-16 rounded-[100%]"></span>
|
||||
|
||||
<div>
|
||||
<Headline headline={headline} questionId="thankYouCard" style={{ "justify-content": "center" }} />
|
||||
<Headline alignTextCenter={true} headline={headline} questionId="thankYouCard" />
|
||||
<Subheader subheader={subheader} questionId="thankYouCard" />
|
||||
<RedirectCountDown redirectUrl={redirectUrl} isRedirectDisabled={isRedirectDisabled} />
|
||||
</div>
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
"clean": "rimraf node_modules dist turbo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.9.0",
|
||||
"@types/react": "18.2.37",
|
||||
"@types/react-dom": "18.2.15",
|
||||
"typescript": "^5.2.2"
|
||||
"@types/node": "20.9.3",
|
||||
"@types/react": "18.2.38",
|
||||
"@types/react-dom": "18.2.16",
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ export default async function EnvironmentNotice({ environmentId }: EnvironmentNo
|
||||
const headersList = headers();
|
||||
const currentUrl = headersList.get("referer") || headersList.get("x-invoke-path") || "";
|
||||
const environment = await getEnvironment(environmentId);
|
||||
if (!environment) {
|
||||
throw new Error("Environment not found");
|
||||
}
|
||||
|
||||
const environments = await getEnvironments(environment.productId);
|
||||
const otherEnvironmentId = environments.find((e) => e.id !== environment.id)?.id || "";
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
"@formbricks/surveys": "workspace:*",
|
||||
"@formbricks/lib": "workspace:*",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@lexical/code": "^0.12.2",
|
||||
"@lexical/link": "^0.12.2",
|
||||
"@lexical/list": "^0.12.2",
|
||||
"@lexical/markdown": "^0.12.2",
|
||||
"@lexical/react": "^0.12.2",
|
||||
"@lexical/rich-text": "^0.12.2",
|
||||
"@lexical/table": "^0.12.2",
|
||||
"@lexical/code": "^0.12.4",
|
||||
"@lexical/link": "^0.12.4",
|
||||
"@lexical/list": "^0.12.4",
|
||||
"@lexical/markdown": "^0.12.4",
|
||||
"@lexical/react": "^0.12.4",
|
||||
"@lexical/rich-text": "^0.12.4",
|
||||
"@lexical/table": "^0.12.4",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
@@ -40,7 +40,7 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cmdk": "^0.2.0",
|
||||
"lexical": "^0.12.2",
|
||||
"lexical": "^0.12.4",
|
||||
"lucide-react": "^0.292.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-confetti": "^6.1.0",
|
||||
|
||||
1472
pnpm-lock.yaml
generated
1472
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user