Skip to content

Commit

Permalink
feat: 🚀 added recent responses and usage card in dashboard page
Browse files Browse the repository at this point in the history
  • Loading branch information
growupanand committed Feb 14, 2024
1 parent 2b21f28 commit 0adb281
Show file tree
Hide file tree
Showing 17 changed files with 316 additions and 116 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"use client";

import Link from "next/link";
import { Skeleton } from "@convoform/ui/components/ui/skeleton";

import { ListCard } from "@/components/common/list";
import { ListItem } from "@/components/common/listItem";
import { timeAgo } from "@/lib/utils";
import { api } from "@/trpc/react";

export function RecentResponseList({
organizationId,
}: Readonly<{ organizationId: string }>) {
const { data, isFetching } = api.conversation.getRecentResponses.useQuery({
organizationId,
take: 10,
});

if (isFetching) {
return <RecentResponseListLoading />;
}

if (data) {
return (
<ListCard>
{data.map((response) => (
<ListItem key={response.id}>
<Link
href={`/forms/${response.formId}/conversations/${response.id}`}
>
<div className="flex items-center justify-between py-2">
<div className="grid">
<span>{response.name}</span>
<span className="text-muted-foreground text-xs">
{response.form.name}
</span>
</div>
<span className="text-muted-foreground text-sm">
{timeAgo(response.createdAt)}
</span>
</div>
</Link>
</ListItem>
))}
</ListCard>
);
}

return null;
}

function RecentResponseListLoading() {
return (
<ListCard>
{[...Array(3)].map((_, i) => (
<ListItem key={i}>
<div className="flex items-center justify-between py-2">
<div className="grid gap-1">
<Skeleton className="h-[10px] w-[150px]" />
<Skeleton className="h-[8px] w-[100px]" />
</div>
<Skeleton className="h-[10px] w-[50px]" />
</div>
</ListItem>
))}
</ListCard>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"use client";

import { Card, CardContent } from "@convoform/ui/components/ui/card";
import { Skeleton } from "@convoform/ui/components/ui/skeleton";
import { ProgressCircle } from "@tremor/react";

import { formSubmissionLimit } from "@/lib/config/pricing";
import { api } from "@/trpc/react";

type Props = {
organizationId: string;
};

export function ResponseUsageCard({ organizationId }: Readonly<Props>) {
const { data, isFetching } = api.metrics.getResponsesCount.useQuery({
organizationId,
});

if (isFetching) {
return (
<Card>
<CardContent className="pt-6">
<div className="grid grid-cols-2">
<div>
<p className="text-tremor-default text-tremor-content dark:text-dark-tremor-content">
<span>Response usage</span>
</p>
<div className="mt-3 flex items-center gap-2">
<Skeleton className="h-[20px] w-[40px]" />
</div>
</div>
<div className="flex justify-end">
<Skeleton className="h-[58px] w-[58px] rounded-full" />
</div>
</div>
</CardContent>
</Card>
);
}

if (data) {
const usage = 200;
const maximum = formSubmissionLimit;
const usagePercentage = (usage / maximum) * 100;

return (
<Card>
<CardContent className="pt-6">
<div className="grid grid-cols-2">
<div>
<p className="text-tremor-default text-tremor-content dark:text-dark-tremor-content">
<span>Response usage</span>
</p>
<p className="mt-3 flex items-end">
<span className="text-tremor-metric text-tremor-content-strong dark:text-dark-tremor-content-strong font-semibold">
{usage}
</span>
<span className="text-tremor-content-subtle dark:text-dark-tremor-content-subtle font-semibold">
/{maximum}
</span>
</p>
</div>
<div className="flex justify-end">
<ProgressCircle value={usagePercentage}>
<span className="text-muted-foreground font-medium">
{usagePercentage}%
</span>
</ProgressCircle>
</div>
</div>
</CardContent>
</Card>
);
}

return null;
}
27 changes: 21 additions & 6 deletions apps/web/src/app/(protectedPage)/(mainPage)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Metadata } from "next";

import { FormDataCard } from "@/components/mainPage/dashboard/formDataCard";
import { ResponseDataCard } from "@/components/mainPage/dashboard/reponseDataCard";
import { ResponseDataCard } from "@/app/(protectedPage)/(mainPage)/dashboard/_components/reponseDataCard";
import { getOrganizationId } from "@/lib/getOrganizationId";
import { RecentResponseList } from "./_components/recentResponseList";
import { ResponseUsageCard } from "./_components/responseUsageCard";

export const metadata: Metadata = {
title: "Dashboard",
Expand All @@ -12,11 +13,25 @@ export default function DashboardPage() {
const orgId = getOrganizationId();

return (
<div className="max-lg:p-3">
<div className="">
<h1 className="mb-5 py-3 text-xl font-medium lg:text-2xl">Dashboard</h1>
<div className="grid gap-3 lg:grid-flow-col lg:grid-cols-2">
<FormDataCard orgId={orgId} />
<ResponseDataCard orgId={orgId} />
<div className="grid gap-5 lg:grid-cols-6">
<div className="max-lg:mb-5 lg:col-span-2">
<div className="grid gap-3">
<ResponseDataCard orgId={orgId} />
<ResponseUsageCard organizationId={orgId} />
</div>
</div>
<div className="lg:col-span-4">
<div>
<h3 className="text-muted-foreground mb-3 text-sm font-normal">
Recent responses
</h3>
<div>
<RecentResponseList organizationId={orgId} />
</div>
</div>
</div>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import { QueryDataCard } from "@/components/common/dataCard/queryDataCard";
import { api } from "@/trpc/react";

type Props = {
orgId: string;
formId: string;
};

export function OverviewDataCard({ orgId, formId }: Readonly<Props>) {
export function OverviewDataCard({ formId }: Readonly<Props>) {
const query = api.metrics.getConversationMetrics.useQuery({
organizationId: orgId,
formId,
});
return <QueryDataCard query={query} dataSourceName="Responses Overview" />;
return <QueryDataCard query={query} dataSourceName="Overview" />;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getOrganizationId } from "@/lib/getOrganizationId";
import { OverviewDataCard } from "./_components/overviewDataCard";
import { OverviewTable } from "./_components/overviewTable";

Expand All @@ -9,12 +8,10 @@ type Props = {
export default function ConversationPage({
params: { formId },
}: Readonly<Props>) {
const orgId = getOrganizationId();

return (
<>
<div className="mb-3 lg:mb-5">
<OverviewDataCard orgId={orgId} formId={formId} />
<OverviewDataCard formId={formId} />
</div>
<div className="col-auto">
<OverviewTable formId={formId} />
Expand Down
7 changes: 1 addition & 6 deletions apps/web/src/app/api/form/[formId]/conversation/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";

import { freePlan } from "@/lib/config/pricing";
import { formSubmissionLimit } from "@/lib/config/pricing";
import { sendErrorResponse } from "@/lib/errorHandlers";
import { ConversationService } from "@/lib/services/conversation";
import { ConversationPayloadSchema } from "@/lib/validations/conversation";
Expand Down Expand Up @@ -43,11 +43,6 @@ export async function POST(
organizationId: form.organizationId,
});

const formSubmissionLimit =
freePlan.features.find(
(feature) => feature.name === "Collect form responses",
)?.featureValue ?? 0;

if (!totalSubmissionsCount) {
console.error("Unable to get total submissions count", {
organizationId: form.organizationId,
Expand Down
10 changes: 5 additions & 5 deletions apps/web/src/components/common/dataCard/dataCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import { Skeleton } from "@convoform/ui/components/ui/skeleton";

import Spinner from "@/components/common/spinner";

const BarChart = dynamic(
async () => import("@tremor/react").then((mod) => mod.BarChart),
const LineChart = dynamic(
async () => import("@tremor/react").then((mod) => mod.LineChart),
{
ssr: false,
loading: () => (
<div className="flex h-full w-full items-center justify-center">
<Spinner className="mr-2" /> Loading
<Spinner className="mr-2" /> Loading chart
</div>
),
},
Expand Down Expand Up @@ -64,9 +64,9 @@ export function DataCard({
</div>
</CardHeader>
{chartData && (
<CardContent>
<CardContent className="pt-3">
<div className="h-[150px] grow ">
<BarChart
<LineChart
className="h-full w-full"
data={chartData}
index="name"
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/components/common/list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function ListCard({
children,
}: Readonly<{ children: React.ReactNode }>) {
return <div className="divide-border grid divide-y border-b">{children}</div>;
}
9 changes: 9 additions & 0 deletions apps/web/src/components/common/listItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function ListItem({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<div className=" py-1 ps-1 align-middle transition-all hover:bg-gray-50 hover:ps-2">
{children}
</div>
);
}
13 changes: 0 additions & 13 deletions apps/web/src/components/mainPage/dashboard/dataCardLoading.tsx

This file was deleted.

12 changes: 0 additions & 12 deletions apps/web/src/components/mainPage/dashboard/formDataCard.tsx

This file was deleted.

5 changes: 3 additions & 2 deletions apps/web/src/components/mainPage/workspace/formList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { ListCard } from "@/components/common/list";
import { api } from "@/trpc/react";
import { FormListItem } from "./formListItem";
import FormListLoading from "./formListLoading";
Expand Down Expand Up @@ -28,11 +29,11 @@ export default function FormList({ workspaceId, orgId }: Readonly<Props>) {
<div className="h-full">
{emptyForms && <p className="text-muted-foreground">No form</p>}
{!emptyForms && (
<div className="divide-border grid divide-y border-b">
<ListCard>
{forms.map((form) => (
<FormListItem key={form.id} form={form} />
))}
</div>
</ListCard>
)}
</div>
);
Expand Down

0 comments on commit 0adb281

Please sign in to comment.