Skip to content

Commit

Permalink
Merge pull request #115 from complexdatacollective/refactor/summary-s…
Browse files Browse the repository at this point in the history
…tats-client-side

Refactor/summary stats to use client side fetch
  • Loading branch information
buckhalt committed Apr 18, 2024
2 parents 8e25d0d + cc7b556 commit bb59d66
Show file tree
Hide file tree
Showing 29 changed files with 742 additions and 532 deletions.
41 changes: 1 addition & 40 deletions app/(dashboard)/dashboard/_components/ActivityFeed/utils.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,4 @@
import { faker } from '@faker-js/faker';
import {
type Activity,
type ActivityType,
activityTypes,
} from '~/lib/data-table/types';

const generateMessageForActivityType = (type: ActivityType) => {
switch (type) {
case 'Protocol Installed':
return `Protocol "${faker.word.words({ count: 4 })}" installed`;
case 'Protocol Uninstalled':
return `Protocol "${faker.word.words({ count: 4 })}" uninstalled`;
case 'Participant(s) Added':
return `Added ${faker.number.int({ min: 1, max: 10 })} participant(s)`;
case 'Participant(s) Removed':
return `Removed ${faker.number.int({ min: 1, max: 10 })} participant(s)`;
case 'Interview Started':
return `Participant "${faker.person.fullName()}" started an interview`;
case 'Interview Completed':
return `Participant "${faker.person.fullName()}" completed an interview`;
case 'Interview(s) Deleted':
return `Deleted ${faker.number.int({ min: 1, max: 10 })} interview(s)`;
case 'Data Exported':
return `Exported data for ${faker.number.int({
min: 1,
max: 10,
})} participant(s)`;
}
};
import { type ActivityType } from '~/lib/data-table/types';

export const getBadgeColorsForActivityType = (type: ActivityType) => {
switch (type.toLowerCase()) {
Expand All @@ -49,13 +20,3 @@ export const getBadgeColorsForActivityType = (type: ActivityType) => {
return 'bg-kiwi hover:bg-kiwi-dark';
}
};

export const generateMockActivity = (): Activity => {
const type = faker.helpers.arrayElement(activityTypes);
return {
id: faker.string.uuid(),
timestamp: faker.date.recent(),
type,
message: generateMessageForActivityType(type),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ const statCardClasses = cn(
);
function StatCard({
title,
initialData,
value,
icon,
}: {
title: string;
initialData: number;
value: number;
icon: React.ReactNode;
}) {
return (
<div className={statCardClasses}>
<div className="hidden md:block">{icon}</div>
<div>
<Heading variant="h4-all-caps">{title}</Heading>
<Heading variant="h1">{initialData}</Heading>
<Heading variant="h1">{value}</Heading>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,78 @@
'use client';

import Image from 'next/image';
import Link from 'next/link';
import ResponsiveContainer from '~/components/ResponsiveContainer';
import StatCard from './StatCard';
import Image from 'next/image';
import { api } from '~/trpc/client';
import { InterviewIcon, ProtocolIcon } from './Icons';
import { api } from '~/trpc/server';
import { unstable_noStore } from 'next/cache';
import StatCard, { StatCardSkeleton } from './StatCard';

export default function SummaryStatistics() {
const { data: protocolCount, isLoading: isProtocolStatsLoading } =
api.dashboard.getSummaryStatistics.protocolCount.useQuery();

export default async function SummaryStatistics() {
unstable_noStore();
const { data: participantCount, isLoading: isParticipantStatsLoading } =
api.dashboard.getSummaryStatistics.participantCount.useQuery();

const interviewCount =
await api.dashboard.getSummaryStatistics.interviewCount.query();
const participantCount =
await api.dashboard.getSummaryStatistics.participantCount.query();
const protocolCount =
await api.dashboard.getSummaryStatistics.protocolCount.query();
const { data: interviewCount, isLoading: isInterviewStatsLoading } =
api.dashboard.getSummaryStatistics.interviewCount.useQuery();

return (
<ResponsiveContainer
className="grid grid-cols-1 gap-4 sm:grid-cols-3 lg:gap-6"
maxWidth="6xl"
>
<Link href="/dashboard/protocols">
<StatCard
title="Protocols"
initialData={protocolCount}
icon={<ProtocolIcon />}
/>
{isProtocolStatsLoading ? (
<StatCardSkeleton title="Protocols" icon={<ProtocolIcon />} />
) : (
<StatCard
title="Protocols"
value={protocolCount!}
icon={<ProtocolIcon />}
/>
)}
</Link>
<Link href="/dashboard/participants">
<StatCard
title="Participants"
initialData={participantCount}
icon={
<Image
src="/images/participant.svg"
width={50}
height={50}
alt="Participant icon"
className="max-w-none"
/>
}
/>
{isParticipantStatsLoading ? (
<StatCardSkeleton
title="Participants"
icon={
<Image
src="/images/participant.svg"
width={50}
height={50}
alt="Participant icon"
className="max-w-none"
/>
}
/>
) : (
<StatCard
title="Participants"
value={participantCount!}
icon={
<Image
src="/images/participant.svg"
width={50}
height={50}
alt="Participant icon"
className="max-w-none"
/>
}
/>
)}
</Link>
<Link href="/dashboard/interviews">
<StatCard
title="Interviews"
initialData={interviewCount}
icon={<InterviewIcon />}
/>
{isInterviewStatsLoading ? (
<StatCardSkeleton title="Interviews" icon={<InterviewIcon />} />
) : (
<StatCard
title="Interviews"
value={interviewCount!}
icon={<InterviewIcon />}
/>
)}
</Link>
</ResponsiveContainer>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use server';

// We need to delete the exported data from UploadThing after the user has downloaded it.
// This is to ensure that we are not storing any sensitive data on UploadThing for longer than necessary.

import { utapi } from '~/app/api/uploadthing/core';
import { getServerSession } from '~/utils/auth';

export const deleteZipFromUploadThing = async (key: string) => {
const session = await getServerSession();

if (!session) {
throw new Error(
'You must be logged in to delete interview data from UploadThing!.',
);
}

const deleteResponse = await utapi.deleteFiles(key);

if (!deleteResponse.success) {
throw new Error('Failed to delete the zip file from UploadThing');
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Heading from '~/components/ui/typography/Heading';
import { ensureError } from '~/utils/ensureError';
import { cn } from '~/utils/shadcn';
import { cardClasses } from '~/components/ui/card';
import { deleteZipFromUploadThing } from '../_actions/deleteZipFromUploadThing';

const ExportingStateAnimation = () => {
return (
Expand Down Expand Up @@ -75,15 +76,22 @@ export const ExportInterviewsDialog = ({
}

const response = await fetch(result.data.url);
const blob = await response.blob();

if (!response.ok) {
throw new Error('HTTP error ' + response.status);
}

const blob = await response.blob();
// create a download link
const url = URL.createObjectURL(blob);

// Download the zip file
download(url, result.data.name);
// clean up the URL object
URL.revokeObjectURL(url);

// Delete the zip file from UploadThing
await deleteZipFromUploadThing(result.data.key);
} catch (error) {
toast({
icon: <XCircle />,
Expand Down
2 changes: 2 additions & 0 deletions app/(dashboard)/dashboard/interviews/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Section from '~/components/layout/Section';
import PageHeader from '~/components/ui/typography/PageHeader';
import { api } from '~/trpc/server';

export const dynamic = 'force-dynamic';

const InterviewPage = async () => {
const initialInterviews = await api.interview.get.all.query();
return (
Expand Down
2 changes: 2 additions & 0 deletions app/(dashboard)/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Paragraph from '~/components/ui/typography/Paragraph';
import SummaryStatistics from './_components/SummaryStatistics/SummaryStatistics';
import AnonymousRecruitmentWarning from './protocols/_components/AnonymousRecruitmentWarning';

export const dynamic = 'force-dynamic';

function Home() {
return (
<>
Expand Down
2 changes: 2 additions & 0 deletions app/(dashboard)/dashboard/participants/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import PageHeader from '~/components/ui/typography/PageHeader';
import { api } from '~/trpc/server';
import ImportExportSection from './_components/ExportParticipants/ImportExportSection';

export const dynamic = 'force-dynamic';

const ParticipantPage = async () => {
const participants = await api.participant.get.all.query();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import ResponsiveContainer from '~/components/ResponsiveContainer';
import { Alert, AlertTitle, AlertDescription } from '~/components/ui/Alert';
import { api } from '~/trpc/server';

export const dynamic = 'force-dynamic';

export default async function AnonymousRecruitmentWarning() {
const allowAnonymousRecruitment =
await api.appSettings.getAnonymousRecruitmentStatus.query();
Expand Down
2 changes: 2 additions & 0 deletions app/(dashboard)/dashboard/protocols/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import PageHeader from '~/components/ui/typography/PageHeader';
import Section from '~/components/layout/Section';
import { api } from '~/trpc/server';

export const dynamic = 'force-dynamic';

const ProtocolsPage = async () => {
const protocols = await api.protocol.get.all.query();
const allowAnonymousRecruitment =
Expand Down
2 changes: 2 additions & 0 deletions app/(dashboard)/dashboard/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { api } from '~/trpc/server';
import VersionSection from '~/components/VersionSection';
import { env } from '~/env.mjs';

export const dynamic = 'force-dynamic';

export default async function Settings() {
const allowAnonymousRecruitment =
await api.appSettings.getAnonymousRecruitmentStatus.query();
Expand Down
2 changes: 2 additions & 0 deletions app/(interview)/interview/[interviewId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { redirect } from 'next/navigation';
import { prisma } from '~/utils/db';
import { unstable_noStore } from 'next/cache';

export const dynamic = 'force-dynamic';

export default async function Page({
params,
}: {
Expand Down
13 changes: 0 additions & 13 deletions app/(interview)/interview/not-found.tsx

This file was deleted.

2 changes: 2 additions & 0 deletions app/(interview)/onboard/[protocolId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { api } from '~/trpc/server';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';

export const dynamic = 'force-dynamic';

const handler = async (
req: NextRequest,
{ params }: { params: { protocolId: string } },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function ManageParticipants() {
controlArea={<LimitInterviewsSwitch />}
>
<Paragraph>
Limit each participant to complete one interview per protocol.
Limit each participant to being allowed to complete one interview per protocol.
</Paragraph>
</SettingsSection>
</div>
Expand Down
2 changes: 2 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const poppins = Quicksand({
display: 'swap',
});

export const dynamic = 'force-dynamic';

async function RootLayout({ children }: { children: React.ReactNode }) {
const session = await getServerSession();
const appSettings = await api.appSettings.get.query();
Expand Down
2 changes: 2 additions & 0 deletions components/LimitInterviewsSwitch/LimitInterviewsSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'server-only';
import { api } from '~/trpc/server';
import Switch from './Switch';

export const dynamic = 'force-dynamic';

const LimitInterviewsSwitch = async () => {
const limitInterviews =
await api.appSettings.getLimitInterviewsStatus.query();
Expand Down
5 changes: 4 additions & 1 deletion components/ui/skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ function Skeleton({
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div className={cn('animate-pulse bg-platinum', className)} {...props} />
<div
className={cn('animate-pulse rounded-input bg-platinum', className)}
{...props}
/>
);
}

Expand Down
Loading

0 comments on commit bb59d66

Please sign in to comment.