Skip to content

Commit

Permalink
feat: single fetch (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
olros authored Apr 26, 2024
1 parent e0cbdc2 commit d5cfd3e
Show file tree
Hide file tree
Showing 30 changed files with 122 additions and 109 deletions.
12 changes: 5 additions & 7 deletions web/app/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@ import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';

export type NavbarProps = {
user: Pick<User, 'avatar_url' | 'name'>;
teams: SerializeFrom<
Prisma.TeamGetPayload<{
include: {
projects: true;
};
}>[]
>;
teams: Prisma.TeamGetPayload<{
include: {
projects: true;
};
}>[];
};

export const Navbar = ({ user, teams }: NavbarProps) => {
Expand Down
4 changes: 2 additions & 2 deletions web/app/components/statistics/loader.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { format, setDay, subMinutes } from 'date-fns';
import { CURRENT_VISITORS_LAST_MINUTES, formatFilterDate, getFilteringParams, mapTrendDataToTrend } from './utils';

export type LoadStatistics = Awaited<ReturnType<typeof loadStatistics>>;
export type LoadStatisticsSerialized = SerializeFrom<Awaited<ReturnType<typeof loadStatistics>>>;
export type LoadStatisticsSerialized = Awaited<ReturnType<typeof loadStatistics>>;

export type Trend = {
x: Date;
y: number;
};
export type TrendSerialized = SerializeFrom<Trend>;
export type TrendSerialized = Trend;

export type TopData = {
name: string | null;
Expand Down
9 changes: 6 additions & 3 deletions web/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import '@fontsource-variable/inter/index.css';
import { Links, Meta, Outlet, Scripts, ScrollRestoration, useLocation, useParams } from '@remix-run/react';
import type { LinksFunction } from '@remix-run/node';
import { ErrorBoundary as BaseErrorBoundary } from '~/components/ErrorBoundary';
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';

import { stats } from './stats';

Expand All @@ -19,11 +19,14 @@ export type LayoutProps = {
export const Layout = ({ children }: LayoutProps) => {
const location = useLocation();
const params = useParams<Record<string, string>>();
const pathname = useMemo(
() => Object.entries(params as Record<string, string>).reduce((path, [key, param]) => path.replace(`/${param}`, `/:${key}`), location.pathname),
[location.pathname, params],
);

useEffect(() => {
const pathname = Object.entries(params as Record<string, string>).reduce((path, [key, param]) => path.replace(`/${param}`, `/:${key}`), location.pathname);
stats.pageview({ pathname });
}, [location.pathname, location.search, params]);
}, [pathname]);

return (
<html lang='no'>
Expand Down
3 changes: 1 addition & 2 deletions web/app/routes/_authed.dashboard._index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Link, NavLink, useLoaderData } from '@remix-run/react';
import type { LoaderFunctionArgs } from '@vercel/remix';
import { json } from '@vercel/remix';
import { getUserOrRedirect } from '~/auth.server';
import { Typography } from '~/components/typography';
import { Button } from '~/components/ui/button';
Expand All @@ -20,7 +19,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
},
},
});
return json({ teams });
return { teams };
};

export default function Dashboard() {
Expand Down
13 changes: 7 additions & 6 deletions web/app/routes/_authed.dashboard.new-team.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { Prisma } from '@prisma/client';
import { Form, Link, useActionData, useNavigation } from '@remix-run/react';
import type { ActionFunctionArgs } from '@vercel/remix';
import { json, redirect } from '@vercel/remix';
import { getUserOrRedirect } from '~/auth.server';
import { Typography } from '~/components/typography';
import { Button } from '~/components/ui/button';
import { Card } from '~/components/ui/card';
import { Input } from '~/components/ui/input';
import { Label } from '~/components/ui/label';
import { prismaClient } from '~/prismaClient';
import { slugify } from '~/utils';
import { redirect, slugify } from '~/utils.server';

export { ErrorBoundary } from '~/components/ErrorBoundary';

export const action = async ({ request }: ActionFunctionArgs) => {
export const action = async ({ request, response }: ActionFunctionArgs) => {
const user = await getUserOrRedirect(request);
const formData = await request.formData();
const name = formData.get('name') as string;
Expand All @@ -29,14 +28,16 @@ export const action = async ({ request }: ActionFunctionArgs) => {
},
},
});
return redirect(`/dashboard/${team.slug}`);
return redirect(response, `/dashboard/${team.slug}`);
} catch (e) {
console.error('[New Team]', e);
if (e instanceof Prisma.PrismaClientKnownRequestError) {
return json({ errors: { name: 'This team name is already taken' } }, { status: 400 });
response!.status = 400;
return { errors: { name: 'This team name is already taken' } };
}
}
return json({ errors: { name: 'Something went wrong' } }, { status: 400 });
response!.status = 400;
return { errors: { name: 'Something went wrong' } };
};

export default function CreateTeam() {
Expand Down
3 changes: 1 addition & 2 deletions web/app/routes/_authed.dashboard_.$teamSlug._index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { NavLink, useLoaderData } from '@remix-run/react';
import type { LoaderFunctionArgs } from '@vercel/remix';
import { json } from '@vercel/remix';
import { ensureIsTeamMember } from '~/auth.server';
import { prismaClient } from '~/prismaClient';
import invariant from 'tiny-invariant';
Expand All @@ -17,7 +16,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
},
});

return json({ projects: await projects });
return { projects: await projects };
};

export default function TeamDashboard() {
Expand Down
15 changes: 8 additions & 7 deletions web/app/routes/_authed.dashboard_.$teamSlug.members.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Form, useActionData, useLoaderData, useNavigation } from '@remix-run/react';
import type { ActionFunctionArgs, LoaderFunctionArgs } from '@vercel/remix';
import { json } from '@vercel/remix';
import { ensureIsTeamMember } from '~/auth.server';
import { prismaClient } from '~/prismaClient';
import invariant from 'tiny-invariant';
Expand All @@ -27,10 +26,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
},
});

return json({ members: await members });
return { members: await members };
};

export const action = async ({ request, params }: ActionFunctionArgs) => {
export const action = async ({ request, params, response }: ActionFunctionArgs) => {
invariant(params.teamSlug, 'Expected params.teamSlug');
const team = await ensureIsTeamMember(request, params.teamSlug);

Expand All @@ -49,7 +48,8 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
});

if (!user) {
return json({ errors: { github_username: 'No users exists with this GitHub username' } }, { status: 400 });
response!.status = 400;
return { errors: { github_username: 'No users exists with this GitHub username' } };
}

await prismaClient.teamUser.create({
Expand All @@ -59,7 +59,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
},
});

return json({ detail: 'Member successfully added to the team' });
return { detail: 'Member successfully added to the team' };
}
if (request.method === 'DELETE') {
const userId = formData.get('userId') as string;
Expand All @@ -74,9 +74,10 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
},
});

return json({ detail: 'Member successfully removed from the team' });
return { detail: 'Member successfully removed from the team' };
} catch {
return json({ errors: { userId: 'Something went wrong' } }, { status: 400 });
response!.status = 400;
return { errors: { userId: 'Something went wrong' } };
}
}
};
Expand Down
16 changes: 9 additions & 7 deletions web/app/routes/_authed.dashboard_.$teamSlug.new-project.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Prisma } from '@prisma/client';
import { Form, Link, useActionData, useNavigation, useParams } from '@remix-run/react';
import type { ActionFunctionArgs } from '@vercel/remix';
import { json, redirect } from '@vercel/remix';
import { ensureIsTeamMember } from '~/auth.server';
import { prismaClient } from '~/prismaClient';
import { slugify } from '~/utils';
import { redirect, slugify } from '~/utils.server';
import invariant from 'tiny-invariant';
import { Card } from '~/components/ui/card';
import { Typography } from '~/components/typography';
Expand All @@ -14,7 +13,7 @@ import { Label } from '~/components/ui/label';

export { ErrorBoundary } from '~/components/ErrorBoundary';

export const action = async ({ request, params }: ActionFunctionArgs) => {
export const action = async ({ request, params, response }: ActionFunctionArgs) => {
invariant(params.teamSlug, 'Expected params.teamSlug');
const team = await ensureIsTeamMember(request, params.teamSlug);
const formData = await request.formData();
Expand All @@ -23,7 +22,8 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
const slug = slugify(name);

if (slug === 'settings' || slug === 'members') {
return json({ errors: { name: `"settings" and "members" are protected project names, chose a different name and try again` } }, { status: 400 });
response!.status = 400;
return { errors: { name: `"settings" and "members" are protected project names, chose a different name and try again` } };
}

try {
Expand All @@ -36,13 +36,15 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
allowed_hosts: '',
},
});
return redirect(`/dashboard/${team.slug}/${project.slug}`);
return redirect(response, `/dashboard/${team.slug}/${project.slug}`);
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
return json({ errors: { name: `This team already contains a project named "${name}"` } }, { status: 400 });
response!.status = 400;
return { errors: { name: `This team already contains a project named "${name}"` } };
}
}
return json({ errors: { name: 'Something went wrong' } }, { status: 400 });
response!.status = 400;
return { errors: { name: 'Something went wrong' } };
};

export default function CreateProject() {
Expand Down
16 changes: 9 additions & 7 deletions web/app/routes/_authed.dashboard_.$teamSlug.settings.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Prisma } from '@prisma/client';
import { Form, useActionData, useLoaderData, useNavigation } from '@remix-run/react';
import type { ActionFunctionArgs, LoaderFunctionArgs } from '@vercel/remix';
import { json, redirect } from '@vercel/remix';
import { ensureIsTeamMember } from '~/auth.server';
import { prismaClient } from '~/prismaClient';
import { useState } from 'react';
import invariant from 'tiny-invariant';
import { redirect } from '~/utils.server';
import { Card } from '~/components/ui/card';
import { Typography } from '~/components/typography';
import { Input } from '~/components/ui/input';
Expand All @@ -17,10 +17,10 @@ export { ErrorBoundary } from '~/components/ErrorBoundary';
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
invariant(params.teamSlug, 'Expected params.teamSlug');
const team = await ensureIsTeamMember(request, params.teamSlug);
return json({ team });
return { team };
};

export const action = async ({ request, params }: ActionFunctionArgs) => {
export const action = async ({ request, params, response }: ActionFunctionArgs) => {
invariant(params.teamSlug, 'Expected params.teamSlug');
await ensureIsTeamMember(request, params.teamSlug);
if (request.method === 'PUT') {
Expand All @@ -35,10 +35,11 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
slug: params.teamSlug.toLowerCase(),
},
});
return redirect(`/dashboard/${team.slug}/settings`);
return redirect(response, `/dashboard/${team.slug}/settings`);
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
return json({ errors: { name: 'This team name is already taken' } }, { status: 400 });
response!.status = 400;
return { errors: { name: 'This team name is already taken' } };
}
}
}
Expand All @@ -48,9 +49,10 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
slug: params.teamSlug.toLowerCase(),
},
});
return redirect(`/dashboard`);
return redirect(response, `/dashboard`);
}
return json({ errors: { name: 'Something went wrong' } }, { status: 400 });
response!.status = 400;
return { errors: { name: 'Something went wrong' } };
};

export default function TeamSettings() {
Expand Down
7 changes: 3 additions & 4 deletions web/app/routes/_authed.dashboard_.$teamSlug.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { Link, Outlet, useLoaderData } from '@remix-run/react';
import { Link, Outlet, useLoaderData, type MetaArgs_SingleFetch } from '@remix-run/react';
import type { LoaderFunctionArgs, MetaFunction } from '@vercel/remix';
import { ensureIsTeamMember } from '~/auth.server';
import { LinkTabs } from '~/components/LinkTabs';
import { jsonHash } from 'remix-utils/json-hash';
import invariant from 'tiny-invariant';
import { Button } from '~/components/ui/button';
import { Card } from '~/components/ui/card';
import { Typography } from '~/components/typography';

export { ErrorBoundary } from '~/components/ErrorBoundary';

export const meta: MetaFunction<typeof loader> = ({ data }) => [{ title: `${data?.team.name} | Stats` }];
export const meta = ({ data }: MetaArgs_SingleFetch<typeof loader>) => [{ title: `${data?.team.name} | Stats` }];

export const loader = async ({ request, params }: LoaderFunctionArgs) => {
invariant(params.teamSlug, 'Expected params.teamSlug');
return jsonHash({ team: ensureIsTeamMember(request, params.teamSlug) });
return { team: await ensureIsTeamMember(request, params.teamSlug) };
};

const TABS = [
Expand Down
3 changes: 1 addition & 2 deletions web/app/routes/_authed.dashboard_.$teamSlug.usage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { LoaderFunctionArgs } from '@vercel/remix';
import { ensureIsTeamMember } from '~/auth.server';
import type { Usage } from '~/utils_usage.server';
import { getTeamUsage } from '~/utils_usage.server';
import { jsonHash } from 'remix-utils/json-hash';
import invariant from 'tiny-invariant';
import { Card } from '~/components/ui/card';
import { Typography } from '~/components/typography';
Expand All @@ -15,7 +14,7 @@ export { ErrorBoundary } from '~/components/ErrorBoundary';
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
invariant(params.teamSlug, 'Expected params.teamSlug');
await ensureIsTeamMember(request, params.teamSlug);
return jsonHash({ usage: getTeamUsage(params.teamSlug) });
return { usage: await getTeamUsage(params.teamSlug) };
};

export const UsageDisplay = ({ label, description, usage }: { label: string; description?: string; usage: Usage }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Statistics } from '~/components/statistics';
import { loadStatistics } from '~/components/statistics/loader.server';
import { useInterval } from '~/hooks/useInterval';
import { secondsToMilliseconds } from 'date-fns';
import { jsonHash } from 'remix-utils/json-hash';
import invariant from 'tiny-invariant';

export { ErrorBoundary } from '~/components/ErrorBoundary';
Expand All @@ -14,7 +13,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
invariant(params.teamSlug, 'Expected params.teamSlug');
invariant(params.projectSlug, 'Expected params.projectSlug');
await ensureIsTeamMember(request, params.teamSlug);
return jsonHash({ statistics: loadStatistics({ request, teamSlug: params.teamSlug, projectSlug: params.projectSlug }) });
return { statistics: await loadStatistics({ request, teamSlug: params.teamSlug, projectSlug: params.projectSlug }) };
};

export default function ProjectPageviewsStatistics() {
Expand Down
Loading

0 comments on commit d5cfd3e

Please sign in to comment.