mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-28 12:42:44 -05:00
fix build warnings, use next-image instead of img
This commit is contained in:
@@ -7,9 +7,8 @@ import {
|
||||
} from "@heroicons/react/outline";
|
||||
import { DotsHorizontalIcon, TrashIcon } from "@heroicons/react/solid";
|
||||
import Link from "next/link";
|
||||
import Router from "next/router";
|
||||
import { Fragment, useState } from "react";
|
||||
import { createForm, useForms } from "../lib/forms";
|
||||
import { useForms } from "../lib/forms";
|
||||
import { classNames } from "../lib/utils";
|
||||
import NewFormModal from "./form/NewFormModal";
|
||||
import EmptyPageFiller from "./layout/EmptyPageFiller";
|
||||
@@ -52,7 +51,7 @@ export default function FormList() {
|
||||
hintText="Start by creating a form."
|
||||
buttonText="create form"
|
||||
borderStyles="border-4 border-dotted border-red"
|
||||
button={true}
|
||||
hasButton={true}
|
||||
>
|
||||
<DocumentAddIcon className="w-24 h-24 mx-auto text-ui-gray-medium stroke-thin" />
|
||||
</EmptyPageFiller>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FaReact, FaVuejs } from "react-icons/fa";
|
||||
import { DocumentSearchIcon } from "@heroicons/react/outline";
|
||||
import { DocumentSearchIcon, TerminalIcon } from "@heroicons/react/outline";
|
||||
import { classNames } from "../../lib/utils";
|
||||
import StandardButton from "../StandardButton";
|
||||
import Link from "next/link";
|
||||
@@ -44,6 +44,10 @@ export default function FormCode({ formId }) {
|
||||
return (
|
||||
<>
|
||||
<SecondNavBar>
|
||||
<SecondNavBarItem link href={`/forms/${formId}/form`}>
|
||||
<TerminalIcon className="w-8 h-8 mx-auto stroke-1" />
|
||||
formID
|
||||
</SecondNavBarItem>
|
||||
<SecondNavBarItem link href={`/forms/${formId}/react`}>
|
||||
<FaReact className="w-8 h-8 mx-auto stroke-1" />
|
||||
React
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/* This example requires Tailwind CSS v2.0+ */
|
||||
import { Dialog, RadioGroup, Transition } from "@headlessui/react";
|
||||
import { CheckCircleIcon, XIcon } from "@heroicons/react/solid";
|
||||
import { useRouter } from "next/router";
|
||||
import { Fragment, useState } from "react";
|
||||
import { BsPlus } from "react-icons/bs";
|
||||
import { createForm } from "../../lib/forms";
|
||||
import { createNoCodeForm } from "../../lib/noCodeForm";
|
||||
import { classNames } from "../../lib/utils";
|
||||
import StandardButton from "../StandardButton";
|
||||
|
||||
const formTypes = [
|
||||
{
|
||||
id: "NOCODE",
|
||||
title: "No-Code Builder",
|
||||
description:
|
||||
"Use the Notion-like builder to build your form without a single line of code.",
|
||||
},
|
||||
{
|
||||
id: "CODE",
|
||||
title: "Code",
|
||||
description:
|
||||
"Use the snoopReact library to code the form yourself and manage the data here.",
|
||||
additionalDescription: "",
|
||||
},
|
||||
];
|
||||
|
||||
type FormOnboardingModalProps = {
|
||||
open: boolean;
|
||||
setOpen: (v: boolean) => void;
|
||||
};
|
||||
|
||||
export default function NewFormModal({
|
||||
open,
|
||||
setOpen,
|
||||
}: FormOnboardingModalProps) {
|
||||
const router = useRouter();
|
||||
const [name, setName] = useState("");
|
||||
const [formType, setFormType] = useState(formTypes[0]);
|
||||
|
||||
const submitForm = async (e) => {
|
||||
e.preventDefault();
|
||||
const form = await createForm({
|
||||
name,
|
||||
formType: formType.id,
|
||||
});
|
||||
if (form.formType === "NOCODE") {
|
||||
await createNoCodeForm(form.id);
|
||||
}
|
||||
router.push(`/forms/${form.id}/form`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition.Root show={open} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={setOpen}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex items-end justify-center min-h-full p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative px-4 pt-5 pb-4 text-left transition-all transform bg-white rounded-lg shadow-xl sm:my-8 sm:max-w-lg sm:w-full sm:p-6">
|
||||
<div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 bg-white rounded-md hover:text-gray-500 focus:outline-none focus:ring-0 focus:ring-offset-2"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<XIcon className="w-6 h-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between">
|
||||
<h2 className="flex-none p-2 text-xl font-bold text-ui-gray-dark">
|
||||
Create new form
|
||||
</h2>
|
||||
</div>
|
||||
<form
|
||||
onSubmit={(e) => submitForm(e)}
|
||||
className="inline-block w-full p-2 overflow-hidden text-left align-bottom transition-all transform sm:align-middle"
|
||||
>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="text-sm font-light text-ui-gray-dark"
|
||||
>
|
||||
Name your form
|
||||
</label>
|
||||
<div className="mt-2">
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
className="block w-full p-2 mb-6 border-none rounded bg-ui-gray-light focus:ring-2 focus:ring-red sm:text-sm placeholder:font-extralight placeholder:text-ui-gray-medium"
|
||||
placeholder="e.g. Customer Research Survey"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
autoFocus
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RadioGroup value={formType} onChange={setFormType}>
|
||||
<RadioGroup.Label className="text-sm font-light text-ui-gray-dark">
|
||||
How do you build your form?
|
||||
</RadioGroup.Label>
|
||||
|
||||
<div className="grid grid-cols-1 mt-4 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
|
||||
{formTypes.map((formType) => (
|
||||
<RadioGroup.Option
|
||||
key={formType.id}
|
||||
value={formType}
|
||||
className={({ checked, active }) =>
|
||||
classNames(
|
||||
checked ? "border-transparent" : "",
|
||||
active
|
||||
? "border-red ring-2 ring-red"
|
||||
: "bg-ui-gray-lighter",
|
||||
"relative bg-white border rounded shadow-sm p-4 flex cursor-pointer focus:outline-none"
|
||||
)
|
||||
}
|
||||
>
|
||||
{({ checked, active }) => (
|
||||
<>
|
||||
<span className="flex flex-1">
|
||||
<span className="flex flex-col">
|
||||
<RadioGroup.Label
|
||||
as="span"
|
||||
className="block font-bold text-md text-ui-gray-dark"
|
||||
>
|
||||
{formType.title}
|
||||
</RadioGroup.Label>
|
||||
<RadioGroup.Description
|
||||
as="span"
|
||||
className="flex items-center mt-1 text-xs whitespace-pre-wrap text-ui-gray-dark"
|
||||
>
|
||||
{formType.description}
|
||||
</RadioGroup.Description>
|
||||
</span>
|
||||
</span>
|
||||
<CheckCircleIcon
|
||||
className={classNames(
|
||||
!checked ? "hidden" : "",
|
||||
"h-5 w-5 text-red"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
checked ? "hidden" : "",
|
||||
"h-4 w-4 rounded-full border-2 border-ui-gray-light"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
className={classNames(
|
||||
active ? "border" : "border-2",
|
||||
checked ? "border-red" : "border-transparent",
|
||||
"absolute -inset-px rounded pointer-events-none"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div className="mt-5 sm:mt-6">
|
||||
<StandardButton fullwidth type="submit">
|
||||
create form
|
||||
<BsPlus className="w-6 h-6 ml-1"></BsPlus>
|
||||
</StandardButton>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
import { QuestionMarkCircleIcon } from "@heroicons/react/outline";
|
||||
import { classNames } from "../../lib/utils";
|
||||
|
||||
interface Props {
|
||||
KPI: string | number;
|
||||
typeText?: boolean;
|
||||
label: string;
|
||||
toolTipText: string;
|
||||
trend: number;
|
||||
}
|
||||
|
||||
const AnalyticsCard: React.FC<Props> = ({
|
||||
KPI,
|
||||
typeText = false,
|
||||
label,
|
||||
toolTipText,
|
||||
trend,
|
||||
}) => {
|
||||
return (
|
||||
<div className="grid content-between px-4 py-2 bg-white rounded-md shadow-md">
|
||||
<div className="inline-flex items-center text-sm text-ui-gray-dark">
|
||||
{label}{" "}
|
||||
{toolTipText && (
|
||||
<QuestionMarkCircleIcon className="w-4 h-4 ml-1 text-red hover:text-ui-gray-dark" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
`font-bold leading-none flex justify-between items-end`,
|
||||
typeText ? "text-3xl tracking-tight leading-10" : "text-7xl"
|
||||
)}
|
||||
>
|
||||
{KPI}
|
||||
{trend && (
|
||||
<div className="flex items-center h-6 px-6 py-2 mb-2.5 text-sm font-light text-green-600 bg-green-200 rounded-full">
|
||||
{trend} %
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalyticsCard;
|
||||
@@ -8,7 +8,7 @@ interface Props {
|
||||
hintText: string;
|
||||
buttonText?: string;
|
||||
borderStyles?: string;
|
||||
button?: boolean;
|
||||
hasButton?: boolean;
|
||||
}
|
||||
|
||||
const EmptyPageFiller: React.FC<Props> = ({
|
||||
@@ -18,7 +18,7 @@ const EmptyPageFiller: React.FC<Props> = ({
|
||||
hintText,
|
||||
buttonText,
|
||||
borderStyles,
|
||||
button = false,
|
||||
hasButton = false,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
@@ -32,7 +32,7 @@ const EmptyPageFiller: React.FC<Props> = ({
|
||||
{alertText}
|
||||
</h3>
|
||||
<p className="mt-1 text-xs font-light text-ui-gray-medium">{hintText}</p>
|
||||
{button && (
|
||||
{hasButton && (
|
||||
<div className="mt-6">
|
||||
<StandardButton onClick={onClick}>{buttonText}</StandardButton>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function Layout({ children }) {
|
||||
<Disclosure as="nav" className="bg-white shadow-sm">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div className="max-w-5xl px-4 mx-auto sm:px-6 lg:px-8">
|
||||
<div className="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between h-16">
|
||||
<div className="flex">
|
||||
<div className="flex items-center flex-shrink-0 w-48">
|
||||
@@ -107,10 +107,12 @@ export default function Layout({ children }) {
|
||||
<div className="pt-4 pb-3 border-t border-ui-gray-light">
|
||||
<div className="flex items-center px-4">
|
||||
<div className="flex-shrink-0">
|
||||
<img
|
||||
<Image
|
||||
className="w-10 h-10 rounded-full"
|
||||
src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=8&w=256&h=256&q=80"
|
||||
alt=""
|
||||
src="/img/avatar-placeholder.png"
|
||||
alt="user avatar"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Head from "next/head";
|
||||
import MenuBreadcrumbs from "./MenuBreadcrumbs";
|
||||
import MenuSteps from "./MenuSteps";
|
||||
import MenuProfile from "./MenuProfile";
|
||||
import { classNames } from "../../lib/utils";
|
||||
import { signIn, useSession } from "next-auth/react";
|
||||
import Head from "next/head";
|
||||
import { classNames } from "../../lib/utils";
|
||||
import Loading from "../Loading";
|
||||
import MenuBreadcrumbs from "./MenuBreadcrumbs";
|
||||
import MenuProfile from "./MenuProfile";
|
||||
import MenuSteps from "./MenuSteps";
|
||||
|
||||
export default function LayoutFormResults({
|
||||
title,
|
||||
@@ -26,9 +26,9 @@ export default function LayoutFormResults({
|
||||
}
|
||||
|
||||
const resultModes = [
|
||||
{ name: "Summary", id: "summary" },
|
||||
{ name: "Responses", id: "responses" },
|
||||
{ name: "Analytics", id: "analytics" },
|
||||
{ name: "Summary", id: "summary", icon: "" },
|
||||
{ name: "Responses", id: "responses", icon: "" },
|
||||
{ name: "Analytics", id: "analytics", icon: "" },
|
||||
];
|
||||
return (
|
||||
<>
|
||||
@@ -48,7 +48,7 @@ export default function LayoutFormResults({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative z-10 flex flex-shrink-0 h-16 border-b border-ui-gray-light shadow-inner bg-gray-50">
|
||||
<div className="relative z-10 flex flex-shrink-0 h-16 border-b shadow-inner border-ui-gray-light bg-gray-50">
|
||||
<div className="flex items-center justify-center flex-1 px-4">
|
||||
<nav className="flex space-x-4" aria-label="resultModes">
|
||||
{resultModes.map((mode) => (
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { signOut } from "next-auth/react";
|
||||
import Image from "next/image";
|
||||
import { Fragment } from "react";
|
||||
import { classNames } from "../../lib/utils";
|
||||
|
||||
export default function MenuProfile({}) {
|
||||
return (
|
||||
<Menu as="div" className="relative flex-shrink-0">
|
||||
<Menu as="div" className="relative z-50 flex-shrink-0">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div className="inline-flex items-center ">
|
||||
<Menu.Button className="flex ml-3 text-sm bg-white rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
||||
<span className="sr-only">Open user menu</span>
|
||||
<img
|
||||
<Image
|
||||
className="w-8 h-8 rounded-full"
|
||||
src="/img/avatar-placeholder.png"
|
||||
alt=""
|
||||
alt="user avatar"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ interface Props {
|
||||
// button component, consuming props
|
||||
const SecondNavBar: React.FC<Props> = ({ children }) => {
|
||||
return (
|
||||
<div className="relative z-10 flex flex-shrink-0 h-16 py-2 bg-ui-gray-lighter">
|
||||
<div className="relative flex flex-shrink-0 h-16 py-2 border-b border-ui-gray-light bg-ui-gray-lighter">
|
||||
<div className="flex items-center justify-center flex-1 px-4 py-2">
|
||||
<nav className="flex space-x-4" aria-label="resultModes">
|
||||
{children}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ClockIcon, InboxIcon, UsersIcon } from "@heroicons/react/outline";
|
||||
import Image from "next/image";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
getSubmissionAnalytics,
|
||||
useSubmissionSessions,
|
||||
} from "../../lib/submissionSessions";
|
||||
import { timeSince } from "../../lib/utils";
|
||||
import AnalyticsCard from "../layout/AnalyticsCard";
|
||||
|
||||
export default function ResultsAnalytics({ formId }) {
|
||||
const { submissionSessions, isLoadingSubmissionSessions } =
|
||||
@@ -22,56 +23,73 @@ export default function ResultsAnalytics({ formId }) {
|
||||
{
|
||||
id: "uniqueUsers",
|
||||
name: "Unique Users",
|
||||
stat: analytics.uniqueUsers,
|
||||
icon: UsersIcon,
|
||||
stat: analytics.uniqueUsers || "--",
|
||||
toolTipText: "placeholder",
|
||||
trend: 12,
|
||||
},
|
||||
{
|
||||
id: "totalSubmissions",
|
||||
name: "Total Submissions",
|
||||
stat: analytics.totalSubmissions,
|
||||
icon: InboxIcon,
|
||||
stat: analytics.totalSubmissions || "--",
|
||||
trend: 10,
|
||||
},
|
||||
{
|
||||
id: "uniqueUsers",
|
||||
id: "lastSubmission",
|
||||
name: "Last Submission",
|
||||
stat: timeSince(analytics.lastSubmissionAt) || "-",
|
||||
icon: ClockIcon,
|
||||
stat: timeSince(analytics.lastSubmissionAt) || "--",
|
||||
typeText: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
}, [analytics]);
|
||||
return (
|
||||
<main>
|
||||
<div className="mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||
<div className="max-w-5xl mx-auto sm:px-6 lg:px-8">
|
||||
<h2 className="mt-8 text-xl font-bold text-ui-gray-dark">Analytics</h2>
|
||||
<div>
|
||||
{stats ? (
|
||||
<dl className="grid grid-cols-1 gap-5 mt-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{stats.map((item) => (
|
||||
<div
|
||||
<AnalyticsCard
|
||||
key={item.id}
|
||||
className="relative px-4 bg-white rounded-lg shadow pt-5overflow-hidden sm:pt-6 sm:px-6"
|
||||
>
|
||||
<dt>
|
||||
<div className="absolute p-3 rounded-md bg-red-500">
|
||||
<item.icon
|
||||
className="w-6 h-6 text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<p className="ml-16 text-sm font-medium text-gray-500 truncate">
|
||||
{item.name}
|
||||
</p>
|
||||
</dt>
|
||||
<dd className="flex items-baseline ml-16 sm:pb-7">
|
||||
<p className="text-xl font-semibold text-gray-800">
|
||||
{item.stat}
|
||||
</p>
|
||||
</dd>
|
||||
</div>
|
||||
KPI={item.stat}
|
||||
label={item.name}
|
||||
toolTipText={item.toolTipText}
|
||||
typeText={item.typeText}
|
||||
trend={item.trend}
|
||||
/>
|
||||
))}
|
||||
</dl>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<h2 className="mt-16 text-xl font-bold text-ui-gray-dark">
|
||||
Optimize Form
|
||||
</h2>
|
||||
<div className="px-3 py-2 ml-2 text-xs text-green-800 rounded-sm bg-green-50">
|
||||
<p>coming soon</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-10 mt-8">
|
||||
<div className="relative p-8 bg-white rounded-md shadow-md h-60">
|
||||
<Image
|
||||
src="/../../img/drop-offs-v1.svg"
|
||||
alt="drop-off"
|
||||
objectFit="cover"
|
||||
layout="fill"
|
||||
className="rounded-md"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative p-8 bg-white rounded-md shadow-md h-60">
|
||||
<Image
|
||||
src="/../../img/a-b-test-v1.svg"
|
||||
alt="drop-off"
|
||||
objectFit="cover"
|
||||
layout="fill"
|
||||
className="rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function ResultsResponses({ formId }: ResultsResponseProps) {
|
||||
if (!isLoadingSubmissionSessions && submissionSessions.length > 0) {
|
||||
setActiveSubmissionSession(submissionSessions[0]);
|
||||
}
|
||||
}, [isLoadingSubmissionSessions]);
|
||||
}, [isLoadingSubmissionSessions, submissionSessions]);
|
||||
|
||||
if (isLoadingSubmissionSessions) {
|
||||
return <Loading />;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { ClockIcon, InboxIcon, UsersIcon } from "@heroicons/react/outline";
|
||||
import { useMemo } from "react";
|
||||
import { useForm } from "../../lib/forms";
|
||||
import {
|
||||
getSubmissionAnalytics,
|
||||
getSubmissionSummary,
|
||||
useSubmissionSessions,
|
||||
} from "../../lib/submissionSessions";
|
||||
import { SubmissionSummary } from "../../lib/types";
|
||||
import { timeSince } from "../../lib/utils";
|
||||
import AnalyticsCard from "../layout/AnalyticsCard";
|
||||
import Loading from "../Loading";
|
||||
import TextResults from "./summary/TextResults";
|
||||
|
||||
@@ -15,6 +17,12 @@ export default function ResultsSummary({ formId }) {
|
||||
|
||||
const { form, isLoadingForm } = useForm(formId);
|
||||
|
||||
const analytics = useMemo(() => {
|
||||
if (!isLoadingSubmissionSessions) {
|
||||
return getSubmissionAnalytics(submissionSessions);
|
||||
}
|
||||
}, [isLoadingSubmissionSessions, submissionSessions]);
|
||||
|
||||
const summary: SubmissionSummary | undefined = useMemo(() => {
|
||||
if (!isLoadingSubmissionSessions && !isLoadingForm) {
|
||||
return getSubmissionSummary(submissionSessions, form?.schema);
|
||||
@@ -22,79 +30,63 @@ export default function ResultsSummary({ formId }) {
|
||||
}, [isLoadingSubmissionSessions, submissionSessions, isLoadingForm, form]);
|
||||
|
||||
const stats = useMemo(() => {
|
||||
if (summary) {
|
||||
if (analytics) {
|
||||
return [
|
||||
{
|
||||
id: "uniqueUsers",
|
||||
name: "Unique Users",
|
||||
stat: 10,
|
||||
icon: UsersIcon,
|
||||
stat: analytics.uniqueUsers || "--",
|
||||
toolTipText: "placeholder",
|
||||
trend: 12,
|
||||
},
|
||||
{
|
||||
id: "totalSubmissions",
|
||||
name: "Total Submissions",
|
||||
stat: 10,
|
||||
icon: InboxIcon,
|
||||
stat: analytics.totalSubmissions || "--",
|
||||
trend: 10,
|
||||
},
|
||||
{
|
||||
id: "uniqueUsers",
|
||||
id: "lastSubmission",
|
||||
name: "Last Submission",
|
||||
stat: 10,
|
||||
icon: ClockIcon,
|
||||
stat: timeSince(analytics.lastSubmissionAt) || "--",
|
||||
typeText: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
}, [summary]);
|
||||
}, [analytics]);
|
||||
|
||||
if (!summary) {
|
||||
if (!summary || !analytics) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="bg-gray-50">
|
||||
<div className="mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||
<div>
|
||||
<dl className="grid grid-cols-1 gap-5 mt-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{stats.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="relative px-4 bg-white rounded-lg shadow pt-5overflow-hidden sm:pt-6 sm:px-6"
|
||||
>
|
||||
<dt>
|
||||
<div className="absolute p-3 rounded-md bg-red-500">
|
||||
<item.icon
|
||||
className="w-6 h-6 text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<p className="ml-16 text-sm font-medium text-gray-500 truncate">
|
||||
{item.name}
|
||||
</p>
|
||||
</dt>
|
||||
<dd className="flex items-baseline ml-16 sm:pb-7">
|
||||
<p className="text-xl font-semibold text-gray-800">
|
||||
{item.stat}
|
||||
</p>
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
<hr className="my-8" />
|
||||
<div className="max-w-5xl mx-auto sm:px-6 lg:px-8">
|
||||
<h2 className="mt-8 text-xl font-bold text-ui-gray-dark">
|
||||
Responses Overview
|
||||
</h2>
|
||||
<dl className="grid grid-cols-1 gap-5 mt-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{stats.map((item) => (
|
||||
<AnalyticsCard
|
||||
key={item.id}
|
||||
KPI={item.stat}
|
||||
label={item.name}
|
||||
toolTipText={item.toolTipText}
|
||||
typeText={item.typeText}
|
||||
trend={item.trend}
|
||||
/>
|
||||
))}
|
||||
</dl>
|
||||
<div>
|
||||
{summary.pages.map(
|
||||
(page, pageIdx) =>
|
||||
(page) =>
|
||||
page.type === "form" && (
|
||||
<div key={page.name}>
|
||||
<h2 className="text-xl font-bold text-gray-700">
|
||||
Page {pageIdx + 1}
|
||||
</h2>
|
||||
{page.elements.map((element) =>
|
||||
element.type === "text" || element.type === "textarea" ? (
|
||||
<TextResults element={element} />
|
||||
) : null
|
||||
)}
|
||||
<hr className="my-8" />
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -6,7 +6,7 @@ export default function TextResults({ element }) {
|
||||
<div className="flow-root px-8 my-4 mt-6 overflow-y-scroll text-center max-h-64">
|
||||
<ul className="-my-5 divide-y divide-ui-gray-light">
|
||||
{element.summary.map((answer) => (
|
||||
<li key={answer} className="py-5">
|
||||
<li key={answer} className="py-8">
|
||||
<div className="relative focus-within:ring-2 focus-within:ring-indigo-500">
|
||||
<h3 className="text-sm text-gray-700">
|
||||
{/* Extend touch target to entire panel */}
|
||||
|
||||
Reference in New Issue
Block a user