fixed merge conflict

This commit is contained in:
Piyush Gupta
2023-07-18 19:40:01 +05:30
191 changed files with 831 additions and 567 deletions

View File

@@ -5,7 +5,10 @@ import Link from "next/link";
export const GitHubSponsorship: React.FC = () => {
return (
<div className="xs:mx-auto xs:w-full relative mx-auto my-4 mb-12 mt-12 rounded-xl bg-gradient-to-br from-slate-100 to-slate-200 px-4 py-8 dark:from-slate-800 dark:via-slate-800 dark:to-slate-700 sm:px-6 sm:pb-12 sm:pt-8 md:max-w-none lg:mt-6 lg:px-8 lg:pt-8 ">
<div className="mx-4 my-4 mb-12 mt-12 rounded-xl bg-gradient-to-br from-slate-100 to-slate-200 px-4 py-8 dark:from-slate-800 dark:via-slate-800 dark:to-slate-700 sm:px-6 sm:pb-12 sm:pt-8 md:max-w-none lg:mt-6 lg:px-8 lg:pt-8">
<style jsx>{`
@media (min-width: 426px);
`}</style>
<div className="right-10 lg:absolute">
<Image
src={GitHubMarkDark}

View File

@@ -22,7 +22,7 @@ export const Hero: React.FC = ({}) => {
<a
href="https://github.com/formbricks/formbricks"
target="_blank"
className="border-brand-dark rounded-full border px-6 py-1.5 text-sm text-slate-500 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800">
className="border-brand-dark rounded-full border px-4 py-1.5 text-sm text-slate-500 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800">
We&apos;re Open-Source | Star us on GitHub{" "}
<ChevronRightIcon className="inline h-5 w-5 text-slate-300" />
</a>
@@ -43,7 +43,7 @@ export const Hero: React.FC = ({}) => {
<p className="hidden whitespace-nowrap pt-3 text-xs text-slate-400 dark:text-slate-500 md:block">
Trusted by
</p>
<div className="grid grid-cols-3 items-center gap-8 pt-2 md:grid-cols-4">
<div className="grid grid-cols-4 items-center gap-5 pt-2 md:gap-8">
<Image
src={CalLogoLight}
alt="Cal Logo"

View File

@@ -42,7 +42,7 @@ Add the following script to the `<head>` tag of your HTML file:
var t = document.createElement("script");
(t.type = "text/javascript"),
(t.async = !0),
(t.src = "https://unpkg.com/@formbricks/js@^0.1.17/dist/index.umd.js");
(t.src = "https://unpkg.com/@formbricks/js@^1.0.0/dist/index.umd.js");
var e = document.getElementsByTagName("script")[0];
e.parentNode.insertBefore(t, e),
setTimeout(function () {

View File

@@ -5,14 +5,6 @@ import { formbricksEnabled } from "@/lib/formbricks";
import formbricks from "@formbricks/js";
import { useEffect } from "react";
/* if (typeof window !== "undefined" && formbricksEnabled) {
formbricks.init({
environmentId: env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID || "",
apiHost: env.NEXT_PUBLIC_FORMBRICKS_API_HOST || "",
debug: true,
});
} */
export default function FormbricksClient({ session }) {
useEffect(() => {
if (formbricksEnabled && session.user && formbricks) {

View File

@@ -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",

View File

@@ -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 />;

View File

@@ -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}
/>
</>
);
}

View File

@@ -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} />
),
},
];
@@ -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 || ""}
/>
</>
);

View File

@@ -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>
);
}

View File

@@ -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,
@@ -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);
};
@@ -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)}>
@@ -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>
@@ -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
@@ -258,7 +253,7 @@ 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>
@@ -266,7 +261,7 @@ export default function EventSettingsTab({ environmentId, eventClassId, setOpen
</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"
@@ -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>
@@ -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.");

View File

@@ -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>
</>
);
}

View File

@@ -1,8 +1,6 @@
"use client";
import Modal from "@/components/shared/Modal";
import { createEventClass } from "@/lib/eventClasses/eventClasses";
import type { Event, NoCodeConfig } from "@formbricks/types/events";
import {
Button,
Input,
@@ -21,24 +19,22 @@ import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { testURLmatch } from "./testURLmatch";
import { createActionClass } from "@formbricks/lib/services/actionClass";
import { TActionClassInput, TActionClassNoCodeConfig } from "@formbricks/types/v1/actionClasses";
import { useRouter } from "next/navigation";
interface EventDetailModalProps {
interface AddNoCodeActionModalProps {
environmentId: string;
open: boolean;
setOpen: (v: boolean) => void;
mutateEventClasses: (data?: any) => void;
}
export default function AddNoCodeEventModal({
environmentId,
open,
setOpen,
mutateEventClasses,
}: EventDetailModalProps) {
export default function AddNoCodeActionModal({ environmentId, open, setOpen }: AddNoCodeActionModalProps) {
const router = useRouter();
const { register, control, handleSubmit, watch, reset } = useForm();
// clean up noCodeConfig before submitting by removing unnecessary fields
const filterNoCodeConfig = (noCodeConfig: NoCodeConfig): NoCodeConfig => {
const filterNoCodeConfig = (noCodeConfig: TActionClassNoCodeConfig): TActionClassNoCodeConfig => {
const { type } = noCodeConfig;
return {
type,
@@ -46,18 +42,18 @@ export default function AddNoCodeEventModal({
};
};
const submitEventClass = async (data: Partial<Event>): Promise<void> => {
const filteredNoCodeConfig = filterNoCodeConfig(data.noCodeConfig as NoCodeConfig);
const submitEventClass = async (data: Partial<TActionClassInput>): Promise<void> => {
const filteredNoCodeConfig = filterNoCodeConfig(data.noCodeConfig as TActionClassNoCodeConfig);
const updatedData: Event = {
const updatedData: TActionClassInput = {
...data,
noCodeConfig: filteredNoCodeConfig,
type: "noCode",
} as Event;
} as TActionClassInput;
try {
await createEventClass(environmentId, updatedData);
mutateEventClasses();
await createActionClass(environmentId, updatedData);
router.refresh();
reset();
setOpen(false);
toast.success("Action added successfully.");

View File

@@ -0,0 +1,11 @@
import ActionsAttributesTabs from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/ActionsAttributesTabs";
import ContentWrapper from "@/components/shared/ContentWrapper";
export default function ActionsAndAttributesLayout({ params, children }) {
return (
<>
<ActionsAttributesTabs activeId="actions" environmentId={params.environmentId} />
<ContentWrapper>{children}</ContentWrapper>
</>
);
}

View File

@@ -0,0 +1,47 @@
import { Button } from "@formbricks/ui";
import { CursorArrowRaysIcon } from "@heroicons/react/24/solid";
export default function Loading() {
return (
<>
<div className="mb-6 text-right">
<Button
variant="darkCTA"
className="pointer-events-none animate-pulse cursor-not-allowed select-none bg-gray-200">
<CursorArrowRaysIcon className="mr-2 h-5 w-5 text-white" />
Loading
</Button>
</div>
<div className="rounded-lg border border-slate-200">
<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>
</div>
{[...Array(3)].map((_, index) => (
<div key={index} 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-6 w-6 flex-shrink-0 animate-pulse rounded-full bg-gray-200 text-slate-500"></div>
<div className="ml-4 text-left">
<div className="font-medium text-slate-900">
<div className="mt-0 h-4 w-48 animate-pulse rounded-full bg-gray-200"></div>
</div>
<div className="mt-1 text-xs text-slate-400">
<div className="h-2 w-24 animate-pulse rounded-full bg-gray-200"></div>
</div>
</div>
</div>
</div>
<div className="col-span-2 my-auto whitespace-nowrap text-center text-sm text-slate-500">
<div className="m-28 h-4 animate-pulse rounded-full bg-gray-200"></div>
</div>
<div className="text-center"></div>
</div>
))}
</>
);
}

View File

@@ -0,0 +1,26 @@
export const revalidate = REVALIDATION_INTERVAL;
import ActionClassesTable from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/ActionClassesTable";
import ActionClassDataRow from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/ActionRowData";
import ActionTableHeading from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/ActionTableHeading";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getActionClasses } from "@formbricks/lib/services/actionClass";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Actions",
};
export default async function ActionClassesComponent({ params }) {
let actionClasses = await getActionClasses(params.environmentId);
return (
<>
<ActionClassesTable environmentId={params.environmentId} actionClasses={actionClasses}>
<ActionTableHeading />
{actionClasses.map((actionClass) => (
<ActionClassDataRow key={actionClass.id} actionClass={actionClass} />
))}
</ActionClassesTable>
</>
);
}

View File

@@ -0,0 +1,81 @@
"use client";
import { Switch } from "@formbricks/ui";
import { useState } from "react";
import AttributeDetailModal from "./AttributeDetailModal";
import UploadAttributesModal from "./UploadAttributesModal";
import { useMemo } from "react";
import { TAttributeClass } from "@formbricks/types/v1/attributeClasses";
export default function AttributeClassesTable({
environmentId,
attributeClasses,
children: [TableHeading, howToAddAttributeButton, attributeRows],
}: {
environmentId: string;
attributeClasses: TAttributeClass[];
children: [JSX.Element, JSX.Element, JSX.Element[]];
}) {
const [isAttributeDetailModalOpen, setAttributeDetailModalOpen] = useState(false);
const [isUploadCSVModalOpen, setUploadCSVModalOpen] = useState(false);
const [activeAttributeClass, setActiveAttributeClass] = useState("" as any);
const [showArchived, setShowArchived] = useState(false);
const displayedAttributeClasses = useMemo(() => {
return attributeClasses
? showArchived
? attributeClasses
: attributeClasses.filter((ac) => !ac.archived)
: [];
}, [showArchived, attributeClasses]);
const hasArchived = useMemo(() => {
return attributeClasses ? attributeClasses.some((ac) => ac.archived) : false;
}, [attributeClasses]);
const handleOpenAttributeDetailModalClick = (e, attributeClass) => {
e.preventDefault();
setActiveAttributeClass(attributeClass);
setAttributeDetailModalOpen(true);
};
const toggleShowArchived = () => {
setShowArchived(!showArchived);
};
return (
<>
<div className="mb-6 flex items-center justify-end text-right">
{hasArchived && (
<div className="flex items-center text-sm font-medium">
Show archived
<Switch className="mx-3" checked={showArchived} onCheckedChange={toggleShowArchived} />
</div>
)}
{howToAddAttributeButton}
</div>
<div className="rounded-lg border border-slate-200">
{TableHeading}
<div className="grid-cols-7">
{displayedAttributeClasses.map((attributeClass, index) => (
<button
onClick={(e) => {
handleOpenAttributeDetailModalClick(e, attributeClass);
}}
className="w-full"
key={attributeClass.id}>
{attributeRows[index]}
</button>
))}
</div>
<AttributeDetailModal
environmentId={environmentId}
open={isAttributeDetailModalOpen}
setOpen={setAttributeDetailModalOpen}
attributeClass={activeAttributeClass}
/>
<UploadAttributesModal open={isUploadCSVModalOpen} setOpen={setUploadCSVModalOpen} />
</div>
</>
);
}

View File

@@ -27,7 +27,6 @@ export default function AttributeDetailModal({
children: (
<AttributeSettingsTab
attributeClass={attributeClass}
environmentId={environmentId}
setOpen={setOpen}
/>
),

View File

@@ -0,0 +1,33 @@
import { timeSinceConditionally } from "@formbricks/lib/time";
import { Badge } from "@formbricks/ui";
import { TagIcon } from "@heroicons/react/24/solid";
export default function AttributeClassDataRow({ attributeClass }) {
return (
<div className="m-2 grid h-16 grid-cols-5 content-center rounded-lg hover:bg-slate-100">
<div className="col-span-3 flex items-center pl-6 text-sm">
<div className="flex items-center">
<div className="h-10 w-10 flex-shrink-0">
<TagIcon className="h-8 w-8 flex-shrink-0 text-slate-500" />
</div>
<div className="ml-4 text-left">
<div className="font-medium text-slate-900">
{attributeClass.name}
<span className="ml-2">
{attributeClass.archived && <Badge text="Archived" type="gray" size="tiny" />}
</span>
</div>
<div className="text-xs text-slate-400">{attributeClass.description}</div>
</div>
</div>
</div>
<div className="my-auto whitespace-nowrap text-center text-sm text-slate-500">
<div className="text-slate-900">{timeSinceConditionally(attributeClass.createdAt.toString())}</div>
</div>
<div className="my-auto whitespace-nowrap text-center text-sm text-slate-500">
<div className="text-slate-900">{timeSinceConditionally(attributeClass.updatedAt.toString())}</div>
</div>
</div>
);
}

View File

@@ -1,41 +1,38 @@
import { useAttributeClasses } from "@/lib/attributeClasses/attributeClasses";
import { useAttributeClassMutation } from "@/lib/attributeClasses/mutateAttributeClasses";
"use client";
import { Button, Input, Label } from "@formbricks/ui";
import type { AttributeClass } from "@prisma/client";
import { useForm } from "react-hook-form";
import { ArchiveBoxArrowDownIcon, ArchiveBoxXMarkIcon } from "@heroicons/react/24/solid";
import { useRouter } from "next/navigation";
import { updatetAttributeClass } from "@formbricks/lib/services/attributeClass";
import { useState } from "react";
interface AttributeSettingsTabProps {
environmentId: string;
attributeClass: AttributeClass;
setOpen: (v: boolean) => void;
}
export default function AttributeSettingsTab({
environmentId,
attributeClass,
setOpen,
}: AttributeSettingsTabProps) {
export default function AttributeSettingsTab({ attributeClass, setOpen }: AttributeSettingsTabProps) {
const router = useRouter();
const { register, handleSubmit } = useForm({
defaultValues: { name: attributeClass.name, description: attributeClass.description },
});
const { triggerAttributeClassMutate, isMutatingAttributeClass } = useAttributeClassMutation(
environmentId,
attributeClass.id
);
const { mutateAttributeClasses } = useAttributeClasses(environmentId);
const [isAttributeBeingSubmitted, setisAttributeBeingSubmitted] = useState(false);
const onSubmit = async (data) => {
await triggerAttributeClassMutate(data);
mutateAttributeClasses();
setisAttributeBeingSubmitted(true);
setOpen(false);
await updatetAttributeClass(attributeClass.id, data);
router.refresh();
setisAttributeBeingSubmitted(false);
};
const handleArchiveToggle = async () => {
setisAttributeBeingSubmitted(true);
const data = { archived: !attributeClass.archived };
await triggerAttributeClassMutate(data);
mutateAttributeClasses();
await updatetAttributeClass(attributeClass.id, data);
setisAttributeBeingSubmitted(false);
};
return (
@@ -101,7 +98,7 @@ export default function AttributeSettingsTab({
</div>
{attributeClass.type !== "automatic" && (
<div className="flex space-x-2">
<Button type="submit" variant="darkCTA" loading={isMutatingAttributeClass}>
<Button type="submit" variant="darkCTA" loading={isAttributeBeingSubmitted}>
Save changes
</Button>
</div>

View File

@@ -0,0 +1,11 @@
export default function AttributeTableHeading() {
return (
<>
<div className="grid h-12 grid-cols-5 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
<div className="col-span-3 pl-6 ">Name</div>
<div className="text-center">Created</div>
<div className="text-center">Last Updated</div>
</div>
</>
);
}

View File

@@ -0,0 +1,14 @@
import { Button } from "@formbricks/ui";
import { QuestionMarkCircleIcon } from "@heroicons/react/24/solid";
export default function HowToAddAttributesButton() {
return (
<Button
variant="secondary"
href="http://formbricks.com/docs/attributes/custom-attributes"
target="_blank">
<QuestionMarkCircleIcon className="mr-2 h-4 w-4" />
How to add attributes
</Button>
);
}

View File

@@ -0,0 +1,11 @@
import ActionsAttributesTabs from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/ActionsAttributesTabs";
import ContentWrapper from "@/components/shared/ContentWrapper";
export default function ActionsAndAttributesLayout({ params, children }) {
return (
<>
<ActionsAttributesTabs activeId="attributes" environmentId={params.environmentId} />
<ContentWrapper>{children}</ContentWrapper>
</>
);
}

View File

@@ -0,0 +1,55 @@
import { Button } from "@formbricks/ui";
import { TagIcon } from "@heroicons/react/24/solid";
import { QuestionMarkCircleIcon } from "@heroicons/react/24/solid";
export default function Loading() {
return (
<>
<div className="mb-6 text-right">
<div className="mb-6 flex items-center justify-end text-right">
<Button
variant="secondary"
className="pointer-events-none animate-pulse cursor-not-allowed select-none">
<QuestionMarkCircleIcon className="mr-2 h-4 w-4" />
Loading Attributes
</Button>
</div>
</div>
<div className="rounded-lg border border-slate-200">
<div className="grid h-12 grid-cols-5 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
<div className="col-span-3 pl-6 ">Name</div>
<div className="text-center">Created</div>
<div className="text-center">Last Updated</div>
</div>
</div>
{[...Array(3)].map((_, index) => (
<div key={index} className="m-2 grid h-16 grid-cols-5 content-center rounded-lg hover:bg-slate-100">
<div className="col-span-3 flex items-center pl-6 text-sm">
<div className="flex items-center">
<div className="h-10 w-10 flex-shrink-0">
<TagIcon className="h-8 w-8 flex-shrink-0 animate-pulse text-slate-500" />
</div>
<div className="ml-4 text-left">
<div className="font-medium text-slate-900">
<div className="mt-0 h-4 w-48 animate-pulse rounded-full bg-gray-200"></div>
</div>
<div className="mt-1 text-xs text-slate-400">
<div className="h-2 w-24 animate-pulse rounded-full bg-gray-200"></div>
</div>
</div>
</div>
</div>
<div className="my-auto whitespace-nowrap text-center text-sm text-slate-500">
<div className="m-4 h-4 animate-pulse rounded-full bg-gray-200"></div>
</div>
<div className="my-auto whitespace-nowrap text-center text-sm text-slate-500">
<div className="m-4 h-4 animate-pulse rounded-full bg-gray-200"></div>
</div>
</div>
))}
</>
);
}

View File

@@ -0,0 +1,29 @@
export const revalidate = REVALIDATION_INTERVAL;
import AttributeClassesTable from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/AttributeClassesTable";
import AttributeClassDataRow from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/AttributeRowData";
import AttributeTableHeading from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/AttributeTableHeading";
import HowToAddAttributesButton from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/attributes/HowToAddAttributesButton";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getAttributeClasses } from "@formbricks/lib/services/attributeClass";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Attributes",
};
export default async function AttributesPage({ params }) {
let attributeClasses = await getAttributeClasses(params.environmentId);
return (
<>
<AttributeClassesTable environmentId={params.environmentId} attributeClasses={attributeClasses}>
<AttributeTableHeading />
<HowToAddAttributesButton />
{attributeClasses.map((attributeClass) => (
<AttributeClassDataRow key={attributeClass.id} attributeClass={attributeClass} />
))}
</AttributeClassesTable>
</>
);
}

View File

@@ -117,9 +117,9 @@ export default function EnvironmentsNavbar({ environmentId, session }: Environme
},
{
name: "Actions & Attributes",
href: `/environments/${environmentId}/events`,
href: `/environments/${environmentId}/actions`,
icon: FilterIcon,
current: pathname?.includes("/events") || pathname?.includes("/attributes"),
current: pathname?.includes("/actions") || pathname?.includes("/attributes"),
},
{
name: "Integrations",
@@ -221,7 +221,7 @@ export default function EnvironmentsNavbar({ environmentId, session }: Environme
<nav className="top-0 z-10 w-full border-b border-slate-200 bg-white">
{environment?.type === "development" && (
<div className="h-6 w-full bg-[#A33700] p-0.5 text-center text-sm text-white">
You&apos;re in development mode. Use it to test surveys, events and attributes.
You&apos;re in development mode. Use it to test surveys, actions and attributes.
</div>
)}

View File

@@ -3,8 +3,8 @@
import {
QuestionOption,
QuestionOptions,
} from "@/app/environments/[environmentId]/surveys/[surveyId]/QuestionsComboBox";
import { QuestionFilterOptions } from "@/app/environments/[environmentId]/surveys/[surveyId]/ResponseFilter";
} from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/QuestionsComboBox";
import { QuestionFilterOptions } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/ResponseFilter";
import { getTodayDate } from "@/lib/surveys/surveys";
import { createContext, useContext, useState } from "react";

View File

@@ -1,12 +1,10 @@
import EnvironmentsNavbar from "@/app/environments/[environmentId]/EnvironmentsNavbar";
import EnvironmentsNavbar from "@/app/(app)/environments/[environmentId]/EnvironmentsNavbar";
import ToasterClient from "@/components/ToasterClient";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import PosthogIdentify from "./PosthogIdentify";
import FormbricksClient from "../../FormbricksClient";
import { PosthogClientWrapper } from "../../PosthogClientWrapper";
import { ResponseFilterProvider } from "@/app/environments/[environmentId]/ResponseFilterContext";
import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]/ResponseFilterContext";
import { hasUserEnvironmentAccess } from "@/lib/api/apiHelper";
export default async function EnvironmentLayout({ children, params }) {
@@ -22,16 +20,13 @@ export default async function EnvironmentLayout({ children, params }) {
return (
<>
<ResponseFilterProvider>
<PosthogIdentify session={session} />
<FormbricksClient session={session} />
<ToasterClient />
<EnvironmentsNavbar environmentId={params.environmentId} session={session} />
<PosthogClientWrapper>
<main className="h-full flex-1 overflow-y-auto bg-slate-50">
{children}
<main />
</main>
</PosthogClientWrapper>
<main className="h-full flex-1 overflow-y-auto bg-slate-50">
{children}
<main />
</main>
</ResponseFilterProvider>
</>
);

View File

@@ -1,6 +1,6 @@
"use client";
import ShareInviteModal from "@/app/environments/[environmentId]/settings/members/ShareInviteModal";
import ShareInviteModal from "@/app/(app)/environments/[environmentId]/settings/members/ShareInviteModal";
import DeleteDialog from "@/components/shared/DeleteDialog";
import LoadingSpinner from "@/components/shared/LoadingSpinner";
import CreateTeamModal from "@/components/team/CreateTeamModal";

View File

@@ -1,5 +1,5 @@
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import SettingsCard from "@/app/environments/[environmentId]/settings/SettingsCard";
import SettingsCard from "@/app/(app)/environments/[environmentId]/settings/SettingsCard";
import { prisma } from "@formbricks/database";
import { NotificationSettings } from "@formbricks/types/users";
import { getServerSession } from "next-auth";

View File

@@ -104,7 +104,7 @@ if (typeof window !== "undefined") {
</p>
<CodeBlock language="js">{`<!-- START Formbricks Surveys -->
<script type="text/javascript">
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^0.1.17/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks=window.js;window.formbricks.init({environmentId: "${environmentId}", apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^1.0.0/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks=window.js;window.formbricks.init({environmentId: "${environmentId}", apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
</script>
<!-- END Formbricks Surveys -->`}</CodeBlock>
<p className="text-lg font-semibold text-slate-800">You&apos;re done 🎉</p>

View File

@@ -1,6 +1,6 @@
"use client";
import MergeTagsCombobox from "@/app/environments/[environmentId]/settings/tags/MergeTagsCombobox";
import MergeTagsCombobox from "@/app/(app)/environments/[environmentId]/settings/tags/MergeTagsCombobox";
import EmptySpaceFiller from "@/components/shared/EmptySpaceFiller";
import LoadingSpinner from "@/components/shared/LoadingSpinner";
import { useDeleteTag, useMergeTags, useUpdateTag } from "@/lib/tags/mutateTags";

View File

@@ -1,4 +1,4 @@
import { getAnalysisData } from "@/app/environments/[environmentId]/surveys/[surveyId]/(analysis)/data";
import { getAnalysisData } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/data";
import { RESPONSES_LIMIT_FREE } from "@formbricks/lib/constants";
import Link from "next/link";

View File

@@ -1,7 +1,7 @@
import { Metadata } from "next";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getServerSession } from "next-auth";
import { getAnalysisData } from "@/app/environments/[environmentId]/surveys/[surveyId]/(analysis)/data";
import { getAnalysisData } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/data";
type Props = {
params: { surveyId: string; environmentId: string };

View File

@@ -1,10 +1,10 @@
"use client";
import CustomFilter from "@/app/environments/[environmentId]/surveys/[surveyId]/CustomFilter";
import SummaryHeader from "@/app/environments/[environmentId]/surveys/[surveyId]/SummaryHeader";
import SurveyResultsTabs from "@/app/environments/[environmentId]/surveys/[surveyId]/(analysis)/SurveyResultsTabs";
import ResponseTimeline from "@/app/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/ResponseTimeline";
import CustomFilter from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/CustomFilter";
import SummaryHeader from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/SummaryHeader";
import SurveyResultsTabs from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/SurveyResultsTabs";
import ResponseTimeline from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/ResponseTimeline";
import ContentWrapper from "@/components/shared/ContentWrapper";
import { useResponseFilter } from "@/app/environments/[environmentId]/ResponseFilterContext";
import { useResponseFilter } from "@/app/(app)/environments/[environmentId]/ResponseFilterContext";
import { getFilterResponses } from "@/lib/surveys/surveys";
import { TResponse } from "@formbricks/types/v1/responses";
import { TSurvey } from "@formbricks/types/v1/surveys";

View File

@@ -1,6 +1,6 @@
"use client";
import TagsCombobox from "@/app/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/TagsCombobox";
import TagsCombobox from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/TagsCombobox";
import { removeTagFromResponse, useAddTagToResponse, useCreateTag } from "@/lib/tags/mutateTags";
import { useTagsForEnvironment } from "@/lib/tags/tags";
import React, { useEffect, useState } from "react";

View File

@@ -1,8 +1,8 @@
export const revalidate = REVALIDATION_INTERVAL;
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import ResponsePage from "@/app/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/ResponsePage";
import { getAnalysisData } from "@/app/environments/[environmentId]/surveys/[surveyId]/(analysis)/data";
import ResponsePage from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/ResponsePage";
import { getAnalysisData } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/data";
import { getServerSession } from "next-auth";
import ResponsesLimitReachedBanner from "../ResponsesLimitReachedBanner";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";

View File

@@ -1,6 +1,6 @@
"use client";
import LinkSurveyModal from "@/app/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/LinkSurveyModal";
import LinkSurveyModal from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/LinkSurveyModal";
import { TSurvey } from "@formbricks/types/v1/surveys";
import { Button } from "@formbricks/ui";
import { ShareIcon } from "@heroicons/react/24/outline";

Some files were not shown because too many files have changed in this diff Show More