diff --git a/apps/web/app/environments/[environmentId]/attributes/page.tsx b/apps/web/app/environments/[environmentId]/attributes/page.tsx
index fcdddff2e94..4a2d1d340d3 100644
--- a/apps/web/app/environments/[environmentId]/attributes/page.tsx
+++ b/apps/web/app/environments/[environmentId]/attributes/page.tsx
@@ -1,11 +1,11 @@
import AttributeClassesList from "./AttributeClassesList";
-import EventsAttributesTabs from "@/components/events_attributes/EventsAttributesTabs";
+import ActionsAttributesTabs from "@/components/events_attributes/EventsAttributesTabs";
import ContentWrapper from "@/components/shared/ContentWrapper";
export default function AttributesPage({ params }) {
return (
-
+
diff --git a/apps/web/app/environments/[environmentId]/events/ActionClassListRender.tsx b/apps/web/app/environments/[environmentId]/events/ActionClassListRender.tsx
new file mode 100644
index 00000000000..cee74a90f0e
--- /dev/null
+++ b/apps/web/app/environments/[environmentId]/events/ActionClassListRender.tsx
@@ -0,0 +1,77 @@
+"use client";
+
+import { Button } from "@formbricks/ui";
+import { CursorArrowRaysIcon } from "@heroicons/react/24/solid";
+import { useState } from "react";
+import AddNoCodeEventModal from "./AddNoCodeEventModal";
+import ActionDetailModal from "./EventDetailModal";
+import { TranformEventClassOutput } from "@formbricks/lib/services/action";
+import { useRouter } from "next/navigation";
+
+export default function ActionClassesListRender({
+ environmentId,
+ actionClasses,
+ children: [TableHeading, actionRows],
+}: {
+ environmentId: string;
+ actionClasses: Array
;
+ children: [JSX.Element, JSX.Element[]];
+}) {
+ const router = useRouter();
+ const [isActionDetailModalOpen, setActionDetailModalOpen] = useState(false);
+ const [isAddActionModalOpen, setAddActionModalOpen] = useState(false);
+
+ const [activeActionClass, setActiveActionClass] = useState("" as any);
+
+ const handleOpenActionDetailModalClick = (e, actionClass) => {
+ e.preventDefault();
+ setActiveActionClass(actionClass);
+ setActionDetailModalOpen(true);
+ };
+
+ const mutateActionClasses = () => {
+ router.refresh();
+ };
+
+ return (
+ <>
+
+
+
+
+ {TableHeading}
+
+ {actionClasses.map((actionClass, index) => (
+
+ ))}
+
+
+
+
+ >
+ );
+}
diff --git a/apps/web/app/environments/[environmentId]/events/ActionClassesList.tsx b/apps/web/app/environments/[environmentId]/events/ActionClassesList.tsx
new file mode 100644
index 00000000000..796fde136b4
--- /dev/null
+++ b/apps/web/app/environments/[environmentId]/events/ActionClassesList.tsx
@@ -0,0 +1,18 @@
+import ActionClassesListRender from "@/app/environments/[environmentId]/events/ActionClassListRender";
+import { getActionClasses } from "@formbricks/lib/services/action";
+import ActionClassDataRow from "@/app/environments/[environmentId]/events/RowData";
+import TableHeading from "@/app/environments/[environmentId]/events/TableHeading";
+
+export default async function ActionClassesList({ environmentId }: { environmentId: string }) {
+ let actionClasses = await getActionClasses(environmentId);
+ return (
+ <>
+
+
+ {actionClasses.map((actionClass) => (
+
+ ))}
+
+ >
+ );
+}
diff --git a/apps/web/app/environments/[environmentId]/events/EventClassesList.tsx b/apps/web/app/environments/[environmentId]/events/EventClassesList.tsx
deleted file mode 100644
index a7790c5fec8..00000000000
--- a/apps/web/app/environments/[environmentId]/events/EventClassesList.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-"use client";
-
-import LoadingSpinner from "@/components/shared/LoadingSpinner";
-import { useEventClasses } from "@/lib/eventClasses/eventClasses";
-import { timeSinceConditionally } from "@formbricks/lib/time";
-import { Button, ErrorComponent } from "@formbricks/ui";
-import { CodeBracketIcon, CursorArrowRaysIcon, SparklesIcon } from "@heroicons/react/24/solid";
-import { useState } from "react";
-import AddNoCodeEventModal from "./AddNoCodeEventModal";
-import EventDetailModal from "./EventDetailModal";
-
-export default function EventClassesList({ environmentId }) {
- const { eventClasses, isLoadingEventClasses, isErrorEventClasses, mutateEventClasses } =
- useEventClasses(environmentId);
-
- const [isEventDetailModalOpen, setEventDetailModalOpen] = useState(false);
- const [isAddEventModalOpen, setAddEventModalOpen] = useState(false);
-
- const [activeEventClass, setActiveEventClass] = useState("" as any);
-
- if (isLoadingEventClasses) {
- return ;
- }
-
- if (isErrorEventClasses) {
- return ;
- }
-
- const handleOpenEventDetailModalClick = (e, eventClass) => {
- e.preventDefault();
- setActiveEventClass(eventClass);
- setEventDetailModalOpen(true);
- };
-
- return (
- <>
-
-
-
-
-
-
Edit
-
User Actions
-
# Reps
-
Created
-
-
- {eventClasses.map((eventClass) => (
-
- ))}
-
-
-
-
- >
- );
-}
diff --git a/apps/web/app/environments/[environmentId]/events/RowData.tsx b/apps/web/app/environments/[environmentId]/events/RowData.tsx
new file mode 100644
index 00000000000..eef650933d6
--- /dev/null
+++ b/apps/web/app/environments/[environmentId]/events/RowData.tsx
@@ -0,0 +1,34 @@
+import { timeSinceConditionally } from "@formbricks/lib/time";
+import { CodeBracketIcon, CursorArrowRaysIcon } from "@heroicons/react/24/solid";
+import { SparklesIcon } from "lucide-react";
+
+export default function ActionClassDataRow({ actionClass }) {
+ return (
+
+
+
+
+ {actionClass.type === "code" ? (
+
+ ) : actionClass.type === "noCode" ? (
+
+ ) : actionClass.type === "automatic" ? (
+
+ ) : null}
+
+
+
{actionClass.name}
+
{actionClass.description}
+
+
+
+
+ {actionClass.eventCount}
+
+
+ {timeSinceConditionally(actionClass.createdAt.toString())}
+
+
+
+ );
+}
diff --git a/apps/web/app/environments/[environmentId]/events/TableHeading.tsx b/apps/web/app/environments/[environmentId]/events/TableHeading.tsx
new file mode 100644
index 00000000000..7caad1147da
--- /dev/null
+++ b/apps/web/app/environments/[environmentId]/events/TableHeading.tsx
@@ -0,0 +1,12 @@
+export default function TableHeading() {
+ return (
+ <>
+
+
Edit
+
User Actions
+
# Reps
+
Created
+
+ >
+ );
+}
diff --git a/apps/web/app/environments/[environmentId]/events/loading.tsx b/apps/web/app/environments/[environmentId]/events/loading.tsx
new file mode 100644
index 00000000000..3f60bf4ddb2
--- /dev/null
+++ b/apps/web/app/environments/[environmentId]/events/loading.tsx
@@ -0,0 +1,58 @@
+import ActionsAttributesTabs from "@/components/events_attributes/EventsAttributesTabs";
+import ContentWrapper from "@/components/shared/ContentWrapper";
+import { Button } from "@formbricks/ui";
+import { CursorArrowRaysIcon } from "@heroicons/react/24/solid";
+
+export default function Loading() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
Edit
+
User Actions
+
# Reps
+
Created
+
+
+
+ {[...Array(3)].map((_, index) => (
+
+ ))}
+
+ >
+ );
+}
diff --git a/apps/web/app/environments/[environmentId]/events/page.tsx b/apps/web/app/environments/[environmentId]/events/page.tsx
index 12fc5880251..582a517b0e0 100644
--- a/apps/web/app/environments/[environmentId]/events/page.tsx
+++ b/apps/web/app/environments/[environmentId]/events/page.tsx
@@ -1,14 +1,14 @@
-import EventClassesList from "./EventClassesList";
-import EventsAttributesTabs from "@/components/events_attributes/EventsAttributesTabs";
+import ActionsAttributesTabs from "@/components/events_attributes/EventsAttributesTabs";
+import ActionClassesList from "@/app/environments/[environmentId]/events/ActionClassesList";
import ContentWrapper from "@/components/shared/ContentWrapper";
-export default function EventsPage({ params }) {
+export default function ActionsPage({ params }) {
return (
-
+ >
);
}
diff --git a/apps/web/components/events_attributes/EventsAttributesTabs.tsx b/apps/web/components/events_attributes/EventsAttributesTabs.tsx
index a521d11dd40..6704bcf6914 100644
--- a/apps/web/components/events_attributes/EventsAttributesTabs.tsx
+++ b/apps/web/components/events_attributes/EventsAttributesTabs.tsx
@@ -6,7 +6,7 @@ interface EventsAttributesTabsProps {
environmentId: string;
}
-export default function EventsAttributesTabs({ activeId, environmentId }: EventsAttributesTabsProps) {
+export default function ActionsAttributesTabs({ activeId, environmentId }: EventsAttributesTabsProps) {
const tabs = [
{
id: "events",
diff --git a/packages/lib/services/action.ts b/packages/lib/services/action.ts
new file mode 100644
index 00000000000..1e797098bae
--- /dev/null
+++ b/packages/lib/services/action.ts
@@ -0,0 +1,66 @@
+import { prisma } from "@formbricks/database";
+import { DatabaseError } from "@formbricks/errors";
+import { EventType } from "@prisma/client";
+
+export type TranformEventClassOutput = {
+ id: string;
+ createdAt: Date;
+ updatedAt: Date;
+ name: string;
+ description: string | null;
+ type: EventType;
+ noCodeConfig: any;
+ environmentId: string;
+ eventCount: number;
+};
+
+export const transformPrismaEventClass = (eventClass): TranformEventClassOutput | null => {
+ if (eventClass === null) {
+ return null;
+ }
+
+ const transformedEventClass: TranformEventClassOutput = {
+ id: eventClass.id,
+ name: eventClass.name,
+ description: eventClass.description,
+ type: eventClass.type,
+ noCodeConfig: eventClass.noCodeConfig,
+ environmentId: eventClass.environmentId,
+ eventCount: eventClass._count.events,
+ createdAt: eventClass.createdAt,
+ updatedAt: eventClass.updatedAt,
+ };
+
+ return transformedEventClass;
+};
+
+export const getActionClasses = async (environmentId: string): Promise => {
+ try {
+ let eventClasses = await prisma.eventClass.findMany({
+ where: {
+ environmentId: environmentId,
+ },
+ include: {
+ _count: {
+ select: {
+ events: true,
+ },
+ },
+ },
+ });
+ eventClasses.sort((first, second) => {
+ return first.createdAt.getTime() - second.createdAt.getTime();
+ });
+
+ const transformedEventClasses: TranformEventClassOutput[] = eventClasses
+ .map(transformPrismaEventClass)
+ .filter(
+ (eventClass: TranformEventClassOutput | null): eventClass is TranformEventClassOutput =>
+ eventClass !== null
+ );
+
+ return transformedEventClasses;
+ } catch (error) {
+ throw new DatabaseError(`Database error when fetching webhooks for environment ${environmentId}`);
+ }
+};