Merge branch 'main' of github.com:formbricks/formbricks into shubham/for-1142-create-responsive-menu-for-settings

This commit is contained in:
Johannes
2023-08-22 11:10:45 +02:00
9 changed files with 124 additions and 108 deletions

View File

@@ -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:

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 &ldquo;Close
<br /> survey on date&rdquo; 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 &ldquo;Close
<br /> survey on date&rdquo; setting in the Response Options.
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Select>
)}
</>
);

View File

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

View File

@@ -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}` : "";

View File

@@ -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 {