Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server Rendering (previously Client): ActionClass & AttributeClass Overviews #495

Merged
merged 21 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e0fe2fb
feat: server rendering of event actions summary page & server actions
ShubhamPalriwala Jul 7, 2023
05b65da
chore: renaming event to action and minor refactoring
ShubhamPalriwala Jul 7, 2023
2aefd52
fix: logging message
ShubhamPalriwala Jul 10, 2023
694efda
delete: unnecessary file
ShubhamPalriwala Jul 10, 2023
ba488cd
feat: migrate attributes overview page
ShubhamPalriwala Jul 10, 2023
8b433f9
feat: impl grouped page & layout, logically differentiate attributes …
ShubhamPalriwala Jul 11, 2023
5271a0c
pnpm format
mattinannt Jul 11, 2023
42e7eee
fix: logical addressing of dirs and minot bugs
ShubhamPalriwala Jul 17, 2023
9b0015c
move: actionsAndAttributes navbar to dedicated dir from components
ShubhamPalriwala Jul 17, 2023
028929d
fix: use server-only build-time checks and move actionsAttributes navbar
ShubhamPalriwala Jul 17, 2023
b4eeffb
revert: unnecessary docker compose changes
ShubhamPalriwala Jul 17, 2023
599f26c
resolve merge conflicts dynamically
ShubhamPalriwala Jul 17, 2023
d442406
fix: address feedback comments
ShubhamPalriwala Jul 18, 2023
b236ce6
use sparkles icon from heroicons
mattinannt Jul 18, 2023
8f0291f
fix updated action not updating in table
mattinannt Jul 18, 2023
4b8e5b4
remove async from client function due to warning
mattinannt Jul 18, 2023
ce4d7e0
move router.refresh in AddNoActionModal
mattinannt Jul 18, 2023
e43aab2
small rename
mattinannt Jul 18, 2023
6d33a28
feat: replace swr w server action in ActionSettingsTab
ShubhamPalriwala Jul 18, 2023
e4d3961
replace custom error with ResourceNotFoundError error class
mattinannt Jul 18, 2023
fc55658
merge changes from main
mattinannt Jul 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import SecondNavbar from "../environments/SecondNavBar";
import SecondNavbar from "@/components/environments/SecondNavBar";
import { CursorArrowRaysIcon, TagIcon } from "@heroicons/react/24/solid";

interface EventsAttributesTabsProps {
interface ActionsAttributesTabsProps {
activeId: string;
environmentId: string;
}

export default function EventsAttributesTabs({ activeId, environmentId }: EventsAttributesTabsProps) {
export default function ActionsAttributesTabs({ activeId, environmentId }: ActionsAttributesTabsProps) {
const tabs = [
{
id: "events",
id: "actions",
label: "Actions",
icon: <CursorArrowRaysIcon />,
href: `/environments/${environmentId}/events`,
href: `/environments/${environmentId}/actions`,
},
{
id: "attributes",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { CodeBracketIcon, CursorArrowRaysIcon, SparklesIcon } from "@heroicons/r

interface ActivityTabProps {
environmentId: string;
eventClassId: string;
actionClassId: string;
}

export default function EventActivityTab({ environmentId, eventClassId }: ActivityTabProps) {
const { eventClass, isLoadingEventClass, isErrorEventClass } = useEventClass(environmentId, eventClassId);
export default function EventActivityTab({ environmentId, actionClassId }: ActivityTabProps) {
const { eventClass, isLoadingEventClass, isErrorEventClass } = useEventClass(environmentId, actionClassId);

if (isLoadingEventClass) return <LoadingSpinner />;
if (isErrorEventClass) return <ErrorComponent />;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use client";

import { Button } from "@formbricks/ui";
import { CursorArrowRaysIcon } from "@heroicons/react/24/solid";
import { useState } from "react";
import AddNoCodeActionModal from "./AddNoCodeActionModal";
import ActionDetailModal from "./ActionDetailModal";
import { TActionClass } from "@formbricks/types/v1/actionClasses";

export default function ActionClassesTable({
environmentId,
actionClasses,
children: [TableHeading, actionRows],
}: {
environmentId: string;
actionClasses: TActionClass[];
children: [JSX.Element, JSX.Element[]];
}) {
const [isActionDetailModalOpen, setActionDetailModalOpen] = useState(false);
const [isAddActionModalOpen, setAddActionModalOpen] = useState(false);

const [activeActionClass, setActiveActionClass] = useState<TActionClass>({
environmentId,
id: "",
name: "",
type: "noCode",
description: "",
noCodeConfig: null,
createdAt: new Date(),
updatedAt: new Date(),
});

const handleOpenActionDetailModalClick = (e, actionClass: TActionClass) => {
e.preventDefault();
setActiveActionClass(actionClass);
setActionDetailModalOpen(true);
};

return (
<>
<div className="mb-6 text-right">
<Button
variant="darkCTA"
onClick={() => {
setAddActionModalOpen(true);
}}>
<CursorArrowRaysIcon className="mr-2 h-5 w-5 text-white" />
Add Action
</Button>
</div>
<div className="rounded-lg border border-slate-200">
{TableHeading}
<div className="grid-cols-7">
{actionClasses.map((actionClass, index) => (
<button
onClick={(e) => {
handleOpenActionDetailModalClick(e, actionClass);
}}
className="w-full"
key={actionClass.id}>
{actionRows[index]}
</button>
))}
</div>
</div>
<ActionDetailModal
environmentId={environmentId}
open={isActionDetailModalOpen}
setOpen={setActionDetailModalOpen}
actionClass={activeActionClass}
/>
<AddNoCodeActionModal
environmentId={environmentId}
open={isAddActionModalOpen}
setOpen={setAddActionModalOpen}
/>
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import ModalWithTabs from "@/components/shared/ModalWithTabs";
import { CodeBracketIcon, CursorArrowRaysIcon, SparklesIcon } from "@heroicons/react/24/solid";
import type { EventClass } from "@prisma/client";
import EventActivityTab from "./EventActivityTab";
import EventSettingsTab from "./EventSettingsTab";
import EventActivityTab from "./ActionActivityTab";
import ActionSettingsTab from "./ActionSettingsTab";
import { TActionClass } from "@formbricks/types/v1/actionClasses";

interface EventDetailModalProps {
interface ActionDetailModalProps {
environmentId: string;
open: boolean;
setOpen: (v: boolean) => void;
eventClass: EventClass;
actionClass: TActionClass;
}

export default function EventDetailModal({
export default function ActionDetailModal({
environmentId,
open,
setOpen,
eventClass,
}: EventDetailModalProps) {
actionClass,
}: ActionDetailModalProps) {
const tabs = [
{
title: "Activity",
children: <EventActivityTab environmentId={environmentId} eventClassId={eventClass.id} />,
children: <EventActivityTab environmentId={environmentId} actionClassId={actionClass.id} />,
},
{
title: "Settings",
children: (
<EventSettingsTab environmentId={environmentId} eventClassId={eventClass.id} setOpen={setOpen} />
<ActionSettingsTab environmentId={environmentId} actionClass={actionClass} setOpen={setOpen} />
),
},
];
Expand All @@ -37,16 +37,16 @@ export default function EventDetailModal({
setOpen={setOpen}
tabs={tabs}
icon={
eventClass.type === "code" ? (
actionClass.type === "code" ? (
<CodeBracketIcon />
) : eventClass.type === "noCode" ? (
) : actionClass.type === "noCode" ? (
<CursorArrowRaysIcon />
) : eventClass.type === "automatic" ? (
) : actionClass.type === "automatic" ? (
<SparklesIcon />
) : null
}
label={eventClass.name}
description={eventClass.description || ""}
label={actionClass.name}
description={actionClass.description || ""}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { timeSinceConditionally } from "@formbricks/lib/time";
import { TActionClass } from "@formbricks/types/v1/actionClasses";
import { CodeBracketIcon, CursorArrowRaysIcon, SparklesIcon } from "@heroicons/react/24/solid";

export default function ActionClassDataRow({ actionClass }: { actionClass: TActionClass }) {
return (
<div className="m-2 grid h-16 grid-cols-6 content-center rounded-lg hover:bg-slate-100">
<div className="col-span-4 flex items-center pl-6 text-sm">
<div className="flex items-center">
<div className="h-5 w-5 flex-shrink-0 text-slate-500">
{actionClass.type === "code" ? (
<CodeBracketIcon />
) : actionClass.type === "noCode" ? (
<CursorArrowRaysIcon />
) : actionClass.type === "automatic" ? (
<SparklesIcon />
) : null}
</div>
<div className="ml-4 text-left">
<div className="font-medium text-slate-900">{actionClass.name}</div>
<div className="text-xs text-slate-400">{actionClass.description}</div>
</div>
</div>
</div>
<div className="col-span-2 my-auto whitespace-nowrap text-center text-sm text-slate-500">
{timeSinceConditionally(actionClass.createdAt.toString())}
</div>
<div className="text-center"></div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"use client";

import DeleteDialog from "@/components/shared/DeleteDialog";
import LoadingSpinner from "@/components/shared/LoadingSpinner";
import { deleteEventClass, useEventClass, useEventClasses } from "@/lib/eventClasses/eventClasses";
import { useEventClassMutation } from "@/lib/eventClasses/mutateEventClasses";
import type { Event, NoCodeConfig } from "@formbricks/types/events";
import type { NoCodeConfig } from "@formbricks/types/events";
import {
Button,
ErrorComponent,
Input,
Label,
RadioGroup,
Expand All @@ -18,46 +16,46 @@ import {
} from "@formbricks/ui";
import { TrashIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
import { testURLmatch } from "./testURLmatch";
import { deleteActionClass, updateActionClass } from "@formbricks/lib/services/actionClass";
import { TActionClassInput } from "@formbricks/types/v1/actionClasses";

interface EventSettingsTabProps {
interface ActionSettingsTabProps {
environmentId: string;
eventClassId: string;
actionClass: any;
setOpen: (v: boolean) => void;
}

export default function EventSettingsTab({ environmentId, eventClassId, setOpen }: EventSettingsTabProps) {
const { eventClass, isLoadingEventClass, isErrorEventClass } = useEventClass(environmentId, eventClassId);
export default function ActionSettingsTab({ environmentId, actionClass, setOpen }: ActionSettingsTabProps) {
const router = useRouter();
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);

const { register, handleSubmit, control, watch } = useForm({
defaultValues: {
name: eventClass.name,
description: eventClass.description,
noCodeConfig: eventClass.noCodeConfig,
name: actionClass.name,
description: actionClass.description,
noCodeConfig: actionClass.noCodeConfig,
},
});
const { triggerEventClassMutate, isMutatingEventClass } = useEventClassMutation(
environmentId,
eventClass.id
);

const { mutateEventClasses } = useEventClasses(environmentId);
const [isUpdatingAction, setIsUpdatingAction] = useState(false);

const onSubmit = async (data) => {
const filteredNoCodeConfig = filterNoCodeConfig(data.noCodeConfig as NoCodeConfig);

const updatedData: Event = {
const updatedData: TActionClassInput = {
...data,
noCodeConfig: filteredNoCodeConfig,
type: "noCode",
} as Event;
} as TActionClassInput;

await triggerEventClassMutate(updatedData);
mutateEventClasses();
setIsUpdatingAction(true);
await updateActionClass(environmentId, actionClass.id, updatedData);
router.refresh();
setIsUpdatingAction(false);
setOpen(false);
};

Expand All @@ -83,9 +81,6 @@ export default function EventSettingsTab({ environmentId, eventClassId, setOpen
if (match === "no") toast.error("Your survey would not be shown.");
};

if (isLoadingEventClass) return <LoadingSpinner />;
if (isErrorEventClass) return <ErrorComponent />;

return (
<div>
<form className="space-y-4" onSubmit={handleSubmit(onSubmit)}>
Expand All @@ -95,8 +90,8 @@ export default function EventSettingsTab({ environmentId, eventClassId, setOpen
type="text"
placeholder="e.g. Product Team Info"
{...register("name", {
value: eventClass.name,
disabled: eventClass.type === "automatic" || eventClass.type === "code" ? true : false,
value: actionClass.name,
disabled: actionClass.type === "automatic" || actionClass.type === "code" ? true : false,
})}
/>
</div>
Expand All @@ -106,18 +101,18 @@ export default function EventSettingsTab({ environmentId, eventClassId, setOpen
type="text"
placeholder="e.g. Triggers when user changed subscription"
{...register("description", {
value: eventClass.description,
disabled: eventClass.type === "automatic" ? true : false,
value: actionClass.description,
disabled: actionClass.type === "automatic" ? true : false,
})}
/>
</div>
<div className="">
<Label>Action Type</Label>
{eventClass.type === "code" ? (
{actionClass.type === "code" ? (
<p className="text-sm text-slate-600">
This is a code action. Please make changes in your code base.
</p>
) : eventClass.type === "noCode" ? (
) : actionClass.type === "noCode" ? (
<div className="flex justify-between rounded-lg">
<div className="w-full space-y-4">
<Controller
Expand Down Expand Up @@ -258,15 +253,15 @@ export default function EventSettingsTab({ environmentId, eventClassId, setOpen
)}
</div>
</div>
) : eventClass.type === "automatic" ? (
) : actionClass.type === "automatic" ? (
<p className="text-sm text-slate-600">
This action was created automatically. You cannot make changes to it.
</p>
) : null}
</div>
<div className="flex justify-between border-t border-slate-200 py-6">
<div>
{eventClass.type !== "automatic" && (
{actionClass.type !== "automatic" && (
<Button
type="button"
variant="warn"
Expand All @@ -281,9 +276,9 @@ export default function EventSettingsTab({ environmentId, eventClassId, setOpen
Read Docs
</Button>
</div>
{eventClass.type !== "automatic" && (
{actionClass.type !== "automatic" && (
<div className="flex space-x-2">
<Button type="submit" variant="darkCTA" loading={isMutatingEventClass}>
<Button type="submit" variant="darkCTA" loading={isUpdatingAction}>
Save changes
</Button>
</div>
Expand All @@ -298,8 +293,8 @@ export default function EventSettingsTab({ environmentId, eventClassId, setOpen
onDelete={async () => {
setOpen(false);
try {
await deleteEventClass(environmentId, eventClass.id);
mutateEventClasses();
await deleteActionClass(environmentId, actionClass.id);
router.refresh();
toast.success("Action deleted successfully");
} catch (error) {
toast.error("Something went wrong. Please try again.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function ActionTableHeading() {
return (
<>
<div className="grid h-12 grid-cols-6 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
<span className="sr-only">Edit</span>
<div className="col-span-4 pl-6 ">User Actions</div>
<div className="col-span-2 text-center">Created</div>
</div>
</>
);
}
Loading