add suspense for project cards

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-05-08 10:32:45 +02:00
parent 63c30caa7a
commit 4936ba1d40
3 changed files with 93 additions and 47 deletions

View File

@@ -1,6 +1,6 @@
import { FullPageEmptyState } from '@/components/full-page-empty-state'; import { FullPageEmptyState } from '@/components/full-page-empty-state';
import FullWidthNavbar from '@/components/full-width-navbar'; import FullWidthNavbar from '@/components/full-width-navbar';
import { ProjectCard } from '@/components/projects/project-card'; import ProjectCard from '@/components/projects/project-card';
import SignOutButton from '@/components/sign-out-button'; import SignOutButton from '@/components/sign-out-button';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';

View File

@@ -0,0 +1,27 @@
'use client';
import { useEffect, useRef } from 'react';
import { cn } from '@/utils/cn';
type Props = {
className?: string;
children: React.ReactNode;
};
export function FadeIn({ className, children }: Props) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (ref.current) {
ref.current.classList.remove('opacity-0');
ref.current.classList.add('opacity-100');
}
}, []);
return (
<div
className={cn('opacity-0 transition-opacity duration-500', className)}
ref={ref}
>
{children}
</div>
);
}

View File

@@ -1,21 +1,69 @@
import { Suspense } from 'react';
import withSuspense from '@/hocs/with-suspense';
import { shortNumber } from '@/hooks/useNumerFormatter'; import { shortNumber } from '@/hooks/useNumerFormatter';
import { Fallback } from '@radix-ui/react-avatar';
import { endOfMonth, startOfMonth } from 'date-fns';
import { escape } from 'sqlstring'; import { escape } from 'sqlstring';
import type { IServiceProject } from '@openpanel/db'; import type { IServiceProject } from '@openpanel/db';
import { chQuery } from '@openpanel/db'; import { chQuery } from '@openpanel/db';
import { ChartSSR } from '../chart-ssr'; import { ChartSSR } from '../chart-ssr';
import { FadeIn } from '../fade-in';
export async function ProjectCard({ function ProjectCard({ id, name, organizationSlug }: IServiceProject) {
id, // For some unknown reason I get when navigating back to this page when using <Link />
name, // Should be solved: https://github.com/vercel/next.js/issues/61336
organizationSlug, // But still get the error
}: IServiceProject) { return (
const [chart, [data]] = await Promise.all([ <a
chQuery<{ value: number; date: string }>( href={`/${organizationSlug}/${id}`}
className="card inline-flex flex-col gap-2 p-4 transition-transform hover:-translate-y-1"
>
<div className="font-medium">{name}</div>
<div className="-mx-4 aspect-[15/1]">
<Suspense>
<ProjectChart id={id} />
</Suspense>
</div>
<div className="flex justify-between gap-4 text-sm text-muted-foreground">
<div className="font-medium">Visitors</div>
<Suspense>
<ProjectMetrics id={id} />
</Suspense>
</div>
</a>
);
}
async function ProjectChart({ id }: { id: string }) {
const chart = await chQuery<{ value: number; date: string }>(
`SELECT countDistinct(profile_id) as value, toStartOfDay(created_at) as date FROM events WHERE project_id = ${escape(id)} AND name = 'session_start' AND created_at >= now() - interval '1 month' GROUP BY date ORDER BY date ASC` `SELECT countDistinct(profile_id) as value, toStartOfDay(created_at) as date FROM events WHERE project_id = ${escape(id)} AND name = 'session_start' AND created_at >= now() - interval '1 month' GROUP BY date ORDER BY date ASC`
), );
chQuery<{ total: number; month: number; day: number }>(
return (
<FadeIn className="h-full w-full">
<ChartSSR data={chart.map((d) => ({ ...d, date: new Date(d.date) }))} />
</FadeIn>
);
}
function Metric({ value, label }: { value: React.ReactNode; label: string }) {
return (
<div className="flex flex-col gap-2 md:flex-row">
<div>{label}</div>
<span className="font-medium text-black">{value}</span>
</div>
);
}
async function ProjectMetrics({ id }: { id: string }) {
const [metrics] = await chQuery<{
total: number;
month: number;
day: number;
}>(
` `
SELECT SELECT
( (
@@ -28,44 +76,15 @@ export async function ProjectCard({
SELECT count(DISTINCT profile_id) as count FROM events WHERE project_id = ${escape(id)} AND created_at >= now() - interval '1 day' SELECT count(DISTINCT profile_id) as count FROM events WHERE project_id = ${escape(id)} AND created_at >= now() - interval '1 day'
) as day ) as day
` `
), );
]);
// For some unknown reason I get when navigating back to this page when using <Link />
// Should be solved: https://github.com/vercel/next.js/issues/61336
// But still get the error
return ( return (
<a <FadeIn className="flex gap-4">
href={`/${organizationSlug}/${id}`} <Metric label="Total" value={shortNumber('en')(metrics?.total)} />
className="card inline-flex flex-col gap-2 p-4 transition-transform hover:-translate-y-1" <Metric label="Month" value={shortNumber('en')(metrics?.month)} />
> <Metric label="24h" value={shortNumber('en')(metrics?.day)} />
<div className="font-medium">{name}</div> </FadeIn>
<div className="-mx-4 aspect-[15/1]">
<ChartSSR data={chart.map((d) => ({ ...d, date: new Date(d.date) }))} />
</div>
<div className="flex justify-between gap-4 text-sm text-muted-foreground">
<div className="font-medium">Visitors</div>
<div className="flex gap-4">
<div className="flex flex-col gap-2 md:flex-row">
<div>Total</div>
<span className="font-medium text-black">
{shortNumber('en')(data?.total)}
</span>
</div>
<div className="flex flex-col gap-2 md:flex-row">
<div>Month</div>
<span className="font-medium text-black">
{shortNumber('en')(data?.month)}
</span>
</div>
<div className="flex flex-col gap-2 md:flex-row">
<div>24h</div>
<span className="font-medium text-black">
{shortNumber('en')(data?.day)}
</span>
</div>
</div>
</div>
</a>
); );
} }
export default ProjectCard;