mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-04 09:29:42 -06:00
Clean up + Bugfixes (#443)
* format code * fix building issue in database package * update turbo config and package jsons * update error package with more information and license * fix typescript issues in ui package * update package-lock * update packages * clean dependencies in web * clean up dependencies in demo & formbricks-com * remove unsused template file * remove legacy capture endpoint * remove unfinished client endpoints * clean up unused functions * fix formbricks not loading on invalid session * update readme
This commit is contained in:
4
LICENSE
4
LICENSE
@@ -3,7 +3,7 @@ Copyright (c) 2023 Matthias Nannt, Johannes Dancker
|
||||
Portions of this software are licensed as follows:
|
||||
|
||||
- All content that resides under the "packages/ee/" directory of this repository, if that directory exists, is licensed under the license defined in "packages/ee/LICENSE".
|
||||
- All content that resides under the "packages/js/" directory of this repository, if that directory exists, is licensed under the "MIT" license as defined in "packages/js/LICENSE".
|
||||
- All content that resides under the "packages/js/", "packages/errors/" and "packages/api/" directories of this repository, if that directories exist, is licensed under the "MIT" license as defined in the "LICENSE" files of these packages.
|
||||
- All third party components incorporated into the Formbricks Software are licensed under the original license provided by the owner of the applicable component.
|
||||
- Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
|
||||
|
||||
@@ -67,7 +67,7 @@ modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
1. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
|
||||
88
README.md
88
README.md
@@ -12,67 +12,83 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/formbricks/formbricks/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPL-purple" alt="License"></a> <a href="https://formbricks.com/discord"><img src="https://img.shields.io/discord/979077669410979880?label=Discord&logo=discord&logoColor=%23fff" alt="Join Formbricks Discord"></a> <a href="https://github.com/formbricks/formbricks/stargazers"><img src="https://img.shields.io/github/stars/formbricks/formbricks?logo=github" alt="Github Stars"></a>
|
||||
<a href="https://github.com/formbricks/formbricks/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-AGPL-purple" alt="License"></a> <a href="https://formbricks.com/discord"><img src="https://img.shields.io/discord/979077669410979880?label=Discord&logo=discord&logoColor=%23fff" alt="Join Formbricks Discord"></a> <a href="https://github.com/formbricks/formbricks/stargazers"><img src="https://img.shields.io/github/stars/formbricks/formbricks?logo=github" alt="Github Stars"></a>
|
||||
<a href="https://news.ycombinator.com/item?id=32303986"><img src="https://img.shields.io/badge/Hacker%20News-122-%23FF6600" alt="Hacker News"></a>
|
||||
<a href="https://www.producthunt.com/products/snoopforms"><img src="https://img.shields.io/badge/Product%20Hunt-%232%20Product%20of%20the%20Day-orange?logo=producthunt&logoColor=%23fff" alt="Product Hunt"></a>
|
||||
<a href="https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next/"><img src="https://img.shields.io/badge/2023-blue?logo=github&label=Github%20Accelerator" alt="Github Accelerator"></a>
|
||||
<a href="https://github.com/formbricks/formbricks/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22"><img src="https://img.shields.io/badge/Help%20Wanted-Contribute-blue"></a>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
## About Formbricks
|
||||
<p align="center">
|
||||
<i>Trusted by</i>
|
||||
<a href="https://github.com/calcom/cal.com/"><img src="https://github.com/formbricks/formbricks/assets/675065/1a8763cf-f47e-4960-90f6-334f6dc12a17" height="20px"></a>
|
||||
<a href="https://github.com/CrowdDotDev/crowd.dev"><img src="https://github.com/formbricks/formbricks/assets/675065/59b1a4d4-25e4-4ef3-b0bf-4426446fbfd0" height="20px"></a>
|
||||
<a href="https://clovyr.io/"><img src="https://github.com/formbricks/formbricks/assets/675065/9291c8df-9aac-423a-a430-a9a581240075" height="20px"></a>
|
||||
<a href="https://neverinstall.com/"><img src="https://github.com/formbricks/formbricks/assets/675065/72e5e37b-8ef7-4340-b06e-f1d12a05330f" height="20px"></a>
|
||||
</p>
|
||||
|
||||
<img width="1527" alt="formbricks-sneak" src="https://user-images.githubusercontent.com/675065/227726212-6ebf930e-6a20-4ffa-b966-56cd41bdf363.png">
|
||||
## ✨ About Formbricks
|
||||
|
||||
Formbricks productizes best practices for qualitative in-app user discovery. Use micro-surveys to target the right users at the right time without making surveys annoying.
|
||||
<img width="1527" alt="formbricks-sneak" src="https://github-production-user-asset-6210df.s3.amazonaws.com/675065/249441967-ccb89ea3-82b4-4bf2-8d2c-528721ec313b.png">
|
||||
|
||||
Formbricks is your go-to solution for in-product micro-surveys that will supercharge your product experience. Use micro-surveys to target the right users at the right time without making surveys annoying.
|
||||
|
||||
**Try it out in the cloud at [formbricks.com](https://formbricks.com)**
|
||||
|
||||
### Mission: Base your decisions on qualitative data.
|
||||
## 💪 Mission: Make customer-centric decisions based on data.
|
||||
|
||||
Formbricks helps you apply best practices from data-driven work and experience management to make better business decisions. Use Formbricks to collect and manage insights from your users; run a product market fit survey to know which audience to focus on and whether your value proposition is being recognized.
|
||||
Formbricks helps you apply best practices from data-driven work and experience management to make better business decisions. Ask users as they experience your product - and leverage a significantly higher conversion rate. Gather all insights you can - including partial submissions and build conviction for the next product decision. Better data, better business.
|
||||
|
||||
### Features
|
||||
|
||||
- 📲 Create in-product surveys with our no code editor with multiple question types
|
||||
- 📚 Choose from a variety of best-practice templates
|
||||
- 👩🏻 Launch and target your surveys to specific user groups without changing your application code
|
||||
- 🔗 Create shareable link surveys
|
||||
- 👨👩👦 Invite your team members to collaborate on your surveys
|
||||
- 🔌 Integrate Formbricks with Slack, Posthog, Zapier and more
|
||||
- 🔒 All open source, transparent and self-hostable
|
||||
- 📲 Create **in-product surveys** with our no code editor with multiple question types
|
||||
- 📚 Choose from a variety of best-practice **templates**
|
||||
- 👩🏻 Launch and **target your surveys to specific user groups** without changing your application code
|
||||
- 🔗 Create shareable **link surveys**
|
||||
- 👨👩👦 Invite your team members to **collaborate** on your surveys
|
||||
- 🔌 Integrate Formbricks with **Slack, Posthog, Zapier and more**
|
||||
- 🔒 All **open source**, transparent and self-hostable
|
||||
|
||||
### Built With
|
||||
### Built on Open Source
|
||||
|
||||
- [Typescript](https://www.typescriptlang.org/)
|
||||
- [Next.js](https://nextjs.org/)
|
||||
- [React](https://reactjs.org/)
|
||||
- [TailwindCSS](https://tailwindcss.com/)
|
||||
- [Prisma](https://prisma.io/)
|
||||
- 💻 [Typescript](https://www.typescriptlang.org/)
|
||||
- 🚀 [Next.js](https://nextjs.org/)
|
||||
- ⚛️ [React](https://reactjs.org/)
|
||||
- 🎨 [TailwindCSS](https://tailwindcss.com/)
|
||||
- 📚 [Prisma](https://prisma.io/)
|
||||
- 🔒 [Auth.js](https://authjs.dev/)
|
||||
- 🧘♂️ [Zod](https://zod.dev/)
|
||||
|
||||
### Upcoming Features
|
||||
## 🚀 Getting started
|
||||
|
||||
| | Feature |
|
||||
| --- | ------------------------------------------ |
|
||||
| 👷 | Zapier, Slack & Posthog Integration |
|
||||
| 👷 | Webhooks |
|
||||
| 🗒️ | Filtering Options in Survey Analysis |
|
||||
| 🗒️ | Multi-Language Functionality |
|
||||
| 🗒️ | Auto-complete Surveys after at x responses |
|
||||
| 🗒️ | Pre-Fill Link-Surveys |
|
||||
| 🗒️ | E-Mail Surveys |
|
||||
### ☁️ Cloud Version
|
||||
|
||||
_👷 In Progress | 🗒️ Up Next_
|
||||
Formbricks has a hosted cloud offering with a generous free plan to get you up and running as quickly as possible. To get started, please visit [formbricks.com](https://formbricks.com)
|
||||
|
||||
## Cloud vs. self-hosted
|
||||
### 🐳 Self-hosted version
|
||||
|
||||
Formbricks is available Open-Source under AGPLv3 license. You can host Formbricks on your own servers without a subscription. Check out our [docs](https://formbricks.com/docs/self-hosting/deployment) to see how to self-host Formbricks.
|
||||
|
||||
We also have a hosted cloud offering with a generous free plan to get you up and running as quickly as possible. For more information, please visit [formbricks.com](https://formbricks.com)
|
||||
Formbricks is available Open-Source under AGPLv3 license. You can host Formbricks on your own servers using Docker without a subscription. To get started with self-hosting, take a look at our [self-hosting docs](https://formbricks.com/docs/self-hosting/deployment).
|
||||
|
||||
(In the future we may develop additional features that aren't in the free Open-Source version)
|
||||
|
||||
## Contributing
|
||||
## ✍️ Contribution
|
||||
|
||||
We are very happy if you are interested in contributing to Formbricks 🤗
|
||||
|
||||
There are many ways to contribute to Formbricks with writing Issues, fixing bugs, building new features or updating the docs. Please check out [our contribution guide](https://formbricks.com/docs/contributing/introduction) for more information.
|
||||
Here are a few options:
|
||||
|
||||
- Star this repo
|
||||
- Create issues every time you feel something is missing or goes wrong
|
||||
- Upvote issues with 👍 reaction so we know what's the demand for particular issue to prioritize it within roadmap
|
||||
|
||||
Please check out [our contribution guide](https://formbricks.com/docs/contributing/introduction) and our [list of open issues](https://github.com/formbricks/formbricks/issues) for more information.
|
||||
|
||||
## ⚖️ License
|
||||
|
||||
Distributed under the AGPLv3 License. See `LICENSE` for more information.
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
We take security very seriously. If you come across any security vulnerabilities, please disclose them by sending an email to security@formbricks.com. We appreciate your help in making our platform as secure as possible and are committed to working with you to resolve any issues quickly and efficiently. See `SECURITY.md` for more information.
|
||||
|
||||
@@ -12,22 +12,21 @@
|
||||
"dependencies": {
|
||||
"@formbricks/js": "workspace:*",
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@types/node": "18.15.11",
|
||||
"@types/react": "18.0.33",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"console-feed": "^3.5.0",
|
||||
"eslint": "8.37.0",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"next": "13.2.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"typescript": "5.0.3"
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@types/node": "18.15.11",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@types/react": "18.0.33",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.21",
|
||||
"rimraf": "^5.0.0",
|
||||
"tailwindcss": "^3.3.1"
|
||||
"tailwindcss": "^3.3.1",
|
||||
"typescript": "5.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -12,44 +12,44 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@calcom/embed-react": "^1.1.1",
|
||||
"@docsearch/react": "^3.3.3",
|
||||
"@docsearch/react": "^3.5.1",
|
||||
"@formbricks/lib": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"@headlessui/react": "^1.7.14",
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@headlessui/react": "^1.7.15",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@mapbox/rehype-prism": "^0.8.0",
|
||||
"@mdx-js/loader": "^2.3.0",
|
||||
"@mdx-js/react": "^2.3.0",
|
||||
"@next/mdx": "^13.3.0",
|
||||
"add": "^2.0.6",
|
||||
"@next/mdx": "^13.4.7",
|
||||
"@paralleldrive/cuid2": "^2.2.0",
|
||||
"clsx": "^1.2.1",
|
||||
"lottie-web": "^5.11.0",
|
||||
"next": "13.3.0",
|
||||
"next-plausible": "^3.7.2",
|
||||
"next-sitemap": "^4.0.7",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"lottie-web": "^5.12.2",
|
||||
"next": "13.4.7",
|
||||
"next-plausible": "^3.8.0",
|
||||
"next-sitemap": "^4.1.3",
|
||||
"prism-react-renderer": "^2.0.6",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-responsive-embed": "^2.1.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.32.0"
|
||||
"sharp": "^0.32.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/node": "18.15.11",
|
||||
"@types/node": "20.3.2",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@types/react": "^18.0.35",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react": "18.2.7",
|
||||
"@types/react-dom": "18.2.4",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"postcss": "^8.4.22",
|
||||
"rimraf": "^5.0.0",
|
||||
"tailwindcss": "^3.3.1",
|
||||
"typescript": "^5.0.4"
|
||||
"postcss": "^8.4.24",
|
||||
"rimraf": "^5.0.1",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "5.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
11
apps/web/app/ClientLogout.tsx
Normal file
11
apps/web/app/ClientLogout.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { signOut } from "next-auth/react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function ClientLogout() {
|
||||
useEffect(() => {
|
||||
signOut();
|
||||
});
|
||||
return null;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
THIS FILE IS WORK IN PROGRESS
|
||||
PLEASE DO NOT USE IT YET
|
||||
*/
|
||||
|
||||
import { responses } from "@/lib/api/response";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function OPTIONS(): Promise<NextResponse> {
|
||||
return responses.successResponse({}, true);
|
||||
}
|
||||
|
||||
export async function POST(request: Request): Promise<NextResponse> {
|
||||
const { sessionId, environmentId, eventName, properties } = await request.json();
|
||||
|
||||
if (!sessionId) {
|
||||
return responses.missingFieldResponse("sessionId", true);
|
||||
}
|
||||
|
||||
if (!environmentId) {
|
||||
return responses.missingFieldResponse("environmentId", true);
|
||||
}
|
||||
|
||||
if (!eventName) {
|
||||
return responses.missingFieldResponse("eventName", true);
|
||||
}
|
||||
|
||||
const action = await prisma.event.create({
|
||||
data: {
|
||||
properties,
|
||||
session: {
|
||||
connect: {
|
||||
id: sessionId,
|
||||
},
|
||||
},
|
||||
eventClass: {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
name_environmentId: {
|
||||
name: eventName,
|
||||
environmentId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
name: eventName,
|
||||
type: "code",
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
return responses.successResponse(action, true);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
THIS FILE IS WORK IN PROGRESS
|
||||
PLEASE DO NOT USE IT YET
|
||||
*/
|
||||
|
||||
import { createSession } from "@/lib/api/clientSession";
|
||||
import { getSettings } from "@/lib/api/clientSettings";
|
||||
import { responses } from "@/lib/api/response";
|
||||
import { captureTelemetry } from "@formbricks/lib/telemetry";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function OPTIONS(): Promise<NextResponse> {
|
||||
return responses.successResponse({}, true);
|
||||
}
|
||||
|
||||
export async function POST(request: Request): Promise<NextResponse> {
|
||||
const { personId, environmentId } = await request.json();
|
||||
|
||||
if (!personId) {
|
||||
return responses.missingFieldResponse("sessionId", true);
|
||||
}
|
||||
|
||||
if (!environmentId) {
|
||||
return responses.missingFieldResponse("environmentId", true);
|
||||
}
|
||||
|
||||
const session = await createSession(personId);
|
||||
const { surveys, noCodeEvents, brandColor } = await getSettings(environmentId, personId);
|
||||
|
||||
captureTelemetry("session created");
|
||||
|
||||
return responses.successResponse({ session, surveys, noCodeEvents, brandColor }, true);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
THIS FILE IS WORK IN PROGRESS
|
||||
PLEASE DO NOT USE IT YET
|
||||
*/
|
||||
|
||||
import { getSettings } from "@/lib/api/clientSettings";
|
||||
import { responses } from "@/lib/api/response";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { captureTelemetry } from "@formbricks/lib/telemetry";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function OPTIONS(): Promise<NextResponse> {
|
||||
return responses.successResponse({}, true);
|
||||
}
|
||||
|
||||
export async function POST(request: Request): Promise<NextResponse> {
|
||||
const { userCuid } = await request.json();
|
||||
|
||||
if (!userCuid) {
|
||||
return responses.missingFieldResponse("userCuid", true);
|
||||
}
|
||||
|
||||
// get user
|
||||
const user = await prisma.person.findUnique({
|
||||
where: {
|
||||
id: userCuid,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
environmentId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return responses.notFoundResponse("User", userCuid, true);
|
||||
}
|
||||
|
||||
const { surveys, noCodeEvents, brandColor } = await getSettings(user.environmentId, user.id);
|
||||
|
||||
captureTelemetry("session created");
|
||||
|
||||
return responses.successResponse({ surveys, noCodeEvents, brandColor }, true);
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
THIS FILE IS WORK IN PROGRESS
|
||||
PLEASE DO NOT USE IT YET
|
||||
*/
|
||||
|
||||
import { getSettings } from "@/lib/api/clientSettings";
|
||||
import { responses } from "@/lib/api/response";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function OPTIONS(): Promise<NextResponse> {
|
||||
return responses.successResponse({}, true);
|
||||
}
|
||||
|
||||
export async function POST(request: Request, params: { userCuid: string }): Promise<NextResponse> {
|
||||
const { userCuid } = params;
|
||||
const { key, value } = await request.json();
|
||||
|
||||
if (!key) {
|
||||
return responses.missingFieldResponse("key", true);
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return responses.missingFieldResponse("value", true);
|
||||
}
|
||||
|
||||
const currentPerson = await prisma.person.findUnique({
|
||||
where: {
|
||||
id: userCuid,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
environmentId: true,
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!currentPerson) {
|
||||
return responses.notFoundResponse("User", userCuid, true);
|
||||
}
|
||||
|
||||
const environmentId = currentPerson.environmentId;
|
||||
|
||||
// find attribute class
|
||||
let attributeClass = await prisma.attributeClass.findUnique({
|
||||
where: {
|
||||
name_environmentId: {
|
||||
name: key,
|
||||
environmentId,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
// create new attribute class if not found
|
||||
if (attributeClass === null) {
|
||||
attributeClass = await prisma.attributeClass.create({
|
||||
data: {
|
||||
name: key,
|
||||
type: "code",
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// upsert attribute (update or create)
|
||||
const attribute = await prisma.attribute.upsert({
|
||||
where: {
|
||||
attributeClassId_personId: {
|
||||
attributeClassId: attributeClass.id,
|
||||
personId: userCuid,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
value,
|
||||
},
|
||||
create: {
|
||||
attributeClass: {
|
||||
connect: {
|
||||
id: attributeClass.id,
|
||||
},
|
||||
},
|
||||
person: {
|
||||
connect: {
|
||||
id: userCuid,
|
||||
},
|
||||
},
|
||||
value,
|
||||
},
|
||||
select: {
|
||||
person: {
|
||||
select: {
|
||||
id: true,
|
||||
environmentId: true,
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const user = attribute.person;
|
||||
|
||||
const { surveys, noCodeEvents, brandColor } = await getSettings(environmentId, user.id);
|
||||
|
||||
return responses.successResponse({ user, surveys, noCodeEvents, brandColor }, true);
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
THIS FILE IS WORK IN PROGRESS
|
||||
PLEASE DO NOT USE IT YET
|
||||
*/
|
||||
|
||||
import { getSettings } from "@/lib/api/clientSettings";
|
||||
import { responses } from "@/lib/api/response";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function OPTIONS(): Promise<NextResponse> {
|
||||
return responses.successResponse({}, true);
|
||||
}
|
||||
|
||||
export async function POST(request: Request, params: { userCuid: string }): Promise<NextResponse> {
|
||||
const { userCuid } = params;
|
||||
const { userId, sessionId } = await request.json();
|
||||
|
||||
if (!userId) {
|
||||
return responses.missingFieldResponse("userId", true);
|
||||
}
|
||||
|
||||
if (!sessionId) {
|
||||
return responses.missingFieldResponse("sessionId", true);
|
||||
}
|
||||
|
||||
let returnedUser;
|
||||
|
||||
// find person
|
||||
const person = await prisma.person.findUnique({
|
||||
where: {
|
||||
id: userCuid,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
environmentId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!person) {
|
||||
return responses.notFoundResponse("User", userCuid, true);
|
||||
}
|
||||
|
||||
const environmentId = person.environmentId;
|
||||
|
||||
// check if person with this userId already exists
|
||||
const existingPerson = await prisma.person.findFirst({
|
||||
where: {
|
||||
environmentId,
|
||||
attributes: {
|
||||
some: {
|
||||
attributeClass: {
|
||||
name: "userId",
|
||||
},
|
||||
value: userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
environmentId: true,
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// if person exists, reconnect ression and delete old user
|
||||
if (existingPerson) {
|
||||
// reconnect session to new person
|
||||
await prisma.session.update({
|
||||
where: {
|
||||
id: sessionId,
|
||||
},
|
||||
data: {
|
||||
person: {
|
||||
connect: {
|
||||
id: existingPerson.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// delete old person
|
||||
await prisma.person.delete({
|
||||
where: {
|
||||
id: userCuid,
|
||||
},
|
||||
});
|
||||
returnedUser = existingPerson;
|
||||
} else {
|
||||
// update person
|
||||
returnedUser = await prisma.person.update({
|
||||
where: {
|
||||
id: userCuid,
|
||||
},
|
||||
data: {
|
||||
attributes: {
|
||||
create: {
|
||||
value: userId,
|
||||
attributeClass: {
|
||||
connect: {
|
||||
name_environmentId: {
|
||||
name: "userId",
|
||||
environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
environmentId: true,
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { surveys, noCodeEvents, brandColor } = await getSettings(environmentId, returnedUser.id);
|
||||
|
||||
return responses.successResponse({ user: returnedUser, surveys, noCodeEvents, brandColor }, true);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
THIS FILE IS WORK IN PROGRESS
|
||||
PLEASE DO NOT USE IT YET
|
||||
*/
|
||||
|
||||
import { createPerson } from "@/lib/api/clientPerson";
|
||||
import { createSession } from "@/lib/api/clientSession";
|
||||
import { getSettings } from "@/lib/api/clientSettings";
|
||||
import { responses } from "@/lib/api/response";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function OPTIONS(): Promise<NextResponse> {
|
||||
return responses.successResponse({}, true);
|
||||
}
|
||||
|
||||
export async function POST(request: Request): Promise<NextResponse> {
|
||||
const { environmentId } = await request.json();
|
||||
|
||||
if (!environmentId) {
|
||||
return responses.missingFieldResponse("environmentId", true);
|
||||
}
|
||||
|
||||
const user = await createPerson(environmentId);
|
||||
const session = await createSession(user.id);
|
||||
const { surveys, noCodeEvents, brandColor } = await getSettings(environmentId, user.id);
|
||||
|
||||
return responses.successResponse({ user, session, surveys, noCodeEvents, brandColor }, true);
|
||||
}
|
||||
@@ -32,7 +32,9 @@ export default function AudienceView({ environmentId, localSurvey, setLocalSurve
|
||||
environmentId={environmentId}
|
||||
/>
|
||||
|
||||
{localSurvey.type==="link" && <ResponseOptionsCard localSurvey={localSurvey} setLocalSurvey={setLocalSurvey} />}
|
||||
{localSurvey.type === "link" && (
|
||||
<ResponseOptionsCard localSurvey={localSurvey} setLocalSurvey={setLocalSurvey} />
|
||||
)}
|
||||
|
||||
<RecontactOptionsCard
|
||||
localSurvey={localSurvey}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import Modal from "components/shared/Modal";
|
||||
|
||||
interface SurveyCreatedSuccessModalProps {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export default function SurveyCreatedSuccessModal({ open, setOpen }: SurveyCreatedSuccessModalProps) {
|
||||
return (
|
||||
<>
|
||||
<Modal open={open} setOpen={setOpen} title="Survey published 🎉">
|
||||
Your survey is live and collecting valuable insights!
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { createSurvey } from "@/lib/surveys/surveys";
|
||||
import type { Template } from "@formbricks/types/templates";
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/solid";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
interface TemplateMenuBarProps {
|
||||
activeTemplate: Template | null;
|
||||
environmentId: string;
|
||||
}
|
||||
|
||||
export default function TemplateMenuBar({ activeTemplate, environmentId }: TemplateMenuBarProps) {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const addSurvey = async (activeTemplate) => {
|
||||
setLoading(true);
|
||||
const survey = await createSurvey(environmentId, activeTemplate.preset);
|
||||
router.push(`/environments/${environmentId}/surveys/${survey.id}/edit`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border-b border-slate-200 bg-white px-5 py-3 sm:flex sm:items-center sm:justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Button variant="minimal" className="px-0" onClick={() => router.back()}>
|
||||
<ArrowLeftIcon className="h-5 w-5 text-slate-700" />
|
||||
</Button>
|
||||
<h1 className="font-slate-700 text-lg font-semibold">Start with a template</h1>
|
||||
</div>
|
||||
<div className="mt-3 flex sm:ml-4 sm:mt-0">
|
||||
<Button
|
||||
variant="highlight"
|
||||
disabled={activeTemplate === null}
|
||||
loading={loading}
|
||||
onClick={() => addSurvey(activeTemplate)}
|
||||
EndIcon={ArrowRightIcon}>
|
||||
Create Survey
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,12 +6,9 @@ import { useProduct } from "@/lib/products/products";
|
||||
import { replacePresetPlaceholders } from "@/lib/templates";
|
||||
import type { Template } from "@formbricks/types/templates";
|
||||
import { ErrorComponent } from "@formbricks/ui";
|
||||
/* import { PaintBrushIcon } from "@heroicons/react/24/solid";
|
||||
import Link from "next/link"; */
|
||||
import { useEffect, useState } from "react";
|
||||
import PreviewSurvey from "../PreviewSurvey";
|
||||
import TemplateList from "./TemplateList";
|
||||
/* import TemplateMenuBar from "./TemplateMenuBar"; */
|
||||
import { templates } from "./templates";
|
||||
|
||||
export default function SurveyTemplatesPage({ params }) {
|
||||
@@ -36,7 +33,6 @@ export default function SurveyTemplatesPage({ params }) {
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col ">
|
||||
{/* <TemplateMenuBar activeTemplate={activeTemplate} environmentId={environmentId} /> */}
|
||||
<div className="relative z-0 flex flex-1 overflow-hidden">
|
||||
<div className="flex-1 flex-col overflow-auto bg-slate-50">
|
||||
<h1 className="ml-6 mt-6 text-2xl font-bold text-slate-800">Create a new survey</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { WEBAPP_URL } from "@/../../packages/lib/constants";
|
||||
import ClientLogout from "@/app/ClientLogout";
|
||||
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
|
||||
import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import type { Session } from "next-auth";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { headers } from "next/headers";
|
||||
@@ -14,6 +15,8 @@ async function getEnvironment() {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await res.json();
|
||||
console.error(error);
|
||||
throw new Error("Failed to fetch data");
|
||||
}
|
||||
|
||||
@@ -31,10 +34,16 @@ export default async function Home() {
|
||||
return redirect(`/onboarding`);
|
||||
}
|
||||
|
||||
const environment = await getEnvironment();
|
||||
let environment;
|
||||
try {
|
||||
environment = await getEnvironment();
|
||||
} catch (error) {
|
||||
console.error("error getting environment", error);
|
||||
}
|
||||
|
||||
if (!environment) {
|
||||
throw Error("No environment found for user");
|
||||
console.error("Failed to get first environment of user; signing out");
|
||||
return <ClientLogout />;
|
||||
}
|
||||
|
||||
return redirect(`/environments/${environment.id}`);
|
||||
|
||||
@@ -460,5 +460,3 @@ export const GrinningSquintingFace: React.FC<React.SVGProps<SVGCircleElement>> =
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export let icons = [<svg viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg"></svg>];
|
||||
|
||||
@@ -21,8 +21,8 @@ export const SignupForm = () => {
|
||||
const handleSubmit = async (e: any) => {
|
||||
e.preventDefault();
|
||||
|
||||
if(!isValid){
|
||||
return
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
setSigningUp(true);
|
||||
try {
|
||||
@@ -48,8 +48,8 @@ export const SignupForm = () => {
|
||||
const [isButtonEnabled, setButtonEnabled] = useState(true);
|
||||
const [isPasswordFocused, setIsPasswordFocused] = useState(false);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [password, setPassword] = useState<string|null>(null)
|
||||
const [isValid, setIsValid] = useState(false)
|
||||
const [password, setPassword] = useState<string | null>(null);
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
|
||||
const checkFormValidity = () => {
|
||||
// If all fields are filled, enable the button
|
||||
@@ -145,7 +145,7 @@ export const SignupForm = () => {
|
||||
)}
|
||||
<Button
|
||||
onClick={(e: any) => {
|
||||
e.preventDefault()
|
||||
e.preventDefault();
|
||||
if (!showLogin) {
|
||||
setShowLogin(true);
|
||||
setButtonEnabled(false);
|
||||
@@ -158,7 +158,7 @@ export const SignupForm = () => {
|
||||
variant="darkCTA"
|
||||
className="w-full justify-center"
|
||||
loading={signingUp}
|
||||
disabled={formRef.current ? (!isButtonEnabled || !isValid): !isButtonEnabled}>
|
||||
disabled={formRef.current ? !isButtonEnabled || !isValid : !isButtonEnabled}>
|
||||
Continue with Email
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@@ -1,33 +1,30 @@
|
||||
import React from 'react'
|
||||
import { useEffect,useState } from 'react'
|
||||
import React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function RedirectCountDown({
|
||||
initiateCountdown
|
||||
}:{
|
||||
initiateCountdown:boolean|undefined
|
||||
}) {
|
||||
export default function RedirectCountDown({ initiateCountdown }: { initiateCountdown: boolean | undefined }) {
|
||||
const [timeRemaining, setTimeRemaining] = useState(3);
|
||||
|
||||
const [timeRemaining, setTimeRemaining] = useState(3)
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setTimeRemaining((prevTime) => prevTime - 1);
|
||||
}, 1000);
|
||||
|
||||
if (timeRemaining === 0) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
// Clean up the interval when the component is unmounted
|
||||
return () => clearInterval(interval);
|
||||
}, [timeRemaining]);
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setTimeRemaining((prevTime) => prevTime - 1);
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{initiateCountdown && <div className="rounded-md bg-slate-100 p-2 text-sm mt-10">
|
||||
<span>You're redirected in </span>
|
||||
<span>{timeRemaining}</span>
|
||||
</div>}
|
||||
if (timeRemaining === 0) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
// Clean up the interval when the component is unmounted
|
||||
return () => clearInterval(interval);
|
||||
}, [timeRemaining]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{initiateCountdown && (
|
||||
<div className="mt-10 rounded-md bg-slate-100 p-2 text-sm">
|
||||
<span>You're redirected in </span>
|
||||
<span>{timeRemaining}</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,12 @@ interface ThankYouCardProps {
|
||||
initiateCountdown?: boolean;
|
||||
}
|
||||
|
||||
export default function ThankYouCard({ headline, subheader, brandColor,initiateCountdown }: ThankYouCardProps) {
|
||||
export default function ThankYouCard({
|
||||
headline,
|
||||
subheader,
|
||||
brandColor,
|
||||
initiateCountdown,
|
||||
}: ThankYouCardProps) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center" style={{ color: brandColor }}>
|
||||
@@ -33,7 +38,7 @@ export default function ThankYouCard({ headline, subheader, brandColor,initiateC
|
||||
<div>
|
||||
<Headline headline={headline} questionId="thankYouCard" />
|
||||
<Subheader subheader={subheader} questionId="thankYouCard" />
|
||||
<RedirectCountDown initiateCountdown={initiateCountdown}/>
|
||||
<RedirectCountDown initiateCountdown={initiateCountdown} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import clsx from "clsx";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
|
||||
interface ContentWrapperProps {
|
||||
children: React.ReactNode;
|
||||
@@ -6,5 +6,5 @@ interface ContentWrapperProps {
|
||||
}
|
||||
|
||||
export default function ContentWrapper({ children, className }: ContentWrapperProps) {
|
||||
return <div className={clsx("mx-auto max-w-7xl p-6", className)}>{children}</div>;
|
||||
return <div className={cn("mx-auto max-w-7xl p-6", className)}>{children}</div>;
|
||||
}
|
||||
|
||||
@@ -12,29 +12,6 @@ export const useApiKeys = (environmentId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useApiKey = (environmentId: string, id: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/v1/environments/${environmentId}/api-keys/${id}`, fetcher);
|
||||
|
||||
return {
|
||||
apiKey: data,
|
||||
isLoadingApiKey: !error && !data,
|
||||
isErrorApiKey: error,
|
||||
mutateApiKey: mutate,
|
||||
};
|
||||
};
|
||||
|
||||
export const persistApiKey = async (environmentId: string, apiKey) => {
|
||||
try {
|
||||
await fetch(`/api/v1/environments/${environmentId}/api-keys/${apiKey.id}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(apiKey),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const createApiKey = async (environmentId: string, apiKey = {}) => {
|
||||
try {
|
||||
const res = await fetch(`/api/v1/environments/${environmentId}/api-keys`, {
|
||||
|
||||
@@ -9,20 +9,3 @@ export async function verifyPassword(password: string, hashedPassword: string) {
|
||||
const isValid = await compare(password, hashedPassword);
|
||||
return isValid;
|
||||
}
|
||||
export function requireAuthentication(gssp: any) {
|
||||
return async (context: any) => {
|
||||
const { req, resolvedUrl } = context;
|
||||
const token = req.cookies.userToken;
|
||||
|
||||
if (!token) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: `/auth/login?callbackUrl=${encodeURIComponent(resolvedUrl)}`,
|
||||
statusCode: 302,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return await gssp(context); // Continue on to call `getServerSideProps` logic
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ interface sendEmailData {
|
||||
html: string;
|
||||
}
|
||||
|
||||
export const sendEmail = async (emailData: sendEmailData) => {
|
||||
const sendEmail = async (emailData: sendEmailData) => {
|
||||
let transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: process.env.SMTP_PORT,
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
import platform from "platform";
|
||||
|
||||
export function capitalizeFirstLetter(string = "") {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
export const onlyUnique = (value, index, self) => {
|
||||
return self.indexOf(value) === index;
|
||||
};
|
||||
|
||||
export const parseUserAgent = (userAgent: string) => {
|
||||
const info = platform.parse(userAgent);
|
||||
return info.description;
|
||||
};
|
||||
|
||||
// write a function that takes a string and truncates it to the specified length
|
||||
export const truncate = (str: string, length: number) => {
|
||||
if (!str) return "";
|
||||
@@ -53,12 +42,3 @@ export function isLight(color) {
|
||||
}
|
||||
return r * 0.299 + g * 0.587 + b * 0.114 > 128;
|
||||
}
|
||||
|
||||
export const toJson = (obj: any): Object | null => {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,31 +15,22 @@
|
||||
"@formbricks/js": "workspace:*",
|
||||
"@formbricks/lib": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"@headlessui/react": "^1.7.15",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@json2csv/node": "^7.0.1",
|
||||
"@paralleldrive/cuid2": "^2.2.0",
|
||||
"@radix-ui/react-collapsible": "^1.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
||||
"@types/node": "20.2.3",
|
||||
"@types/react": "18.2.7",
|
||||
"@types/react-dom": "18.2.4",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"class-variance-authority": "^0.6.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"dotenv-webpack": "^8.0.1",
|
||||
"eslint": "8.41.0",
|
||||
"eslint-config-next": "^13.4.3",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.221.0",
|
||||
"markdown-it": "^13.0.1",
|
||||
"next": "13.4.3",
|
||||
"next-auth": "^4.22.1",
|
||||
"nodemailer": "^6.9.2",
|
||||
"platform": "^1.3.6",
|
||||
"posthog-js": "^1.57.3",
|
||||
"posthog-node": "^3.1.1",
|
||||
"preact": "10.15.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
@@ -47,10 +38,7 @@
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-use": "^17.4.0",
|
||||
"stripe": "^12.6.0",
|
||||
"swr": "^2.1.5",
|
||||
"typescript": "5.0.4",
|
||||
"ua-parser-js": "^1.0.35",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
@@ -63,12 +51,14 @@
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/lodash": "^4.14.195",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/node": "20.2.3",
|
||||
"@types/react": "18.2.7",
|
||||
"@types/react-dom": "18.2.4",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"postcss": "^8.4.23",
|
||||
"rimraf": "^5.0.1",
|
||||
"tailwind-merge": "^1.12.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tailwindcss-animate": "^1.0.5"
|
||||
"typescript": "5.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const formId = req.query.formId?.toString();
|
||||
|
||||
// CORS
|
||||
if (req.method === "OPTIONS") {
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
// POST/capture/forms/[formId]/schema
|
||||
// Update form schema
|
||||
// Required fields in body: -
|
||||
// Optional fields in body: customerId, data
|
||||
else if (req.method === "POST") {
|
||||
if (process.env.FORMBRICKS_LEGACY_HOST) {
|
||||
const response = await fetch(
|
||||
`${process.env.FORMBRICKS_LEGACY_HOST}/api/capture/forms/${formId}/schema`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${process.env.FORMBRICKS_LEGACY_HOST}`,
|
||||
},
|
||||
body: JSON.stringify(req.body),
|
||||
}
|
||||
);
|
||||
const responseData = await response.json();
|
||||
res.json(responseData);
|
||||
} else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const formId = req.query.formId?.toString();
|
||||
const submissionId = req.query.submissionId?.toString();
|
||||
|
||||
// CORS
|
||||
if (req.method === "OPTIONS") {
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
// PUT /capture/forms/[formId]/submissions/[submissionId]
|
||||
// Extend an existing form submission
|
||||
// Required fields in body: -
|
||||
// Optional fields in body: customerId, data
|
||||
else if (req.method === "PUT") {
|
||||
// redirect request and make a request to legacy.formbricks.com
|
||||
if (process.env.FORMBRICKS_LEGACY_HOST) {
|
||||
const response = await fetch(
|
||||
`${process.env.FORMBRICKS_LEGACY_HOST}/api/capture/forms/${formId}/submissions/${submissionId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${process.env.FORMBRICKS_LEGACY_HOST}`,
|
||||
},
|
||||
body: JSON.stringify(req.body),
|
||||
}
|
||||
);
|
||||
const responseData = await response.json();
|
||||
res.json(responseData);
|
||||
} else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const formId = req.query.formId?.toString();
|
||||
|
||||
// CORS
|
||||
if (req.method === "OPTIONS") {
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
// POST/capture/forms/[formId]/submissions
|
||||
// Create a new form submission
|
||||
// Required fields in body: -
|
||||
// Optional fields in body: customerId, data
|
||||
else if (req.method === "POST") {
|
||||
if (process.env.FORMBRICKS_LEGACY_HOST) {
|
||||
const response = await fetch(
|
||||
`${process.env.FORMBRICKS_LEGACY_HOST}/api/capture/forms/${formId}/submissions`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${process.env.FORMBRICKS_LEGACY_HOST}`,
|
||||
},
|
||||
body: JSON.stringify(req.body),
|
||||
}
|
||||
);
|
||||
const responseData = await response.json();
|
||||
res.json(responseData);
|
||||
} else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const environmentId = req.query?.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
// POST
|
||||
if (req.method === "POST") {
|
||||
// lastSyncedAt is the last time the environment was synced (iso string)
|
||||
const { lastSyncedAt } = req.body;
|
||||
|
||||
let lastSyncedCondition = lastSyncedAt
|
||||
? {
|
||||
OR: [
|
||||
{
|
||||
createdAt: {
|
||||
gt: lastSyncedAt,
|
||||
},
|
||||
},
|
||||
{
|
||||
updatedAt: {
|
||||
gt: lastSyncedAt,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {};
|
||||
|
||||
// Get all displays that have been created or updated since lastSyncedAt
|
||||
const displays = await prisma.display.findMany({
|
||||
where: {
|
||||
survey: {
|
||||
environmentId,
|
||||
},
|
||||
...lastSyncedCondition,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
person: {
|
||||
select: {
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Get all responses that have been created or updated since lastSyncedAt
|
||||
const responses = await prisma.response.findMany({
|
||||
where: {
|
||||
survey: {
|
||||
environmentId,
|
||||
},
|
||||
...lastSyncedCondition,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
person: {
|
||||
select: {
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const events = [
|
||||
...displays.map((display) => ({
|
||||
name: "formbricks_survey_displayed",
|
||||
timestamp: display.createdAt,
|
||||
userId: display.person?.attributes?.find((attr) => attr.attributeClass.name === "userId")?.value,
|
||||
})),
|
||||
...responses.map((response) => ({
|
||||
name: "formbricks_response_created",
|
||||
timestamp: response.createdAt,
|
||||
userId: response.person?.attributes?.find((attr) => attr.attributeClass.name === "userId")?.value,
|
||||
})),
|
||||
];
|
||||
|
||||
return res.json({ events, lastSyncedAt: new Date().toISOString() });
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
interface FormbricksUser {
|
||||
userId: string;
|
||||
attributes: { [key: string]: string };
|
||||
}
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const environmentId = req.query?.environmentId?.toString();
|
||||
|
||||
if (!environmentId) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
// POST
|
||||
if (req.method === "POST") {
|
||||
// lastSyncedAt is the last time the environment was synced (iso string)
|
||||
const { users }: { users: FormbricksUser[] } = req.body;
|
||||
|
||||
for (const user of users) {
|
||||
// check if user with this userId as attribute already exists
|
||||
const existingUser = await prisma.person.findFirst({
|
||||
where: {
|
||||
attributes: {
|
||||
some: {
|
||||
attributeClass: {
|
||||
name: "userId",
|
||||
environmentId,
|
||||
},
|
||||
value: user.userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
attributes: {
|
||||
select: {
|
||||
id: true,
|
||||
value: true,
|
||||
attributeClass: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
// user already exists, loop through attributes and update or create them
|
||||
const attributeType: "noCode" = "noCode";
|
||||
for (const key of Object.keys(user.attributes)) {
|
||||
const existingAttribute = existingUser.attributes.find(
|
||||
(attribute) => attribute.attributeClass.name === key
|
||||
);
|
||||
if (existingAttribute) {
|
||||
// skip if value is the same
|
||||
if (existingAttribute.value === user.attributes[key]) {
|
||||
continue;
|
||||
}
|
||||
await prisma.attribute.update({
|
||||
where: {
|
||||
id: existingAttribute.id,
|
||||
},
|
||||
data: {
|
||||
value: user.attributes[key].toString(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// create attribute
|
||||
await prisma.attribute.create({
|
||||
data: {
|
||||
value: user.attributes[key],
|
||||
attributeClass: {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
name_environmentId: {
|
||||
name: key,
|
||||
environmentId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
name: key,
|
||||
description: "Created by Posthog Import",
|
||||
type: attributeType,
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
person: {
|
||||
connect: {
|
||||
id: existingUser.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
version: "3.3"
|
||||
services:
|
||||
postgres:
|
||||
restart: unless-stopped
|
||||
restart: always
|
||||
image: postgres:15-alpine
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
@@ -9,7 +9,7 @@ services:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
|
||||
formbricks:
|
||||
restart: unless-stopped
|
||||
restart: always
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./apps/web/Dockerfile
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@formbricks/tsconfig/js-library.json",
|
||||
"include": ["**/*.ts", "tsup.config.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -24,21 +24,21 @@
|
||||
"studio": "prisma studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@prisma/client": "^4.15.0",
|
||||
"prisma-json-types-generator": "^2.4.0",
|
||||
"@prisma/client": "^4.16.1",
|
||||
"prisma-json-types-generator": "^2.5.0",
|
||||
"zod": "^3.21.4",
|
||||
"zod-prisma": "^0.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"eslint": "^8.41.0",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"eslint": "^8.43.0",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"prisma": "^4.15.0",
|
||||
"prisma": "^4.16.1",
|
||||
"prisma-dbml-generator": "^0.10.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"tsup": "^6.7.0",
|
||||
"tsup": "^7.1.0",
|
||||
"tsx": "^3.12.7",
|
||||
"typescript": "^5.0.4"
|
||||
"typescript": "^5.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ datasource db {
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["filteredRelationCount", "extendedWhereUnique"]
|
||||
previewFeatures = ["extendedWhereUnique"]
|
||||
//provider = "prisma-dbml-generator"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@formbricks/tsconfig/node16.json",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "tsup.config.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": ["node_modules", "dist", "zod"]
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/database": "workspace:*",
|
||||
"next": "^13.4.4"
|
||||
"next": "^13.4.4",
|
||||
"stripe": "^12.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
9
packages/errors/LICENSE
Normal file
9
packages/errors/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Matthias Nannt, Johannes Dancker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@formbricks/errors",
|
||||
"license": "MIT",
|
||||
"description": "A helper package containing general error classes for Formbricks",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"version": "0.1.0",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -15,8 +15,8 @@
|
||||
"dev": "tsup --watch",
|
||||
"lint": "eslint ./src --fix",
|
||||
"clean": "rimraf .turbo node_modules dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@formbricks/tsconfig/node16.json",
|
||||
"include": ["**/*.ts", "tsup.config.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf .turbo node_modules dist",
|
||||
"dev": "microbundle --css inline",
|
||||
"dev": "microbundle --css inline --watch",
|
||||
"lint": "eslint '{src,tests}/**/*.{ts,tsx}'",
|
||||
"test": "jest",
|
||||
"build": "microbundle --css inline"
|
||||
|
||||
@@ -58,5 +58,6 @@
|
||||
"references": [
|
||||
{ "path": "../../../types/tsconfig.json" } // Add this line, adjust the path to the actual location
|
||||
],
|
||||
"include": ["src", "../types", "../lib/client"]
|
||||
"include": ["src", "../types", "../lib/client"],
|
||||
"exclude": ["node_modules", "dist", "coverage"]
|
||||
}
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
"dependencies": {
|
||||
"@formbricks/database": "*",
|
||||
"@formbricks/types": "*",
|
||||
"@formbricks/errors": "*"
|
||||
"@formbricks/errors": "*",
|
||||
"date-fns": "^2.30.0",
|
||||
"markdown-it": "^13.0.1",
|
||||
"posthog-node": "^3.1.1",
|
||||
"tailwind-merge": "^1.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "*",
|
||||
|
||||
@@ -25,9 +25,8 @@ const PasswordInput = ({ className, ...rest }: PasswordInputProps) => {
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className={cn("absolute top-1/2 right-3 transform -translate-y-1/2")}
|
||||
onClick={togglePasswordVisibility}
|
||||
>
|
||||
className={cn("absolute right-3 top-1/2 -translate-y-1/2 transform")}
|
||||
onClick={togglePasswordVisibility}>
|
||||
{showPassword ? (
|
||||
<EyeSlashIcon className="h-5 w-5 text-slate-400 " />
|
||||
) : (
|
||||
@@ -39,4 +38,3 @@ const PasswordInput = ({ className, ...rest }: PasswordInputProps) => {
|
||||
};
|
||||
|
||||
export { PasswordInput };
|
||||
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
},
|
||||
"types": "./index.tsx",
|
||||
"scripts": {
|
||||
"build": "tsup index.tsx --format esm,cjs --dts --external react",
|
||||
"clean": "rimraf .turbo node_modules dist",
|
||||
"dev": "tsup index.tsx --format esm,cjs --dts --external react --watch"
|
||||
"clean": "rimraf .turbo node_modules dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
@@ -24,7 +22,6 @@
|
||||
"postcss": "^8.4.24",
|
||||
"react": "^18.2.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"tsup": "^6.7.0",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -47,6 +44,7 @@
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.6",
|
||||
"boring-avatars": "^1.7.0",
|
||||
"class-variance-authority": "^0.6.0",
|
||||
"clsx": "^1.2.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"lucide-react": "^0.233.0",
|
||||
|
||||
1827
pnpm-lock.yaml
generated
1827
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**", ".next/**"],
|
||||
"env": [
|
||||
"FORMBRICKS_LEGACY_HOST",
|
||||
"GITHUB_ID",
|
||||
"GITHUB_SECRET",
|
||||
"GOOGLE_CLIENT_ID",
|
||||
@@ -80,7 +79,8 @@
|
||||
"outputs": []
|
||||
},
|
||||
"dev": {
|
||||
"cache": false
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"start": {
|
||||
"outputs": []
|
||||
|
||||
Reference in New Issue
Block a user