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}`); + } +};