mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 18:30:32 -06:00
Merge branch 'formbricks:main' into gitpod-doc
This commit is contained in:
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -31,9 +31,10 @@ Fixes # (issue)
|
||||
|
||||
<!-- We're starting to get more and more contributions. Please help us making this efficient for all of us and go through this checklist. Please tick off what you did -->
|
||||
|
||||
- [ ] Added a screen recording or screenshots to this PR
|
||||
### Required
|
||||
|
||||
- [ ] Filled out the "How to test" section in this PR
|
||||
- [ ] Read the [contributing guide](https://github.com/formbricks/formbricks/blob/main/CONTRIBUTING.md)
|
||||
- [ ] Read [How we Code at Formbricks](<[https://github.com/formbricks/formbricks/blob/main/CONTRIBUTING.md](https://formbricks.com/docs/contributing/how-we-code)>)
|
||||
- [ ] Self-reviewed my own code
|
||||
- [ ] Commented on my code in hard-to-understand bits
|
||||
- [ ] Ran `pnpm build`
|
||||
@@ -41,4 +42,8 @@ Fixes # (issue)
|
||||
- [ ] Removed all `console.logs`
|
||||
- [ ] Merged the latest changes from main onto my branch with `git pull origin main`
|
||||
- [ ] My changes don't cause any responsiveness issues
|
||||
|
||||
### Appreciated
|
||||
|
||||
- [ ] If a UI change was made: Added a screen recording or screenshots to this PR
|
||||
- [ ] Updated the Formbricks Docs if changes were necessary
|
||||
|
||||
@@ -6,82 +6,121 @@ import n8nLogo from "@/images/n8n.png";
|
||||
import MakeLogo from "@/images/make-small.png";
|
||||
import { Card } from "@formbricks/ui";
|
||||
import Image from "next/image";
|
||||
import { getCountOfWebhooksBasedOnSource } from "@formbricks/lib/services/webhook";
|
||||
import { getEnvironment } from "@formbricks/lib/services/environment";
|
||||
import { getIntegrations } from "@formbricks/lib/services/integrations";
|
||||
|
||||
export default async function IntegrationsPage({ params }) {
|
||||
const integrations = await getIntegrations(params.environmentId);
|
||||
const environmentId = params.environmentId;
|
||||
|
||||
const [environment, integrations, userWebhooks, zapierWebhooks] = await Promise.all([
|
||||
getEnvironment(environmentId),
|
||||
getIntegrations(environmentId),
|
||||
getCountOfWebhooksBasedOnSource(environmentId, "user"),
|
||||
getCountOfWebhooksBasedOnSource(environmentId, "zapier"),
|
||||
]);
|
||||
|
||||
const containsGoogleSheetIntegration = integrations.some(
|
||||
(integration) => integration.type === "googleSheets"
|
||||
);
|
||||
|
||||
const integrationCards = [
|
||||
{
|
||||
docsHref: "https://formbricks.com/docs/getting-started/framework-guides#next-js",
|
||||
docsText: "Docs",
|
||||
docsNewTab: true,
|
||||
label: "Javascript Widget",
|
||||
description: "Integrate Formbricks into your Webapp",
|
||||
icon: <Image src={JsLogo} alt="Javascript Logo" />,
|
||||
connected: environment?.widgetSetupCompleted,
|
||||
statusText: environment?.widgetSetupCompleted ? "Connected" : "Not Connected",
|
||||
},
|
||||
{
|
||||
docsHref: "https://formbricks.com/docs/integrations/zapier",
|
||||
docsText: "Docs",
|
||||
docsNewTab: true,
|
||||
connectHref: "https://zapier.com/apps/formbricks/integrations",
|
||||
connectText: "Connect",
|
||||
connectNewTab: true,
|
||||
label: "Zapier",
|
||||
description: "Integrate Formbricks with 5000+ apps via Zapier",
|
||||
icon: <Image src={ZapierLogo} alt="Zapier Logo" />,
|
||||
connected: zapierWebhooks > 0,
|
||||
statusText:
|
||||
zapierWebhooks === 1 ? "1 zap" : zapierWebhooks === 0 ? "Not Connected" : `${zapierWebhooks} zaps`,
|
||||
},
|
||||
{
|
||||
connectHref: `/environments/${params.environmentId}/integrations/webhooks`,
|
||||
connectText: "Manage Webhooks",
|
||||
connectNewTab: false,
|
||||
docsHref: "https://formbricks.com/docs/webhook-api/overview",
|
||||
docsText: "Docs",
|
||||
docsNewTab: true,
|
||||
label: "Webhooks",
|
||||
description: "Trigger Webhooks based on actions in your surveys",
|
||||
icon: <Image src={WebhookLogo} alt="Webhook Logo" />,
|
||||
connected: userWebhooks > 0,
|
||||
statusText:
|
||||
userWebhooks === 1 ? "1 webhook" : userWebhooks === 0 ? "Not Connected" : `${userWebhooks} zaps`,
|
||||
},
|
||||
{
|
||||
connectHref: `/environments/${params.environmentId}/integrations/google-sheets`,
|
||||
connectText: `${containsGoogleSheetIntegration ? "Manage Sheets" : "Connect"}`,
|
||||
connectNewTab: false,
|
||||
docsHref: "https://formbricks.com/docs/integrations/google-sheets",
|
||||
docsText: "Docs",
|
||||
docsNewTab: true,
|
||||
label: "Google Sheets",
|
||||
description: "Instantly populate your spreadsheets with survey data",
|
||||
icon: <Image src={GoogleSheetsLogo} alt="Google sheets Logo" />,
|
||||
connected: containsGoogleSheetIntegration ? true : false,
|
||||
statusText: containsGoogleSheetIntegration ? "Connected" : "Not Connected",
|
||||
},
|
||||
{
|
||||
docsHref: "https://formbricks.com/docs/integrations/n8n",
|
||||
docsText: "Docs",
|
||||
docsNewTab: true,
|
||||
connectHref: "https://n8n.io",
|
||||
connectText: "Connect",
|
||||
connectNewTab: true,
|
||||
label: "n8n",
|
||||
description: "Integrate Formbricks with 350+ apps via n8n",
|
||||
icon: <Image src={n8nLogo} alt="n8n Logo" />,
|
||||
},
|
||||
{
|
||||
docsHref: "https://formbricks.com/docs/integrations/make",
|
||||
docsText: "Docs",
|
||||
docsNewTab: true,
|
||||
connectHref: "https://www.make.com/en/integrations/formbricks",
|
||||
connectText: "Connect",
|
||||
connectNewTab: true,
|
||||
label: "Make.com",
|
||||
description: "Integrate Formbricks with 1000+ apps via Make",
|
||||
icon: <Image src={MakeLogo} alt="Make Logo" />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="my-2 text-3xl font-bold text-slate-800">Integrations</h1>
|
||||
<p className="mb-6 text-slate-500">Connect Formbricks with your favorite tools.</p>
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
<Card
|
||||
docsHref="https://formbricks.com/docs/getting-started/framework-guides#next-js"
|
||||
docsText="Docs"
|
||||
docsNewTab={true}
|
||||
label="Javascript Widget"
|
||||
description="Integrate Formbricks into your Webapp"
|
||||
icon={<Image src={JsLogo} alt="Javascript Logo" />}
|
||||
/>
|
||||
<Card
|
||||
docsHref="https://formbricks.com/docs/integrations/zapier"
|
||||
docsText="Docs"
|
||||
docsNewTab={true}
|
||||
connectHref="https://zapier.com/apps/formbricks/integrations"
|
||||
connectText="Connect"
|
||||
connectNewTab={true}
|
||||
label="Zapier"
|
||||
description="Integrate Formbricks with 5000+ apps via Zapier"
|
||||
icon={<Image src={ZapierLogo} alt="Zapier Logo" />}
|
||||
/>
|
||||
<Card
|
||||
connectHref={`/environments/${params.environmentId}/integrations/webhooks`}
|
||||
connectText="Manage Webhooks"
|
||||
connectNewTab={false}
|
||||
docsHref="https://formbricks.com/docs/webhook-api/overview"
|
||||
docsText="Docs"
|
||||
docsNewTab={true}
|
||||
label="Webhooks"
|
||||
description="Trigger Webhooks based on actions in your surveys"
|
||||
icon={<Image src={WebhookLogo} alt="Webhook Logo" />}
|
||||
/>
|
||||
<Card
|
||||
connectHref={`/environments/${params.environmentId}/integrations/google-sheets`}
|
||||
connectText={`${containsGoogleSheetIntegration ? "Manage Sheets" : "Connect"}`}
|
||||
connectNewTab={false}
|
||||
docsHref="https://formbricks.com/docs/integrations/google-sheets"
|
||||
docsText="Docs"
|
||||
docsNewTab={true}
|
||||
label="Google Sheets"
|
||||
description="Instantly populate your spreadsheets with survey data"
|
||||
icon={<Image src={GoogleSheetsLogo} alt="Google sheets Logo" />}
|
||||
/>
|
||||
<Card
|
||||
docsHref="https://formbricks.com/docs/integrations/n8n"
|
||||
docsText="Docs"
|
||||
docsNewTab={true}
|
||||
connectHref="https://n8n.io"
|
||||
connectText="Connect"
|
||||
connectNewTab={true}
|
||||
label="n8n"
|
||||
description="Integrate Formbricks with 350+ apps via n8n"
|
||||
icon={<Image src={n8nLogo} alt="n8n Logo" />}
|
||||
/>
|
||||
<Card
|
||||
docsHref="https://formbricks.com/docs/integrations/make"
|
||||
docsText="Docs"
|
||||
docsNewTab={true}
|
||||
connectHref="https://www.make.com/en/integrations/formbricks"
|
||||
connectText="Connect"
|
||||
connectNewTab={true}
|
||||
label="Make.com"
|
||||
description="Integrate Formbricks with 1000+ apps via Make"
|
||||
icon={<Image src={MakeLogo} alt="Make Logo" />}
|
||||
/>
|
||||
{integrationCards.map((card) => (
|
||||
<Card
|
||||
key={card.label}
|
||||
docsHref={card.docsHref}
|
||||
docsText={card.docsText}
|
||||
docsNewTab={card.docsNewTab}
|
||||
connectHref={card.connectHref}
|
||||
connectText={card.connectText}
|
||||
connectNewTab={card.connectNewTab}
|
||||
label={card.label}
|
||||
description={card.description}
|
||||
icon={card.icon}
|
||||
connected={card.connected}
|
||||
statusText={card.statusText}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { TTeam } from "@formbricks/types/v1/teams";
|
||||
import React from "react";
|
||||
import MembersInfo from "@/app/(app)/environments/[environmentId]/settings/members/EditMemberships/MembersInfo";
|
||||
import { getMembersByTeamId } from "@formbricks/lib/services/membership";
|
||||
import { getInviteesByTeamId } from "@formbricks/lib/services/invite";
|
||||
import { getInvitesByTeamId } from "@formbricks/lib/services/invite";
|
||||
import { TMembership } from "@formbricks/types/v1/memberships";
|
||||
|
||||
type EditMembershipsProps = {
|
||||
@@ -18,7 +18,7 @@ export async function EditMemberships({
|
||||
currentUserMembership: membership,
|
||||
}: EditMembershipsProps) {
|
||||
const members = await getMembersByTeamId(team.id);
|
||||
const invites = await getInviteesByTeamId(team.id);
|
||||
const invites = await getInvitesByTeamId(team.id);
|
||||
|
||||
const currentUserRole = membership?.role;
|
||||
const isUserAdminOrOwner = membership?.role === "admin" || membership?.role === "owner";
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import "server-only";
|
||||
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { getActionClass } from "./service";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
export const canUserAccessActionClass = async (userId: string, actionClassId: string): Promise<boolean> =>
|
||||
await unstable_cache(
|
||||
@@ -20,5 +23,5 @@ export const canUserAccessActionClass = async (userId: string, actionClassId: st
|
||||
},
|
||||
|
||||
[`users-${userId}-actionClasses-${actionClassId}`],
|
||||
{ revalidate: 30 * 60, tags: [`actionClasses-${actionClassId}`] }
|
||||
)(); // 30 minutes
|
||||
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [`actionClasses-${actionClassId}`] }
|
||||
)();
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
import { TActionClass, TActionClassInput, ZActionClassInput } from "@formbricks/types/v1/actionClasses";
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
|
||||
import { revalidateTag, unstable_cache } from "next/cache";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
|
||||
const halfHourInSeconds = 60 * 30;
|
||||
|
||||
export const getActionClassCacheTag = (name: string, environmentId: string): string =>
|
||||
`environments-${environmentId}-actionClass-${name}`;
|
||||
|
||||
@@ -50,7 +49,7 @@ export const getActionClasses = (environmentId: string): Promise<TActionClass[]>
|
||||
[`environments-${environmentId}-actionClasses`],
|
||||
{
|
||||
tags: [getActionClassesCacheTag(environmentId)],
|
||||
revalidate: halfHourInSeconds,
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -168,6 +167,6 @@ export const getActionClassCached = async (name: string, environmentId: string)
|
||||
[`environments-${environmentId}-actionClasses-${name}`],
|
||||
{
|
||||
tags: [getActionClassesCacheTag(environmentId)],
|
||||
revalidate: halfHourInSeconds,
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import "server-only";
|
||||
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { getApiKey } from "./service";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
export const canUserAccessApiKey = async (userId: string, apiKeyId: string): Promise<boolean> =>
|
||||
await unstable_cache(
|
||||
@@ -19,5 +22,5 @@ export const canUserAccessApiKey = async (userId: string, apiKeyId: string): Pro
|
||||
},
|
||||
|
||||
[`users-${userId}-apiKeys-${apiKeyId}`],
|
||||
{ revalidate: 30 * 60, tags: [`apiKeys-${apiKeyId}`] }
|
||||
)(); // 30 minutes
|
||||
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [`apiKeys-${apiKeyId}`] }
|
||||
)();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import z from "zod";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { TApiKey, TApiKeyCreateInput, ZApiKeyCreateInput } from "@formbricks/types/v1/apiKeys";
|
||||
|
||||
@@ -4,6 +4,7 @@ import { env } from "@/env.mjs";
|
||||
export const RESPONSES_LIMIT_FREE = 100;
|
||||
export const IS_FORMBRICKS_CLOUD = env.IS_FORMBRICKS_CLOUD === "1";
|
||||
export const REVALIDATION_INTERVAL = 0; //TODO: find a good way to cache and revalidate data when it changes
|
||||
export const SERVICES_REVALIDATION_INTERVAL = 60 * 30; // 30 minutes
|
||||
export const MAU_LIMIT = IS_FORMBRICKS_CLOUD ? 5000 : 1000000;
|
||||
|
||||
// URLs
|
||||
|
||||
@@ -2,6 +2,7 @@ import { prisma } from "@formbricks/database";
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
export const hasUserEnvironmentAccess = async (userId: string, environmentId: string) => {
|
||||
return await unstable_cache(
|
||||
@@ -31,6 +32,6 @@ export const hasUserEnvironmentAccess = async (userId: string, environmentId: st
|
||||
return environmentUsers.includes(userId);
|
||||
},
|
||||
[`users-${userId}-environments-${environmentId}`],
|
||||
{ revalidate: 30 * 60, tags: [`environments-${environmentId}`] }
|
||||
)(); // 30 minutes
|
||||
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [`environments-${environmentId}`] }
|
||||
)();
|
||||
};
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import "server-only";
|
||||
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { getResponse, getResponseCacheTag } from "./service";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { getSurvey } from "../services/survey";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
export const canUserAccessResponse = async (userId: string, responseId: string): Promise<boolean> =>
|
||||
await unstable_cache(
|
||||
@@ -24,5 +27,5 @@ export const canUserAccessResponse = async (userId: string, responseId: string):
|
||||
return true;
|
||||
},
|
||||
[`users-${userId}-responses-${responseId}`],
|
||||
{ revalidate: 30 * 60, tags: [getResponseCacheTag(responseId)] }
|
||||
)(); // 30 minutes
|
||||
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [getResponseCacheTag(responseId)] }
|
||||
)();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import {
|
||||
TResponse,
|
||||
@@ -11,7 +13,6 @@ import { TPerson } from "@formbricks/types/v1/people";
|
||||
import { TTag } from "@formbricks/types/v1/tags";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { cache } from "react";
|
||||
import "server-only";
|
||||
import { getPerson, transformPrismaPerson } from "../services/person";
|
||||
import { captureTelemetry } from "../telemetry";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use server";
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import {
|
||||
TAttributeClass,
|
||||
@@ -12,6 +13,7 @@ import { validateInputs } from "../utils/validate";
|
||||
import { DatabaseError } from "@formbricks/types/v1/errors";
|
||||
import { cache } from "react";
|
||||
import { revalidateTag, unstable_cache } from "next/cache";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
const attributeClassesCacheTag = (environmentId: string): string =>
|
||||
`environments-${environmentId}-attributeClasses`;
|
||||
@@ -99,7 +101,7 @@ export const getAttributeClassByNameCached = async (environmentId: string, name:
|
||||
getAttributeClassesCacheKey(environmentId),
|
||||
{
|
||||
tags: getAttributeClassesCacheKey(environmentId),
|
||||
revalidate: 30 * 60, // 30 minutes
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import {
|
||||
TDisplay,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { z } from "zod";
|
||||
import { Prisma, EnvironmentType } from "@prisma/client";
|
||||
@@ -8,6 +9,7 @@ import { populateEnvironment } from "../utils/createDemoProductHelpers";
|
||||
import { ZEnvironment, ZEnvironmentUpdateInput, ZId } from "@formbricks/types/v1/environment";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { unstable_cache, revalidateTag } from "next/cache";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
export const getEnvironmentCacheTag = (environmentId: string) => `environments-${environmentId}`;
|
||||
export const getEnvironmentsCacheTag = (productId: string) => `products-${productId}-environments`;
|
||||
@@ -45,7 +47,7 @@ export const getEnvironment = (environmentId: string) =>
|
||||
[`environments-${environmentId}`],
|
||||
{
|
||||
tags: [getEnvironmentCacheTag(environmentId)],
|
||||
revalidate: 30 * 60, // 30 minutes
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -92,7 +94,7 @@ export const getEnvironments = async (productId: string): Promise<TEnvironment[]
|
||||
[`products-${productId}-environments`],
|
||||
{
|
||||
tags: [getEnvironmentsCacheTag(productId)],
|
||||
revalidate: 30 * 60, // 30 minutes
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { DatabaseError } from "@formbricks/types/v1/errors";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { TInvite, TInviteUpdateInput } from "@formbricks/types/v1/invites";
|
||||
@@ -19,7 +21,7 @@ const inviteSelect = {
|
||||
role: true,
|
||||
};
|
||||
|
||||
export const getInviteesByTeamId = cache(async (teamId: string): Promise<TInvite[] | null> => {
|
||||
export const getInvitesByTeamId = cache(async (teamId: string): Promise<TInvite[] | null> => {
|
||||
const invites = await prisma.invite.findMany({
|
||||
where: { teamId },
|
||||
select: inviteSelect,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/v1/errors";
|
||||
import { TMember, TMembership, TMembershipUpdateInput } from "@formbricks/types/v1/memberships";
|
||||
|
||||
@@ -10,6 +10,7 @@ import { cache } from "react";
|
||||
import { PEOPLE_PER_PAGE } from "../constants";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { getAttributeClassByName } from "./attributeClass";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
export const selectPerson = {
|
||||
id: true,
|
||||
@@ -100,7 +101,7 @@ export const getPersonCached = async (personId: string) =>
|
||||
getPersonCacheKey(personId),
|
||||
{
|
||||
tags: getPersonCacheKey(personId),
|
||||
revalidate: 30 * 60, // 30 minutes
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { DatabaseError, ValidationError } from "@formbricks/types/v1/errors";
|
||||
@@ -6,12 +8,12 @@ import { ZProduct, ZProductUpdateInput } from "@formbricks/types/v1/product";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { revalidateTag, unstable_cache } from "next/cache";
|
||||
import { cache } from "react";
|
||||
import "server-only";
|
||||
import { z } from "zod";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { EnvironmentType } from "@prisma/client";
|
||||
import { EventType } from "@prisma/client";
|
||||
import { getEnvironmentCacheTag, getEnvironmentsCacheTag } from "./environment";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
export const getProductsCacheTag = (teamId: string): string => `teams-${teamId}-products`;
|
||||
const getProductCacheTag = (environmentId: string): string => `environments-${environmentId}-product`;
|
||||
@@ -85,7 +87,7 @@ export const getProducts = async (teamId: string): Promise<TProduct[]> =>
|
||||
[`teams-${teamId}-products`],
|
||||
{
|
||||
tags: [getProductsCacheTag(teamId)],
|
||||
revalidate: 30 * 60, // 30 minutes
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -124,7 +126,7 @@ export const getProductByEnvironmentIdCached = (environmentId: string) =>
|
||||
getProductCacheKey(environmentId),
|
||||
{
|
||||
tags: getProductCacheKey(environmentId),
|
||||
revalidate: 30 * 60, // 30 minutes
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
|
||||
@@ -8,6 +10,7 @@ import { unstable_cache, revalidateTag } from "next/cache";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { deleteTeam } from "./team";
|
||||
import { z } from "zod";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
const responseSelection = {
|
||||
id: true,
|
||||
@@ -50,7 +53,7 @@ export const getProfile = async (userId: string): Promise<TProfile | null> =>
|
||||
[`profiles-${userId}`],
|
||||
{
|
||||
tags: [getProfileByEmailCacheTag(userId)],
|
||||
revalidate: 30 * 60, // 30 minutes
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -82,7 +85,7 @@ export const getProfileByEmail = async (email: string): Promise<TProfile | null>
|
||||
[`profiles-${email}`],
|
||||
{
|
||||
tags: [getProfileCacheTag(email)],
|
||||
revalidate: 30 * 60, // 30 minutes
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -195,29 +198,3 @@ export const deleteProfile = async (userId: string): Promise<void> => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
export async function getUserIdFromEnvironment(environmentId: string) {
|
||||
const environment = await prisma.environment.findUnique({
|
||||
where: { id: environmentId },
|
||||
select: {
|
||||
product: {
|
||||
select: {
|
||||
team: {
|
||||
select: {
|
||||
memberships: {
|
||||
select: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return environment?.product.team.memberships[0].user.id;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/v1/errors";
|
||||
@@ -10,7 +12,6 @@ import {
|
||||
} from "@formbricks/types/v1/surveys";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { revalidateTag, unstable_cache } from "next/cache";
|
||||
import "server-only";
|
||||
import { z } from "zod";
|
||||
import { captureTelemetry } from "../telemetry";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { TTagsCount } from "@formbricks/types/v1/tags";
|
||||
import { cache } from "react";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
|
||||
@@ -24,6 +26,7 @@ import {
|
||||
updateEnvironmentArgs,
|
||||
} from "../utils/createDemoProductHelpers";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
export const select = {
|
||||
id: true,
|
||||
@@ -64,7 +67,7 @@ export const getTeamsByUserId = async (userId: string): Promise<TTeam[]> =>
|
||||
[`users-${userId}-teams`],
|
||||
{
|
||||
tags: [getTeamsByUserIdCacheTag(userId)],
|
||||
revalidate: 30 * 60, // 30 minutes
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -100,7 +103,7 @@ export const getTeamByEnvironmentId = async (environmentId: string): Promise<TTe
|
||||
[`environments-${environmentId}-team`],
|
||||
{
|
||||
tags: [getTeamByEnvironmentIdCacheTag(environmentId)],
|
||||
revalidate: 30 * 60, // 30 minutes
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -182,15 +185,8 @@ export const deleteTeam = async (teamId: string) => {
|
||||
|
||||
export const createDemoProduct = async (teamId: string) => {
|
||||
validateInputs([teamId, ZId]);
|
||||
const productWithEnvironment = Prisma.validator<Prisma.ProductArgs>()({
|
||||
include: {
|
||||
environments: true,
|
||||
},
|
||||
});
|
||||
|
||||
type ProductWithEnvironment = Prisma.ProductGetPayload<typeof productWithEnvironment>;
|
||||
|
||||
const demoProduct: ProductWithEnvironment = await prisma.product.create({
|
||||
const demoProduct = await prisma.product.create({
|
||||
data: {
|
||||
name: "Demo Product",
|
||||
team: {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
|
||||
@@ -22,6 +22,24 @@ export const getWebhooks = cache(async (environmentId: string): Promise<TWebhook
|
||||
}
|
||||
});
|
||||
|
||||
export const getCountOfWebhooksBasedOnSource = async (
|
||||
environmentId: string,
|
||||
source: TWebhookInput["source"]
|
||||
): Promise<number> => {
|
||||
validateInputs([environmentId, ZId], [source, ZId]);
|
||||
try {
|
||||
const count = await prisma.webhook.count({
|
||||
where: {
|
||||
environmentId,
|
||||
source,
|
||||
},
|
||||
});
|
||||
return count;
|
||||
} catch (error) {
|
||||
throw new DatabaseError(`Database error when fetching webhooks for environment ${environmentId}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const getWebhook = async (id: string): Promise<TWebhook | null> => {
|
||||
validateInputs([id, ZId]);
|
||||
const webhook = await prisma.webhook.findUnique({
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import "server-only";
|
||||
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { getTag } from "./service";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
export const canUserAccessTag = async (userId: string, tagId: string): Promise<boolean> =>
|
||||
await unstable_cache(
|
||||
@@ -19,6 +22,6 @@ export const canUserAccessTag = async (userId: string, tagId: string): Promise<b
|
||||
},
|
||||
[`${userId}-${tagId}`],
|
||||
{
|
||||
revalidate: 30 * 60, // 30 minutes
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "server-only";
|
||||
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { TTag } from "@formbricks/types/v1/tags";
|
||||
import { cache } from "react";
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import "server-only";
|
||||
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { ZId } from "@formbricks/types/v1/environment";
|
||||
import { canUserAccessResponse } from "../response/auth";
|
||||
import { canUserAccessTag } from "../tag/auth";
|
||||
import { getTagOnResponseCacheTag } from "../services/tagOnResponse";
|
||||
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
|
||||
export const canUserAccessTagOnResponse = async (
|
||||
userId: string,
|
||||
@@ -20,5 +23,5 @@ export const canUserAccessTagOnResponse = async (
|
||||
return isAuthorizedForTag && isAuthorizedForResponse;
|
||||
},
|
||||
[`users-${userId}-tagOnResponse-${tagId}-${responseId}`],
|
||||
{ revalidate: 30 * 60, tags: [getTagOnResponseCacheTag(tagId, responseId)] }
|
||||
{ revalidate: SERVICES_REVALIDATION_INTERVAL, tags: [getTagOnResponseCacheTag(tagId, responseId)] }
|
||||
)();
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function RatingQuestion({
|
||||
key={number}
|
||||
onMouseOver={() => setHoveredNumber(number)}
|
||||
onMouseLeave={() => setHoveredNumber(0)}
|
||||
className="max-w-10 relative max-h-10 flex-1 cursor-pointer bg-white text-center text-sm leading-10">
|
||||
className="max-w-10 relative flex max-h-10 flex-1 cursor-pointer justify-center bg-white text-center text-sm leading-10">
|
||||
{question.scale === "number" ? (
|
||||
<label
|
||||
className={cn(
|
||||
@@ -131,7 +131,11 @@ export default function RatingQuestion({
|
||||
)}
|
||||
</label>
|
||||
) : (
|
||||
<label className="flex h-full w-full justify-center text-slate-800">
|
||||
<label
|
||||
className={cn(
|
||||
"flex items-center justify-center text-slate-800",
|
||||
question.range === 10 ? "h-6 w-6" : "h-full w-full"
|
||||
)}>
|
||||
<HiddenRadioInput number={number} />
|
||||
<RatingSmiley
|
||||
active={value === number || hoveredNumber === number}
|
||||
|
||||
@@ -10,6 +10,8 @@ interface CardProps {
|
||||
label: string;
|
||||
description: string;
|
||||
icon?: React.ReactNode;
|
||||
connected?: boolean;
|
||||
statusText?: string;
|
||||
}
|
||||
|
||||
export type { CardProps };
|
||||
@@ -24,8 +26,26 @@ export const Card: React.FC<CardProps> = ({
|
||||
label,
|
||||
description,
|
||||
icon,
|
||||
connected,
|
||||
statusText,
|
||||
}) => (
|
||||
<div className="rounded-lg bg-white p-8 text-left shadow-sm ">
|
||||
<div className="relative rounded-lg bg-white p-8 text-left shadow-sm ">
|
||||
{connected != undefined && statusText != undefined && (
|
||||
<div className="absolute right-4 top-4 flex items-center rounded bg-slate-200 px-2 py-1 text-xs text-slate-500 dark:bg-slate-800 dark:text-slate-400">
|
||||
{connected === true ? (
|
||||
<span className="relative mr-1 flex h-2 w-2">
|
||||
<span className="animate-ping-slow absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-green-500"></span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="relative mr-1 flex h-2 w-2">
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-gray-400"></span>
|
||||
</span>
|
||||
)}
|
||||
{statusText}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{icon && <div className="mb-6 h-8 w-8">{icon}</div>}
|
||||
<h3 className="text-lg font-bold text-slate-800">{label}</h3>
|
||||
<p className="text-xs text-slate-500">{description}</p>
|
||||
|
||||
Reference in New Issue
Block a user