mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-22 02:55:04 -05:00
Improve documentation (#552)
* update nextjs app docs * remove prisma extendedWhereUnique from schema * change button titles in pricing table * fix smaller bugs
This commit is contained in:
@@ -36,22 +36,24 @@ pnpm add @formbricks/js
|
||||
|
||||
## Integrating with Next.js 13 App Directory
|
||||
|
||||
The Next.js 13 app directory requires us to initialize Formbricks differently than the pages directory. Specifically, the app directory server-side renders components by default, and the formbricks-js library is a client-side library. To make these work together, create a `formbricks.js` file (or formbricks.ts if you are using Typescript) and set up the FormbricksProvider with the 'use client' directive:
|
||||
The Next.js 13 app directory requires us to initialize Formbricks differently than the pages directory. Specifically, the app directory server-side renders components by default, and the formbricks-js library is a client-side library. To make these work together, create a `formbricks.tsx` file (or `formbricks.js` if you don't use Typescript) and set up the FormbricksProvider with the 'use client' directive:
|
||||
|
||||
```tsx
|
||||
// app/formbricks.js
|
||||
// app/formbricks.tsx
|
||||
"use client";
|
||||
import formbricks from "@formbricks/js";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "your-environment-id",
|
||||
apiHost: "your-api-host", // e.g. https://app.formbricks.com
|
||||
environmentId: "clj66eqzu00m5qu0g8leglrns",
|
||||
apiHost: "https://app.formbricks.com", // e.g. https://app.formbricks.com
|
||||
debug: true, // remove when in production
|
||||
});
|
||||
}
|
||||
|
||||
export default function Providers({ Component, pageProps }: AppProps) {
|
||||
export default function FormbricksProvider() {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
@@ -63,21 +65,17 @@ export default function Providers({ Component, pageProps }: AppProps) {
|
||||
}
|
||||
```
|
||||
|
||||
Once we do this, we can then import the `provider.js` file in our `app/layout.js` file, and wrap our app in the Formbricks provider.
|
||||
Once we do this, we can then import the `formbricks.tsx` file in our `app/layout.tsx` file, and wrap our app in the Formbricks provider.
|
||||
|
||||
```tsx
|
||||
// app/layout.js
|
||||
import Providers from "./formbricks";
|
||||
// app/layout.tsx
|
||||
import FormbricksProvider from "./formbricks";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<Providers />
|
||||
<FormbricksProvider />
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -17,7 +17,11 @@ export default async function PeoplePage({ params }) {
|
||||
return (
|
||||
<>
|
||||
{people.length === 0 ? (
|
||||
<EmptySpaceFiller type="table" environmentId={params.environmentId} />
|
||||
<EmptySpaceFiller
|
||||
type="table"
|
||||
environmentId={params.environmentId}
|
||||
emptyMessage="Your users will appear here as soon as they use your app ⏲️"
|
||||
/>
|
||||
) : (
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="grid h-12 grid-cols-7 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
|
||||
|
||||
@@ -134,7 +134,7 @@ export default function PricingTable({ environmentId, session }: PricingTablePro
|
||||
variant="secondary"
|
||||
className="mt-6 w-full justify-center py-4 shadow-sm"
|
||||
onClick={() => openCustomerPortal()}>
|
||||
Change Plan
|
||||
Manage Subscription
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
|
||||
@@ -130,7 +130,7 @@ export function DeleteProduct({ environmentId }) {
|
||||
|
||||
if (deleteProductRes?.id?.length > 0) {
|
||||
toast.success("Product deleted successfully.");
|
||||
router.push("/environments");
|
||||
router.push("/");
|
||||
} else if (deleteProductRes?.message?.length > 0) {
|
||||
toast.error(deleteProductRes.message);
|
||||
setIsDeleteDialogOpen(false);
|
||||
|
||||
+4
@@ -36,6 +36,10 @@ export default function SuccessMessage({ environmentId, survey }: SummaryMetadat
|
||||
if (survey.type === "link") {
|
||||
setShowLinkModal(true);
|
||||
}
|
||||
// Remove success param from url
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete("success");
|
||||
window.history.replaceState({}, "", url.toString());
|
||||
}
|
||||
}
|
||||
}, [environment, searchParams, survey]);
|
||||
|
||||
@@ -100,10 +100,6 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
|
||||
return <div>Error</div>;
|
||||
}
|
||||
|
||||
/* if (localSurvey.type === "link") {
|
||||
return null;
|
||||
} */
|
||||
|
||||
return (
|
||||
<>
|
||||
<Collapsible.Root
|
||||
|
||||
@@ -85,7 +85,11 @@ export default function WhoToSendCard({ environmentId, localSurvey, setLocalSurv
|
||||
<>
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
onOpenChange={(openState) => {
|
||||
if (localSurvey.type !== "link") {
|
||||
setOpen(openState);
|
||||
}
|
||||
}}
|
||||
className="w-full rounded-lg border border-slate-300 bg-white">
|
||||
<Collapsible.CollapsibleTrigger
|
||||
asChild
|
||||
|
||||
@@ -9,9 +9,15 @@ type EmptySpaceFillerProps = {
|
||||
type: "table" | "response" | "event" | "linkResponse" | "tag";
|
||||
environmentId: string;
|
||||
noWidgetRequired?: boolean;
|
||||
emptyMessage?: string;
|
||||
};
|
||||
|
||||
const EmptySpaceFiller: React.FC<EmptySpaceFillerProps> = ({ type, environmentId, noWidgetRequired }) => {
|
||||
const EmptySpaceFiller: React.FC<EmptySpaceFillerProps> = ({
|
||||
type,
|
||||
environmentId,
|
||||
noWidgetRequired,
|
||||
emptyMessage,
|
||||
}) => {
|
||||
const { environment, isErrorEnvironment, isLoadingEnvironment } = useEnvironment(environmentId);
|
||||
|
||||
if (isLoadingEnvironment) return <LoadingSpinner />;
|
||||
@@ -34,7 +40,7 @@ const EmptySpaceFiller: React.FC<EmptySpaceFillerProps> = ({ type, environmentId
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{(environment.widgetSetupCompleted || noWidgetRequired) &&
|
||||
{((environment.widgetSetupCompleted || noWidgetRequired) && emptyMessage) ||
|
||||
"Your data will appear here as soon as you receive your first response ⏲️"}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -123,23 +123,36 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
// DELETE
|
||||
else if (req.method === "DELETE") {
|
||||
// get teamId from product
|
||||
const environment = await prisma.environment.findUnique({
|
||||
where: { id: environmentId },
|
||||
select: {
|
||||
product: {
|
||||
select: {
|
||||
id: true,
|
||||
teamId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!environment) {
|
||||
res.status(404).json({ error: "Environment not found" });
|
||||
return;
|
||||
}
|
||||
const teamId = environment?.product.teamId;
|
||||
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_teamId: {
|
||||
userId: currentUser.id,
|
||||
teamId: currentUser.teamId,
|
||||
teamId: teamId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (membership?.role !== "admin" && membership?.role !== "owner") {
|
||||
return res.status(403).json({ message: "You are not allowed to delete products." });
|
||||
}
|
||||
const environment = await prisma.environment.findUnique({
|
||||
where: { id: environmentId },
|
||||
select: {
|
||||
productId: true,
|
||||
},
|
||||
});
|
||||
const productId = environment?.product.id;
|
||||
|
||||
if (environment === null) {
|
||||
return res.status(404).json({ message: "This environment doesn't exist" });
|
||||
@@ -147,7 +160,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
// Delete the product with
|
||||
const prismaRes = await prisma.product.delete({
|
||||
where: { id: environment.productId },
|
||||
where: { id: productId },
|
||||
});
|
||||
|
||||
return res.json(prismaRes);
|
||||
|
||||
@@ -7,8 +7,7 @@ datasource db {
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["extendedWhereUnique"]
|
||||
provider = "prisma-client-js"
|
||||
//provider = "prisma-dbml-generator"
|
||||
}
|
||||
|
||||
|
||||
@@ -115,9 +115,9 @@ export const initialize = async (
|
||||
// continue for now - next sync will check complete state
|
||||
}
|
||||
} else {
|
||||
logger.debug("No valid session found. Creating new config.");
|
||||
logger.debug("No valid configuration found. Creating new config.");
|
||||
// we need new config
|
||||
config.update({ environmentId: c.environmentId, apiHost: c.apiHost });
|
||||
config.update({ environmentId: c.environmentId, apiHost: c.apiHost, state: undefined });
|
||||
|
||||
logger.debug("Syncing.");
|
||||
const syncResult = await sync();
|
||||
|
||||
@@ -83,11 +83,11 @@ export const getPeople = cache(async (environmentId: string): Promise<TPerson[]>
|
||||
throw new ResourceNotFoundError("Persons", "All Persons");
|
||||
}
|
||||
|
||||
const transformedPersons: TPerson[] = personsPrisma
|
||||
const transformedPeople: TPerson[] = personsPrisma
|
||||
.map(transformPrismaPerson)
|
||||
.filter((person: TPerson | null): person is TPerson => person !== null);
|
||||
|
||||
return transformedPersons;
|
||||
return transformedPeople;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError("Database operation failed");
|
||||
|
||||
Reference in New Issue
Block a user