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:
Matti Nannt
2023-07-14 12:48:57 +02:00
committed by GitHub
parent 62d2c1af18
commit e24f6cd017
12 changed files with 63 additions and 39 deletions
@@ -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);
@@ -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);
+1 -2
View File
@@ -7,8 +7,7 @@ datasource db {
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["extendedWhereUnique"]
provider = "prisma-client-js"
//provider = "prisma-dbml-generator"
}
+2 -2
View File
@@ -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();
+2 -2
View File
@@ -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");