mirror of
https://github.com/formbricks/formbricks.git
synced 2026-03-13 11:09:29 -05:00
Merge branch 'main' of github.com:formbricks/formbricks into shubham/for-1142-create-responsive-menu-for-settings
This commit is contained in:
@@ -37,7 +37,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
{
|
||||
name: "Erxes",
|
||||
description:
|
||||
"The Open-Source HubSpot Alternative. A single XOS enables to create unique and life-changing experiences that work for all types of business.",
|
||||
"The Open-Source HubSpot Alternative. A single XOS enables to create unique and life-changing experiences that work for all types of business.",
|
||||
href: "https://erxes.io",
|
||||
},
|
||||
{
|
||||
@@ -46,6 +46,12 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
"Survey granular user segments at any point in the user journey. Gather up to 6x more insights with targeted micro-surveys. All open-source.",
|
||||
href: "https://formbricks.com",
|
||||
},
|
||||
{
|
||||
name: "Ghostfolio",
|
||||
description:
|
||||
"Ghostfolio is a privacy-first, open source dashboard for your personal finances. Designed to simplify asset tracking and empower informed investment decisions.",
|
||||
href: "https://ghostfol.io",
|
||||
},
|
||||
{
|
||||
name: "GitWonk",
|
||||
description:
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"use client";
|
||||
|
||||
import { deletePersonAction } from "@/app/(app)/environments/[environmentId]/people/[personId]/actions";
|
||||
import DeleteDialog from "@/components/shared/DeleteDialog";
|
||||
import { TrashIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface DeletePersonButtonProps {
|
||||
environmentId: string;
|
||||
personId: string;
|
||||
}
|
||||
|
||||
export function DeletePersonButton({ environmentId, personId }: DeletePersonButtonProps) {
|
||||
const router = useRouter();
|
||||
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [isDeletingPerson, setIsDeletingPerson] = useState(false);
|
||||
|
||||
const handleDeletePerson = async () => {
|
||||
try {
|
||||
setIsDeletingPerson(true);
|
||||
await deletePersonAction(personId);
|
||||
router.push(`/environments/${environmentId}/people`);
|
||||
toast.success("Person deleted successfully.");
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
} finally {
|
||||
setIsDeletingPerson(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
setDeleteDialogOpen(true);
|
||||
}}>
|
||||
<TrashIcon className="h-5 w-5 text-slate-500 hover:text-red-700" />
|
||||
</button>
|
||||
<DeleteDialog
|
||||
open={deleteDialogOpen}
|
||||
setOpen={setDeleteDialogOpen}
|
||||
deleteWhat="person"
|
||||
onDelete={handleDeletePerson}
|
||||
isDeleting={isDeletingPerson}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,39 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import DeleteDialog from "@/components/shared/DeleteDialog";
|
||||
import GoBackButton from "@/components/shared/GoBackButton";
|
||||
import { deletePersonAction } from "./actions";
|
||||
import { TPerson } from "@formbricks/types/v1/people";
|
||||
import { TrashIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { DeletePersonButton } from "./DeletePersonButton";
|
||||
import { getPerson } from "@formbricks/lib/services/person";
|
||||
|
||||
export default function HeadingSection({
|
||||
environmentId,
|
||||
person,
|
||||
}: {
|
||||
interface HeadingSectionProps {
|
||||
environmentId: string;
|
||||
person: TPerson;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
personId: string;
|
||||
}
|
||||
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [isDeletingPerson, setIsDeletingPerson] = useState(false);
|
||||
|
||||
const handleDeletePerson = async () => {
|
||||
try {
|
||||
setIsDeletingPerson(true);
|
||||
await deletePersonAction(person.id);
|
||||
router.push(`/environments/${environmentId}/people`);
|
||||
toast.success("Person deleted successfully.");
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
} finally {
|
||||
setIsDeletingPerson(false);
|
||||
}
|
||||
};
|
||||
export default async function HeadingSection({ environmentId, personId }: HeadingSectionProps) {
|
||||
const person = await getPerson(personId);
|
||||
|
||||
if (!person) {
|
||||
throw new Error("No such person found");
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<GoBackButton />
|
||||
@@ -42,21 +21,9 @@ export default function HeadingSection({
|
||||
<span>{person.attributes.email || person.id}</span>
|
||||
</h1>
|
||||
<div className="flex items-center space-x-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
setDeleteDialogOpen(true);
|
||||
}}>
|
||||
<TrashIcon className="h-5 w-5 text-slate-500 hover:text-red-700" />
|
||||
</button>
|
||||
<DeletePersonButton environmentId={environmentId} personId={personId} />
|
||||
</div>
|
||||
</div>
|
||||
<DeleteDialog
|
||||
open={deleteDialogOpen}
|
||||
setOpen={setDeleteDialogOpen}
|
||||
deleteWhat="person"
|
||||
onDelete={handleDeletePerson}
|
||||
isDeleting={isDeletingPerson}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
export const revalidate = REVALIDATION_INTERVAL;
|
||||
|
||||
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
|
||||
import { getPerson } from "@formbricks/lib/services/person";
|
||||
import AttributesSection from "@/app/(app)/environments/[environmentId]/people/[personId]/(attributeSection)/AttributesSection";
|
||||
import ActivitySection from "@/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivitySection";
|
||||
import HeadingSection from "@/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection";
|
||||
import AttributesSection from "@/app/(app)/environments/[environmentId]/people/[personId]/(attributeSection)/AttributesSection";
|
||||
import ResponseSection from "@/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponseSection";
|
||||
import HeadingSection from "@/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection";
|
||||
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
|
||||
|
||||
export default async function PersonPage({ params }) {
|
||||
const person = await getPerson(params.personId);
|
||||
if (!person) {
|
||||
throw new Error("No such person found");
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<main className="mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<>
|
||||
<HeadingSection environmentId={params.environmentId} person={person} />
|
||||
<HeadingSection environmentId={params.environmentId} personId={params.personId} />
|
||||
<section className="pb-24 pt-6">
|
||||
<div className="grid grid-cols-1 gap-x-8 md:grid-cols-4">
|
||||
<AttributesSection personId={params.personId} />
|
||||
|
||||
@@ -70,7 +70,7 @@ const SummaryHeader = ({ surveyId, environmentId, survey }: SummaryHeaderProps)
|
||||
</div>
|
||||
<div className="block sm:hidden">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="sm" variant="secondary" className="h-full w-full rounded-md p-2">
|
||||
<EllipsisHorizontalIcon className="h-6" />
|
||||
</Button>
|
||||
|
||||
@@ -52,33 +52,31 @@ export default function SurveyStatusDropdown({
|
||||
{survey.status === "archived" && <p className="text-sm italic text-slate-600">Archived</p>}
|
||||
</div>
|
||||
) : (
|
||||
<TooltipProvider delayDuration={50}>
|
||||
<Tooltip open={isStatusChangeDisabled ? undefined : false}>
|
||||
<TooltipTrigger>
|
||||
<Select
|
||||
disabled={isStatusChangeDisabled}
|
||||
onValueChange={(value) => {
|
||||
triggerSurveyMutate({ status: value })
|
||||
.then(() => {
|
||||
toast.success(
|
||||
value === "inProgress"
|
||||
? "Survey live"
|
||||
: value === "paused"
|
||||
? "Survey paused"
|
||||
: value === "completed"
|
||||
? "Survey completed"
|
||||
: ""
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`Error: ${error.message}`);
|
||||
});
|
||||
<Select
|
||||
disabled={isStatusChangeDisabled}
|
||||
onValueChange={(value) => {
|
||||
triggerSurveyMutate({ status: value })
|
||||
.then(() => {
|
||||
toast.success(
|
||||
value === "inProgress"
|
||||
? "Survey live"
|
||||
: value === "paused"
|
||||
? "Survey paused"
|
||||
: value === "completed"
|
||||
? "Survey completed"
|
||||
: ""
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`Error: ${error.message}`);
|
||||
});
|
||||
|
||||
if (updateLocalSurveyStatus)
|
||||
updateLocalSurveyStatus(
|
||||
value as "draft" | "inProgress" | "paused" | "completed" | "archived"
|
||||
);
|
||||
}}>
|
||||
if (updateLocalSurveyStatus)
|
||||
updateLocalSurveyStatus(value as "draft" | "inProgress" | "paused" | "completed" | "archived");
|
||||
}}>
|
||||
<TooltipProvider delayDuration={50}>
|
||||
<Tooltip open={isStatusChangeDisabled ? undefined : false}>
|
||||
<TooltipTrigger asChild>
|
||||
<SelectTrigger className="w-[170px] bg-white py-6 md:w-[200px]">
|
||||
<SelectValue>
|
||||
<div className="flex items-center">
|
||||
@@ -93,28 +91,29 @@ export default function SurveyStatusDropdown({
|
||||
</div>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-white">
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="inProgress">
|
||||
<PlayCircleIcon className="-mt-1 mr-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
In-progress
|
||||
</SelectItem>
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="paused">
|
||||
<PauseCircleIcon className="-mt-1 mr-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
Paused
|
||||
</SelectItem>
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="completed">
|
||||
<CheckCircleIcon className="-mt-1 mr-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
Completed
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
To update the survey status, update the “Close
|
||||
<br /> survey on date” setting in the Response Options.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</TooltipTrigger>
|
||||
<SelectContent className="bg-white">
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="inProgress">
|
||||
<PlayCircleIcon className="-mt-1 mr-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
In-progress
|
||||
</SelectItem>
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="paused">
|
||||
<PauseCircleIcon className="-mt-1 mr-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
Paused
|
||||
</SelectItem>
|
||||
<SelectItem className="group font-normal hover:text-slate-900" value="completed">
|
||||
<CheckCircleIcon className="-mt-1 mr-1 inline h-5 w-5 text-slate-500 group-hover:text-slate-800" />
|
||||
Completed
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
|
||||
<TooltipContent>
|
||||
To update the survey status, update the “Close
|
||||
<br /> survey on date” setting in the Response Options.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Select>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -328,7 +328,7 @@ model Product {
|
||||
teamId String
|
||||
environments Environment[]
|
||||
brandColor String @default("#64748b")
|
||||
highlightBorderColor String?
|
||||
highlightBorderColor String?
|
||||
recontactDays Int @default(7)
|
||||
formbricksSignature Boolean @default(true)
|
||||
placement WidgetPlacement @default(bottomRight)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const RESPONSES_LIMIT_FREE = 100;
|
||||
export const IS_FORMBRICKS_CLOUD = process.env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD === "1";
|
||||
export const REVALIDATION_INTERVAL = process.env.NODE_ENV === "production" ? 30 : 0; // 30 seconds in production, 10 seconds in development
|
||||
export const REVALIDATION_INTERVAL = 0; //TODO: find a good way to cache and revalidate data when it changes
|
||||
|
||||
// URLs
|
||||
const VERCEL_URL = process.env.NEXT_PUBLIC_VERCEL_URL ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}` : "";
|
||||
|
||||
@@ -53,7 +53,7 @@ export const transformPrismaPerson = (person: TransformPersonInput): TPerson =>
|
||||
};
|
||||
};
|
||||
|
||||
export const getPerson = async (personId: string): Promise<TPerson | null> => {
|
||||
export const getPerson = cache(async (personId: string): Promise<TPerson | null> => {
|
||||
try {
|
||||
const personPrisma = await prisma.person.findUnique({
|
||||
where: {
|
||||
@@ -76,7 +76,7 @@ export const getPerson = async (personId: string): Promise<TPerson | null> => {
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const getPeople = cache(async (environmentId: string): Promise<TPerson[]> => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user