mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-29 18:00:26 -06:00
fixed merge conflict
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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'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"
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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) {
|
||||
@@ -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",
|
||||
@@ -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 />;
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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 || ""}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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.");
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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.");
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -27,7 +27,6 @@ export default function AttributeDetailModal({
|
||||
children: (
|
||||
<AttributeSettingsTab
|
||||
attributeClass={attributeClass}
|
||||
environmentId={environmentId}
|
||||
setOpen={setOpen}
|
||||
/>
|
||||
),
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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're in development mode. Use it to test surveys, events and attributes.
|
||||
You're in development mode. Use it to test surveys, actions and attributes.
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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're done 🎉</p>
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
|
||||
@@ -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 };
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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
Reference in New Issue
Block a user