Merge branch 'formbricks:main' into gitpod-doc

This commit is contained in:
Anjy Gupta
2023-10-03 21:23:52 +05:30
committed by GitHub
30 changed files with 232 additions and 133 deletions

View File

@@ -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

View File

@@ -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>
);

View File

@@ -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";

View File

@@ -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}`] }
)();

View File

@@ -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,
}
)();

View File

@@ -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}`] }
)();

View File

@@ -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";

View File

@@ -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

View File

@@ -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}`] }
)();
};

View File

@@ -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)] }
)();

View File

@@ -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";

View File

@@ -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,
}
)();

View File

@@ -1,3 +1,5 @@
import "server-only";
import { prisma } from "@formbricks/database";
import {
TDisplay,

View File

@@ -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,
}
)();

View File

@@ -1,3 +1,5 @@
import "server-only";
import { prisma } from "@formbricks/database";
import { Prisma } from "@prisma/client";
import { DatabaseError } from "@formbricks/types/v1/errors";

View File

@@ -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,

View File

@@ -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";

View File

@@ -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,
}
)();

View File

@@ -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,
}
)();

View File

@@ -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;
}

View File

@@ -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";

View File

@@ -1,3 +1,5 @@
import "server-only";
import { prisma } from "@formbricks/database";
import { TTagsCount } from "@formbricks/types/v1/tags";
import { cache } from "react";

View File

@@ -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: {

View File

@@ -1,3 +1,5 @@
import "server-only";
import { prisma } from "@formbricks/database";
import { Prisma } from "@prisma/client";
import { validateInputs } from "../utils/validate";

View File

@@ -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({

View File

@@ -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,
}
)();

View File

@@ -1,3 +1,5 @@
import "server-only";
import { prisma } from "@formbricks/database";
import { TTag } from "@formbricks/types/v1/tags";
import { cache } from "react";

View File

@@ -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)] }
)();

View File

@@ -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}

View File

@@ -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>