mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
chore: moves setup checklist to react server components (#695)
* Chore: moves setup checklist to RSC * fix other merge conflictsg * made code refactors * added TAction as return type for getActions * fixed build issues * fix environmentNotice component * refactor EnvironmentNotice component * fix js tests --------- Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
b0d7bd8686
commit
0a1de196aa
@@ -1,18 +1,10 @@
|
||||
{
|
||||
"extends": "@formbricks/tsconfig/nextjs.json",
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"../../packages/types/*.d.ts"
|
||||
],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "../../packages/types/*.d.ts"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"strictNullChecks": true
|
||||
},
|
||||
|
||||
@@ -5,22 +5,27 @@ import SettingsCard from "../SettingsCard";
|
||||
import SettingsTitle from "../SettingsTitle";
|
||||
import ApiKeyList from "./ApiKeyList";
|
||||
import EnvironmentNotice from "@/components/shared/EnvironmentNotice";
|
||||
import { getEnvironment } from "@formbricks/lib/services/environment";
|
||||
|
||||
export default async function ProfileSettingsPage({ params }) {
|
||||
const environment = await getEnvironment(params.environmentId);
|
||||
return (
|
||||
<div>
|
||||
<SettingsTitle title="API Keys" />
|
||||
<EnvironmentNotice environmentId={params.environmentId} pageType="apiSettings" />
|
||||
<SettingsCard
|
||||
title="Development Env Keys"
|
||||
description="Add and remove API keys for your Development environment.">
|
||||
<ApiKeyList environmentId={params.environmentId} environmentType="development" />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
title="Production Env Keys"
|
||||
description="Add and remove API keys for your Production environment.">
|
||||
<ApiKeyList environmentId={params.environmentId} environmentType="production" />
|
||||
</SettingsCard>
|
||||
<EnvironmentNotice environment={environment} />
|
||||
{environment.type === "development" ? (
|
||||
<SettingsCard
|
||||
title="Development Env Keys"
|
||||
description="Add and remove API keys for your Development environment.">
|
||||
<ApiKeyList environmentId={params.environmentId} environmentType="development" />
|
||||
</SettingsCard>
|
||||
) : (
|
||||
<SettingsCard
|
||||
title="Production Env Keys"
|
||||
description="Add and remove API keys for your Production environment.">
|
||||
<ApiKeyList environmentId={params.environmentId} environmentType="production" />
|
||||
</SettingsCard>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import CodeBlock from "@/components/shared/CodeBlock";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { TabBar } from "@formbricks/ui";
|
||||
import Link from "next/link";
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/themes/prism.css";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { IoLogoHtml5, IoLogoNpm } from "react-icons/io5";
|
||||
import packageJson from "@/package.json";
|
||||
import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
|
||||
const tabs = [
|
||||
{ id: "npm", label: "NPM", icon: <IoLogoNpm /> },
|
||||
@@ -18,10 +18,6 @@ const tabs = [
|
||||
export default function SetupInstructions({ environmentId }) {
|
||||
const [activeTab, setActiveTab] = useState(tabs[0].id);
|
||||
|
||||
useEffect(() => {
|
||||
Prism.highlightAll();
|
||||
}, [activeTab]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TabBar tabs={tabs} activeId={activeTab} setActiveId={setActiveTab} />
|
||||
@@ -37,10 +33,8 @@ export default function SetupInstructions({ environmentId }) {
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "${environmentId}",
|
||||
apiHost: "${typeof window !== "undefined" && window.location.protocol}//${
|
||||
typeof window !== "undefined" && window.location.host
|
||||
}",
|
||||
debug: true, // remove when in production
|
||||
apiHost: "${WEBAPP_URL}",
|
||||
debug: true, // remove when in production
|
||||
});
|
||||
}`}</CodeBlock>
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
"use server";
|
||||
|
||||
import { updateEnvironment } from "@formbricks/lib/services/environment";
|
||||
import { TEnvironment, TEnvironmentUpdateInput } from "@formbricks/types/v1/environment";
|
||||
|
||||
export async function updateEnvironmentAction(environmentId: string, data: Partial<TEnvironmentUpdateInput>): Promise<TEnvironment> {
|
||||
return await updateEnvironment(environmentId, data);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
function LoadingCard({ title, description, skeletonLines }) {
|
||||
return (
|
||||
<div className="my-4 rounded-lg border border-slate-200">
|
||||
<div className="grid content-center rounded-lg bg-slate-100 px-6 py-5 text-left text-slate-900">
|
||||
<h3 className="text-lg font-medium leading-6">{title}</h3>
|
||||
<p className="mt-1 text-sm text-slate-500">{description}</p>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="rounded-lg px-6 py-5 hover:bg-slate-100">
|
||||
{skeletonLines.map((line, index) => (
|
||||
<div key={index} className="mt-4">
|
||||
<div className={`animate-pulse bg-gray-200 ${line.classes}`}></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Loading() {
|
||||
const cards = [
|
||||
{
|
||||
title: "Widget Status",
|
||||
description: "Check if the Formbricks widget is alive and kicking.",
|
||||
skeletonLines: [{ classes: "h-32 max-w-full rounded-md" }],
|
||||
},
|
||||
{
|
||||
title: "How to setup",
|
||||
description: "Follow these steps to setup the Formbricks widget within your app",
|
||||
skeletonLines: [
|
||||
{ classes: "h-6 w-24 rounded-full" },
|
||||
{ classes: "h-4 w-60 rounded-full" },
|
||||
{ classes: "h-4 w-60 rounded-full" },
|
||||
{ classes: "h-6 w-24 rounded-full" },
|
||||
{ classes: "h-4 w-60 rounded-full" },
|
||||
{ classes: "h-4 w-60 rounded-full" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="my-4 text-2xl font-medium leading-6 text-slate-800">Setup Checklist</h2>
|
||||
{cards.map((card, index) => (
|
||||
<LoadingCard key={index} {...card} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +1,51 @@
|
||||
export const revalidate = REVALIDATION_INTERVAL;
|
||||
|
||||
import { updateEnvironmentAction } from "@/app/(app)/environments/[environmentId]/settings/setup/actions";
|
||||
import EnvironmentNotice from "@/components/shared/EnvironmentNotice";
|
||||
import WidgetStatusIndicator from "@/components/shared/WidgetStatusIndicator";
|
||||
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
|
||||
import { getActionsByEnvironmentId } from "@formbricks/lib/services/actions";
|
||||
import { getEnvironment } from "@formbricks/lib/services/environment";
|
||||
import { ErrorComponent } from "@formbricks/ui";
|
||||
import SettingsCard from "../SettingsCard";
|
||||
import SettingsTitle from "../SettingsTitle";
|
||||
import EnvironmentNotice from "../../../../../../components/shared/EnvironmentNotice";
|
||||
import SetupInstructions from "./SetupInstructions";
|
||||
|
||||
export default function ProfileSettingsPage({ params }) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<SettingsTitle title="Setup Checklist" />
|
||||
<SettingsCard title="Widget Status" description="Check if the Formbricks widget is alive and kicking.">
|
||||
<WidgetStatusIndicator environmentId={params.environmentId} type="large" />
|
||||
</SettingsCard>
|
||||
export default async function ProfileSettingsPage({ params }) {
|
||||
const [environment, actions] = await Promise.all([
|
||||
await getEnvironment(params.environmentId),
|
||||
getActionsByEnvironmentId(params.environmentId),
|
||||
]);
|
||||
|
||||
<EnvironmentNotice environmentId={params.environmentId} pageType="setupChecklist" />
|
||||
<SettingsCard
|
||||
title="How to setup"
|
||||
description="Follow these steps to setup the Formbricks widget within your app"
|
||||
noPadding>
|
||||
<SetupInstructions environmentId={params.environmentId} />
|
||||
</SettingsCard>
|
||||
</div>
|
||||
if (!environment) {
|
||||
return <ErrorComponent />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{environment && (
|
||||
<div className="space-y-4">
|
||||
<SettingsTitle title="Setup Checklist" />
|
||||
<EnvironmentNotice environment={environment} />
|
||||
<SettingsCard
|
||||
title="Widget Status"
|
||||
description="Check if the Formbricks widget is alive and kicking.">
|
||||
<WidgetStatusIndicator
|
||||
environment={environment}
|
||||
actions={actions}
|
||||
type="large"
|
||||
updateEnvironmentAction={updateEnvironmentAction}
|
||||
/>
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsCard
|
||||
title="How to setup"
|
||||
description="Follow these steps to setup the Formbricks widget within your app"
|
||||
noPadding>
|
||||
<SetupInstructions environmentId={params.environmentId} />
|
||||
</SettingsCard>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ export default async function SurveysList({ environmentId }: { environmentId: st
|
||||
key={`survey-${survey.id}`}
|
||||
environmentId={environmentId}
|
||||
environment={environment}
|
||||
otherEnvironment={otherEnvironment}
|
||||
otherEnvironment={otherEnvironment!}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
export const revalidate = REVALIDATION_INTERVAL;
|
||||
|
||||
import { updateEnvironmentAction } from "@/app/(app)/environments/[environmentId]/settings/setup/actions";
|
||||
import ContentWrapper from "@/components/shared/ContentWrapper";
|
||||
import WidgetStatusIndicator from "@/components/shared/WidgetStatusIndicator";
|
||||
import SurveysList from "./SurveyList";
|
||||
import { Metadata } from "next";
|
||||
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
|
||||
import { getActionsByEnvironmentId } from "@formbricks/lib/services/actions";
|
||||
import { getEnvironment } from "@formbricks/lib/services/environment";
|
||||
import { Metadata } from "next";
|
||||
import SurveysList from "./SurveyList";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Your Surveys",
|
||||
};
|
||||
|
||||
export default async function SurveysPage({ params }) {
|
||||
const [environment, actions] = await Promise.all([
|
||||
getEnvironment(params.environmentId),
|
||||
getActionsByEnvironmentId(params.environmentId),
|
||||
]);
|
||||
|
||||
return (
|
||||
<ContentWrapper className="flex h-full flex-col justify-between">
|
||||
<SurveysList environmentId={params.environmentId} />
|
||||
<WidgetStatusIndicator environmentId={params.environmentId} type="mini" />
|
||||
{environment && (
|
||||
<WidgetStatusIndicator
|
||||
environment={environment}
|
||||
actions={actions}
|
||||
type="mini"
|
||||
updateEnvironmentAction={updateEnvironmentAction}
|
||||
/>
|
||||
)}
|
||||
</ContentWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,69 +1,39 @@
|
||||
"use client";
|
||||
import { getEnvironments } from "@formbricks/lib/services/environment";
|
||||
import { TEnvironment } from "@formbricks/types/v1/environment";
|
||||
import { LightBulbIcon } from "@heroicons/react/24/outline";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useEnvironment } from "@/lib/environments/environments";
|
||||
import { ErrorComponent } from "@formbricks/ui";
|
||||
import { LightBulbIcon } from "@heroicons/react/24/solid";
|
||||
import { useRouter } from "next/navigation";
|
||||
interface EnvironmentNoticeProps {
|
||||
environment: TEnvironment;
|
||||
}
|
||||
|
||||
export default function EnvironmentNotice({
|
||||
environmentId,
|
||||
pageType,
|
||||
}: {
|
||||
environmentId: string;
|
||||
pageType: string;
|
||||
}) {
|
||||
const { environment, isErrorEnvironment, isLoadingEnvironment } = useEnvironment(environmentId);
|
||||
const router = useRouter();
|
||||
export default async function EnvironmentNotice({ environment }: EnvironmentNoticeProps) {
|
||||
const headersList = headers();
|
||||
const currentUrl = headersList.get("x-invoke-path") || "";
|
||||
const environments = await getEnvironments(environment.productId);
|
||||
const otherEnvironmentId = environments.find((e) => e.id !== environment.id)?.id || "";
|
||||
|
||||
const changeEnvironment = (environmentType: string) => {
|
||||
const newEnvironmentId = environment.product.environments.find((e) => e.type === environmentType)?.id;
|
||||
router.push(`/environments/${newEnvironmentId}/`);
|
||||
const replaceEnvironmentId = (url: string, newId: string): string => {
|
||||
const regex = /environments\/([a-zA-Z0-9]+)/;
|
||||
if (regex.test(url)) {
|
||||
return url.replace(regex, `environments/${newId}`);
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
if (isLoadingEnvironment) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (isErrorEnvironment) {
|
||||
return <ErrorComponent />;
|
||||
}
|
||||
if (pageType === "apiSettings") {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center space-y-3 rounded-lg border border-blue-100 bg-blue-50 p-4 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base">
|
||||
<LightBulbIcon className="mr-3 h-8 w-8 text-blue-400" />
|
||||
<p>
|
||||
{environment.type === "production"
|
||||
? "You're currently in the production environment, so you can only create production API keys. "
|
||||
: "You're currently in the development environment, so you can only create development API keys. "}
|
||||
<a
|
||||
onClick={() =>
|
||||
changeEnvironment(environment.type === "production" ? "development" : "production")
|
||||
}
|
||||
className="ml-1 cursor-pointer underline">
|
||||
Switch to {environment.type === "production" ? "Development" : "Production"} now.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center space-y-3 rounded-lg border border-blue-100 bg-blue-50 p-4 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base">
|
||||
<LightBulbIcon className="mr-3 h-4 w-4 text-blue-400" />
|
||||
<p>
|
||||
{`You're currently in the ${environment.type} environment.`}
|
||||
<a
|
||||
href={replaceEnvironmentId(currentUrl, otherEnvironmentId)}
|
||||
className="ml-1 cursor-pointer text-sm underline">
|
||||
Switch to {environment.type === "production" ? "Development" : "Production"} now.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (pageType === "setupChecklist")
|
||||
return (
|
||||
<div>
|
||||
{environment.type === "production" && !environment.widgetSetupCompleted && (
|
||||
<div className="flex items-center space-y-3 rounded-lg border border-blue-100 bg-blue-50 p-4 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base">
|
||||
<LightBulbIcon className="mr-3 h-6 w-6 text-blue-400" />
|
||||
<p>
|
||||
You're currently in the Production environment.
|
||||
<a onClick={() => changeEnvironment("development")} className="ml-1 cursor-pointer underline">
|
||||
Switch to Development environment.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useEnvironment } from "@/lib/environments/environments";
|
||||
import { useEnvironmentMutation } from "@/lib/environments/mutateEnvironments";
|
||||
import { useEvents } from "@/lib/events/events";
|
||||
import { timeSince } from "@formbricks/lib/time";
|
||||
import { Confetti, ErrorComponent } from "@formbricks/ui";
|
||||
import { TAction } from "@formbricks/types/v1/actions";
|
||||
import { TEnvironment, TEnvironmentUpdateInput } from "@formbricks/types/v1/environment";
|
||||
import { Confetti } from "@formbricks/ui";
|
||||
import { ArrowDownIcon, CheckIcon, ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
interface WidgetStatusIndicatorProps {
|
||||
environmentId: string;
|
||||
environment: TEnvironment;
|
||||
type: "large" | "mini";
|
||||
actions: TAction[];
|
||||
updateEnvironmentAction: (environmentId: string, data: Partial<TEnvironmentUpdateInput>) => Promise<TEnvironment>;
|
||||
}
|
||||
|
||||
export default function WidgetStatusIndicator({ environmentId, type }: WidgetStatusIndicatorProps) {
|
||||
const { events, isLoadingEvents, isErrorEvents } = useEvents(environmentId);
|
||||
const { triggerEnvironmentMutate } = useEnvironmentMutation(environmentId);
|
||||
const { environment, isErrorEnvironment, isLoadingEnvironment } = useEnvironment(environmentId);
|
||||
export default function WidgetStatusIndicator({
|
||||
environment,
|
||||
type,
|
||||
actions,
|
||||
updateEnvironmentAction,
|
||||
}: WidgetStatusIndicatorProps) {
|
||||
const [confetti, setConfetti] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!environment?.widgetSetupCompleted && events && events.length > 0) {
|
||||
triggerEnvironmentMutate({ widgetSetupCompleted: true });
|
||||
if (!environment?.widgetSetupCompleted && actions && actions.length > 0) {
|
||||
updateEnvironmentAction(environment.id, { widgetSetupCompleted: true });
|
||||
}
|
||||
}, [environment, triggerEnvironmentMutate, events]);
|
||||
}, [environment, actions]);
|
||||
|
||||
const stati = {
|
||||
notImplemented: {
|
||||
@@ -45,8 +47,8 @@ export default function WidgetStatusIndicator({ environmentId, type }: WidgetSta
|
||||
};
|
||||
|
||||
const status = useMemo(() => {
|
||||
if (events && events.length > 0) {
|
||||
const lastEvent = events[0];
|
||||
if (actions && actions.length > 0) {
|
||||
const lastEvent = actions[0];
|
||||
const currentTime = new Date();
|
||||
const lastEventTime = new Date(lastEvent.createdAt);
|
||||
const timeDifference = currentTime.getTime() - lastEventTime.getTime();
|
||||
@@ -60,22 +62,10 @@ export default function WidgetStatusIndicator({ environmentId, type }: WidgetSta
|
||||
} else {
|
||||
return "notImplemented";
|
||||
}
|
||||
}, [events]);
|
||||
}, [actions]);
|
||||
|
||||
const currentStatus = stati[status];
|
||||
|
||||
if (isLoadingEvents || isLoadingEnvironment) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorEvents || isErrorEnvironment) {
|
||||
return <ErrorComponent />;
|
||||
}
|
||||
|
||||
if (type === "large") {
|
||||
return (
|
||||
<div
|
||||
@@ -97,7 +87,7 @@ export default function WidgetStatusIndicator({ environmentId, type }: WidgetSta
|
||||
<p className="text-md font-bold text-slate-800 md:text-xl">{currentStatus.title}</p>
|
||||
<p className="text-sm text-slate-700">
|
||||
{currentStatus.subtitle}{" "}
|
||||
{status !== "notImplemented" && <span>{timeSince(events[0].createdAt)}</span>}
|
||||
{status !== "notImplemented" && <span>{timeSince(actions[0].createdAt.toISOString())}</span>}
|
||||
</p>
|
||||
{confetti && <Confetti />}
|
||||
</div>
|
||||
@@ -105,12 +95,12 @@ export default function WidgetStatusIndicator({ environmentId, type }: WidgetSta
|
||||
}
|
||||
if (type === "mini") {
|
||||
return (
|
||||
<Link href={`/environments/${environmentId}/settings/setup`}>
|
||||
<Link href={`/environments/${environment.id}/settings/setup`}>
|
||||
<div className="group my-4 flex justify-center">
|
||||
<div className=" flex rounded-full bg-slate-100 px-2 py-1">
|
||||
<p className="mr-2 text-sm text-slate-400 group-hover:underline">
|
||||
{currentStatus.subtitle}{" "}
|
||||
{status !== "notImplemented" && <span>{timeSince(events[0].createdAt)}</span>}
|
||||
{status !== "notImplemented" && <span>{timeSince(actions[0].createdAt.toISOString())}</span>}
|
||||
</p>
|
||||
<div
|
||||
className={clsx(
|
||||
|
||||
@@ -214,7 +214,7 @@ export const mockRegisterRouteChangeResponse = () => {
|
||||
console.log("Checking page url: http://localhost/");
|
||||
};
|
||||
|
||||
export const mockLogoutResponse = () => {
|
||||
export const mockResetResponse = () => {
|
||||
fetchMock.mockResponseOnce(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
|
||||
@@ -6,7 +6,7 @@ import formbricks from "../src/index";
|
||||
import {
|
||||
mockEventTrackResponse,
|
||||
mockInitResponse,
|
||||
mockLogoutResponse,
|
||||
mockResetResponse,
|
||||
mockRegisterRouteChangeResponse,
|
||||
mockSetCustomAttributeResponse,
|
||||
mockSetEmailIdResponse,
|
||||
@@ -145,9 +145,9 @@ test("Formbricks should register for route change", async () => {
|
||||
expect(consoleLogMock).toHaveBeenCalledWith(expect.stringMatching(/Checking page url/));
|
||||
});
|
||||
|
||||
test("Formbricks should logout", async () => {
|
||||
mockLogoutResponse();
|
||||
await formbricks.logout();
|
||||
test("Formbricks should reset", async () => {
|
||||
mockResetResponse();
|
||||
await formbricks.reset();
|
||||
const currentStatePerson = formbricks.getPerson();
|
||||
const currentStatePersonAttributes = currentStatePerson.attributes;
|
||||
|
||||
|
||||
45
packages/lib/services/actions.ts
Normal file
45
packages/lib/services/actions.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { DatabaseError } from "@formbricks/errors";
|
||||
import { TAction } from "@formbricks/types/v1/actions";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { cache } from "react";
|
||||
import "server-only";
|
||||
|
||||
export const getActionsByEnvironmentId = cache(
|
||||
async (environmentId: string, limit?: number): Promise<TAction[]> => {
|
||||
try {
|
||||
const actionsPrisma = await prisma.event.findMany({
|
||||
where: {
|
||||
eventClass: {
|
||||
environmentId: environmentId,
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
take: limit ? limit : 20,
|
||||
include: {
|
||||
eventClass: true,
|
||||
},
|
||||
});
|
||||
const actions: TAction[] = [];
|
||||
// transforming response to type TAction[]
|
||||
actionsPrisma.forEach((action) => {
|
||||
actions.push({
|
||||
id: action.id,
|
||||
createdAt: action.createdAt,
|
||||
sessionId: action.sessionId,
|
||||
properties: action.properties,
|
||||
actionClass: action.eventClass,
|
||||
});
|
||||
});
|
||||
return actions;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError("Database operation failed");
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -4,10 +4,10 @@ import { z } from "zod";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/errors";
|
||||
import { ZEnvironment } from "@formbricks/types/v1/environment";
|
||||
import type { TEnvironment } from "@formbricks/types/v1/environment";
|
||||
import type { TEnvironment, TEnvironmentUpdateInput } from "@formbricks/types/v1/environment";
|
||||
import { cache } from "react";
|
||||
|
||||
export const getEnvironment = cache(async (environmentId: string): Promise<TEnvironment | null> => {
|
||||
export const getEnvironment = cache(async (environmentId: string): Promise<TEnvironment> => {
|
||||
let environmentPrisma;
|
||||
try {
|
||||
environmentPrisma = await prisma.environment.findUnique({
|
||||
@@ -75,3 +75,22 @@ export const getEnvironments = cache(async (productId: string): Promise<TEnviron
|
||||
throw new ValidationError("Data validation of environments array failed");
|
||||
}
|
||||
});
|
||||
|
||||
export const updateEnvironment = async (environmentId: string, data: Partial<TEnvironmentUpdateInput>): Promise<TEnvironment> => {
|
||||
const newData = { ...data, updatedAt: new Date() };
|
||||
let updatedEnvironment;
|
||||
try {
|
||||
updatedEnvironment = await prisma.environment.update({
|
||||
where: {
|
||||
id: environmentId,
|
||||
},
|
||||
data: newData,
|
||||
});
|
||||
return updatedEnvironment;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError("Database operation failed");
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,4 +18,4 @@
|
||||
},
|
||||
"include": ["src", "next-env.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
}
|
||||
|
||||
14
packages/types/v1/actions.ts
Normal file
14
packages/types/v1/actions.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { z } from "zod";
|
||||
import { ZActionClass } from "./actionClasses";
|
||||
|
||||
|
||||
export const ZAction = z.object({
|
||||
id: z.string(),
|
||||
createdAt: z.date(),
|
||||
sessionId: z.string(),
|
||||
properties: z.record(z.string()),
|
||||
actionClass: ZActionClass.nullable(),
|
||||
});
|
||||
|
||||
export type TAction = z.infer<typeof ZAction>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ZEnvironment: any = z.object({
|
||||
export const ZEnvironment = z.object({
|
||||
id: z.string().cuid2(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
@@ -10,3 +10,11 @@ export const ZEnvironment: any = z.object({
|
||||
});
|
||||
|
||||
export type TEnvironment = z.infer<typeof ZEnvironment>;
|
||||
|
||||
export const ZEnvironmentUpdateInput = z.object({
|
||||
type: z.enum(["development", "production"]),
|
||||
productId: z.string(),
|
||||
widgetSetupCompleted: z.boolean(),
|
||||
});
|
||||
|
||||
export type TEnvironmentUpdateInput = z.infer<typeof ZEnvironmentUpdateInput>;
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
"@formbricks/demo#go": {
|
||||
"cache": false,
|
||||
"persistent": true,
|
||||
"dependsOn": [
|
||||
"@formbricks/js#build"
|
||||
]
|
||||
"dependsOn": ["@formbricks/js#build"]
|
||||
},
|
||||
"@formbricks/api#build": {
|
||||
"outputs": ["dist/**"],
|
||||
|
||||
Reference in New Issue
Block a user