diff --git a/apps/web/app/environments/[environmentId]/settings/billing/PricingTable.tsx b/apps/web/app/environments/[environmentId]/settings/billing/PricingTable.tsx
index bbfd04c6ba..0d5c81812b 100644
--- a/apps/web/app/environments/[environmentId]/settings/billing/PricingTable.tsx
+++ b/apps/web/app/environments/[environmentId]/settings/billing/PricingTable.tsx
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import LoadingSpinner from "@/components/shared/LoadingSpinner";
-import { useTeam } from "@/lib/teams";
+import { useTeam } from "@/lib/teams/teams";
import { Badge, Button, ErrorComponent } from "@formbricks/ui";
import type { Session } from "next-auth";
import { useRouter } from "next/navigation";
diff --git a/apps/web/app/environments/[environmentId]/settings/members/EditTeamName.tsx b/apps/web/app/environments/[environmentId]/settings/members/EditTeamName.tsx
index 8a8d7c64f7..223b12b922 100644
--- a/apps/web/app/environments/[environmentId]/settings/members/EditTeamName.tsx
+++ b/apps/web/app/environments/[environmentId]/settings/members/EditTeamName.tsx
@@ -1,18 +1,51 @@
"use client";
-import { Button } from "@formbricks/ui";
-import { Input } from "@formbricks/ui";
-import { Label } from "@formbricks/ui";
+import { useEffect, useState } from "react";
+import LoadingSpinner from "@/components/shared/LoadingSpinner";
+import { useTeamMutation } from "@/lib/teams/mutateTeams";
+import { useTeam } from "@/lib/teams/teams";
+import { Button, ErrorComponent, Input, Label } from "@formbricks/ui";
+import { useForm } from "react-hook-form";
+import toast from "react-hot-toast";
+
+export default function EditTeamName({ environmentId }) {
+ const { team, isLoadingTeam, isErrorTeam } = useTeam(environmentId);
+ const { register, handleSubmit } = useForm();
+ const [teamId, setTeamId] = useState("");
+
+ useEffect(() => {
+ if (team && team.id !== "") {
+ setTeamId(team.id);
+ }
+ }, [team]);
+
+ const { isMutatingTeam, triggerTeamMutate } = useTeamMutation(teamId);
+
+ if (isLoadingTeam) {
+ return
;
+ }
+ if (isErrorTeam) {
+ return
;
+ }
-export function EditTeamName() {
return (
-
+
+
);
}
diff --git a/apps/web/app/environments/[environmentId]/settings/members/page.tsx b/apps/web/app/environments/[environmentId]/settings/members/page.tsx
index 44a699de7f..83a3524346 100644
--- a/apps/web/app/environments/[environmentId]/settings/members/page.tsx
+++ b/apps/web/app/environments/[environmentId]/settings/members/page.tsx
@@ -1,14 +1,18 @@
import SettingsCard from "../SettingsCard";
import SettingsTitle from "../SettingsTitle";
import { EditMemberships } from "./EditMemberships";
+import EditTeamName from "./EditTeamName";
export default function MembersSettingsPage({ params }) {
return (
-
+
+
+
+
);
}
diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryList.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryList.tsx
index 8a3eafb562..7bf72e5726 100644
--- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryList.tsx
+++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryList.tsx
@@ -19,8 +19,6 @@ export default function SummaryList({ environmentId, surveyId }) {
const responses = responsesData?.responses;
- console.log(responses);
-
const summaryData: QuestionSummary[] = useMemo(() => {
if (survey && responses) {
return survey.questions.map((question) => {
diff --git a/apps/web/lib/teams/mutateTeams.ts b/apps/web/lib/teams/mutateTeams.ts
new file mode 100644
index 0000000000..023c7df2cc
--- /dev/null
+++ b/apps/web/lib/teams/mutateTeams.ts
@@ -0,0 +1,11 @@
+import useSWRMutation from "swr/mutation";
+import { updateRessource } from "@formbricks/lib/fetcher";
+
+export function useTeamMutation(teamId: string) {
+ const { trigger, isMutating } = useSWRMutation(`/api/v1/teams/${teamId}`, updateRessource);
+
+ return {
+ triggerTeamMutate: trigger,
+ isMutatingTeam: isMutating,
+ };
+}
diff --git a/apps/web/lib/teams.ts b/apps/web/lib/teams/teams.ts
similarity index 100%
rename from apps/web/lib/teams.ts
rename to apps/web/lib/teams/teams.ts
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
index 2b373bb8a3..27e991e862 100644
--- a/apps/web/next.config.js
+++ b/apps/web/next.config.js
@@ -1,5 +1,10 @@
/** @type {import('next').NextConfig} */
+const path = require("path");
+const Dotenv = require("dotenv-webpack");
+
+const rootPath = path.join(__dirname, "..", "..");
+
const { createId } = require("@paralleldrive/cuid2");
const nextConfig = {
@@ -48,6 +53,14 @@ const nextConfig = {
},
];
},
+ webpack: (config) => {
+ config.plugins.push(
+ new Dotenv({
+ path: path.resolve(rootPath, ".env"),
+ })
+ );
+ return config;
+ },
env: {
INSTANCE_ID: createId(),
},
diff --git a/apps/web/package.json b/apps/web/package.json
index 8efb083f67..01f8c1a76f 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -24,6 +24,7 @@
"bcryptjs": "^2.4.3",
"class-variance-authority": "^0.5.2",
"date-fns": "^2.29.3",
+ "dotenv-webpack": "^8.0.1",
"eslint": "8.38.0",
"eslint-config-next": "^13.3.0",
"jsonwebtoken": "^9.0.0",
diff --git a/apps/web/pages/api/v1/memberships/index.ts b/apps/web/pages/api/v1/memberships/index.ts
index 7bfb26b42f..74c43d3b08 100644
--- a/apps/web/pages/api/v1/memberships/index.ts
+++ b/apps/web/pages/api/v1/memberships/index.ts
@@ -16,6 +16,26 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
where: {
userId: user.id,
},
+ include: {
+ team: {
+ select: {
+ id: true,
+ name: true,
+ products: {
+ select: {
+ id: true,
+ name: true,
+ environments: {
+ select: {
+ id: true,
+ type: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
});
return res.json(memberships);
}
diff --git a/apps/web/pages/api/v1/teams/[teamId]/index.ts b/apps/web/pages/api/v1/teams/[teamId]/index.ts
new file mode 100644
index 0000000000..1c16cb7d4f
--- /dev/null
+++ b/apps/web/pages/api/v1/teams/[teamId]/index.ts
@@ -0,0 +1,60 @@
+import { getSessionUser, hasTeamAccess } from "@/lib/api/apiHelper";
+import { prisma } from "@formbricks/database";
+import type { NextApiRequest, NextApiResponse } from "next";
+
+export default async function handle(req: NextApiRequest, res: NextApiResponse) {
+ // Check Authentication
+
+ const currentUser: any = await getSessionUser(req, res);
+ if (!currentUser) {
+ return res.status(401).json({ message: "Not authenticated" });
+ }
+
+ const teamId = req.query.teamId?.toString();
+ if (teamId === undefined) {
+ return res.status(400).json({ message: "Missing teamId" });
+ }
+
+ const hasAccess = await hasTeamAccess(currentUser, teamId);
+ if (!hasAccess) {
+ return res.status(403).json({ message: "Not authorized" });
+ }
+
+ // PUT /api/v1/teams/[teamId]
+ // Update a team
+ if (req.method === "PUT") {
+ const { name } = req.body;
+ if (name === undefined) {
+ return res.status(400).json({ message: "Missing name" });
+ }
+
+ // check if currentUser is owner of the team
+ const membership = await prisma.membership.findUnique({
+ where: {
+ userId_teamId: {
+ userId: currentUser.id,
+ teamId,
+ },
+ },
+ });
+ if (membership?.role !== "owner") {
+ return res.status(403).json({ message: "You are not allowed to update this team" });
+ }
+
+ // update team
+ const team = await prisma.team.update({
+ where: {
+ id: teamId,
+ },
+ data: {
+ name,
+ },
+ });
+ return res.json(team);
+ }
+
+ // Unknown HTTP Method
+ else {
+ throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
+ }
+}
diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts
index 98c9ba9fff..ad2ea2c39f 100644
--- a/packages/lib/constants.ts
+++ b/packages/lib/constants.ts
@@ -1 +1,2 @@
export const RESPONSES_LIMIT_FREE = 100;
+export const IS_FORMBRICKS_CLOUD = process.env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD !== "1";
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 70b3760551..f0382753a8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -219,6 +219,9 @@ importers:
date-fns:
specifier: ^2.29.3
version: 2.29.3
+ dotenv-webpack:
+ specifier: ^8.0.1
+ version: 8.0.1(webpack@5.75.0)
eslint:
specifier: 8.38.0
version: 8.38.0
@@ -4645,7 +4648,7 @@ packages:
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
dependencies:
mini-svg-data-uri: 1.4.4
- tailwindcss: 3.3.1(postcss@8.4.21)
+ tailwindcss: 3.3.1(postcss@8.4.22)
dev: true
/@tailwindcss/typography@0.5.9(tailwindcss@3.3.1):
@@ -8445,6 +8448,22 @@ packages:
dependencies:
is-obj: 2.0.0
+ /dotenv-defaults@2.0.2:
+ resolution: {integrity: sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==}
+ dependencies:
+ dotenv: 8.6.0
+ dev: false
+
+ /dotenv-webpack@8.0.1(webpack@5.75.0):
+ resolution: {integrity: sha512-CdrgfhZOnx4uB18SgaoP9XHRN2v48BbjuXQsZY5ixs5A8579NxQkmMxRtI7aTwSiSQcM2ao12Fdu+L3ZS3bG4w==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ webpack: ^4 || ^5
+ dependencies:
+ dotenv-defaults: 2.0.2
+ webpack: 5.75.0
+ dev: false
+
/dotenv@16.0.3:
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
engines: {node: '>=12'}
@@ -8455,6 +8474,11 @@ packages:
engines: {node: '>=4.6.0'}
dev: false
+ /dotenv@8.6.0:
+ resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==}
+ engines: {node: '>=10'}
+ dev: false
+
/duplexer3@0.1.5:
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}