Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: pages loading #69

Merged
merged 13 commits into from
Jun 7, 2024
11 changes: 8 additions & 3 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import nextBundleAnalyzer from "@next/bundle-analyzer";

/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
* for Docker builds.
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation
*/
await import("./src/env.js");

/** @type {import("next").NextConfig} */
const config = {};

export default config;
const withBundleAnalyzer = nextBundleAnalyzer({
enabled: process.env.ANALYZE === "true",
});

export default withBundleAnalyzer(config);
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": "module",
"scripts": {
"build": "next build",
"build:with-analyzer": "ANALYZE=true yarn build",
"db:push": "prisma db push",
"db:studio": "prisma studio",
"dev": "next dev",
Expand All @@ -17,6 +18,7 @@
"dependencies": {
"@auth/prisma-adapter": "^1.4.0",
"@hookform/resolvers": "^3.3.4",
"@next/bundle-analyzer": "^14.2.3",
"@prisma/client": "^5.14.0",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
Expand All @@ -28,6 +30,7 @@
"@radix-ui/react-slot": "^1.0.2",
"@t3-oss/env-nextjs": "^0.10.1",
"@tanstack/react-query": "^5.25.0",
"@tanstack/react-query-devtools": "^5.40.1",
"@tanstack/react-table": "^8.17.3",
"@trpc/client": "next",
"@trpc/react-query": "next",
Expand Down
File renamed without changes.
18 changes: 16 additions & 2 deletions src/app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { type Metadata } from "next";
import Image from "next/image";

export const metadata: Metadata = {
title: "Sobre",
description:
"O SOS Pet é um sistema dedicado a conectar animais resgatados de enchentes com abrigos temporários disponíveis. Acreditamos que, em momentos de crise, cada vida é importante, e é nossa missão ajudar a garantir que animais em situação de risco encontrem um local seguro e acolhedor enquanto aguardam seu retorno ao lar ou um novo começo.",
keywords: "Sobre, SOS Pet, animais, abrigos",
};

export const dynamic = "force-static";
export const revalidate = 60 * 60 * 24; // Revalidate almost once a day

export default function About() {
return (
<main className="mb-8">
Expand Down Expand Up @@ -61,8 +72,11 @@ export default function About() {
transformar um momento de crise em uma oportunidade para fazer a
diferença na vida de um animal. Juntos, podemos salvar vidas e
construir um futuro mais seguro para nossos amigos de quatro patas.
Além disso, esse projeto tem o código fonte aberto e disponível para colaboração:
<a href="https://github.com/emiliosheinz/sos-pet" target="blank"> https://github.com/emiliosheinz/sos-pet</a>
Além disso, esse projeto tem o código fonte aberto e disponível para
colaboração:
<a href="https://github.com/emiliosheinz/sos-pet" target="blank">
https://github.com/emiliosheinz/sos-pet
</a>
</p>
</div>
</div>
Expand Down
6 changes: 4 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import "~/styles/globals.css";
import { Inter } from "next/font/google";
import { TRPCReactProvider } from "~/trpc/react";
import { Analytics } from "@vercel/analytics/react";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

import Providers from "./providers";
import Providers from "./_components/providers";
import { getServerAuthSession } from "~/server/auth";
import { Toaster } from "~/components/ui/sonner";
import { Footer } from "~/components/footer";
Expand Down Expand Up @@ -70,14 +71,15 @@ export default async function RootLayout({
const session = await getServerAuthSession();

return (
<html lang="en">
<html lang="pt-BR">
<body className={`font-sans ${inter.variable}`}>
<Providers session={session}>
<TRPCReactProvider>
<Header />
<div className="min-h-[60vh]">{children}</div>
<Lead />
<Footer />
<ReactQueryDevtools />
</TRPCReactProvider>
</Providers>
<Toaster />
Expand Down
22 changes: 9 additions & 13 deletions src/app/map/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@
import { type LatLngTuple } from "leaflet";
import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
import { Suspense, useEffect, useState } from "react";
import { Skeleton } from "~/components/ui/skeleton";
import { api } from "~/trpc/react";

const DEFAULT_LOCATION: LatLngTuple = [-30.0346, -51.2177]; // Porto Alegre

const Loader = <Skeleton className="h-[75vh] w-full max-w-7xl rounded-md" />;
const MapComponent = dynamic(() => import("~/components/map/"), {
loading: () => <Loader2 className="size-8 animate-spin" />,
ssr: false,
loading: () => Loader,
});

export default function Map() {
const { data, isLoading } = api.shelter.findAll.useQuery();
const [shelters] = api.shelter.findAll.useSuspenseQuery();
const [userLocation, setUserLocation] =
useState<LatLngTuple>(DEFAULT_LOCATION);

Expand All @@ -29,17 +31,11 @@ export default function Map() {
);
}, []);

if (isLoading && !data) {
return (
<div className="flex w-full justify-center pt-28">
<Loader2 className="size-8 animate-spin" />
</div>
);
}

return (
<main className="flex w-full justify-center pt-8">
<MapComponent userLocation={userLocation} shelter={data ?? []} />
<main className="relative flex w-full justify-center px-2 pt-8">
<Suspense fallback={Loader}>
<MapComponent userLocation={userLocation} shelter={shelters} />
emiliosheinz marked this conversation as resolved.
Show resolved Hide resolved
</Suspense>
</main>
);
}
68 changes: 41 additions & 27 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
"use client";
import { Card } from "~/components/card/";
import { SearchInput } from "~/components/search-input";
import { api } from "~/trpc/react";
import Fuse from "fuse.js";
import { useMemo } from "react";
import { type PropsWithChildren, Suspense, useMemo } from "react";
import { Skeleton } from "~/components/ui/skeleton";
import { useDebouncedState } from "~/hooks/use-debouced-state";
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
import { FiInfo } from "react-icons/fi";
import dynamic from "next/dynamic";

const ShelterCard = dynamic(
() =>
import("~/components/shelter-card").then((module) => module.ShelterCard),
{ loading: () => <Skeleton className="h-[306px] w-full rounded-xl" /> },
);

const Grid = ({ children }: PropsWithChildren) => (
<div className="grid w-full max-w-7xl grid-cols-1 gap-5 md:grid-cols-2">
{children}
</div>
);

export default function Home() {
const { data, isLoading } = api.shelter.findAll.useQuery();
const [shelters] = api.shelter.findAll.useSuspenseQuery();
const [searchTerm, setSearchTerm] = useDebouncedState("", 300);

const filteredShelters = useMemo(() => {
const trimmedSearchTerm = searchTerm.trim();
if (trimmedSearchTerm.length === 0 || !data) {
return data ?? [];
if (trimmedSearchTerm.length === 0 || !shelters) {
return shelters ?? [];
}

const fuse = new Fuse(data, {
const fuse = new Fuse(shelters, {
keys: [
"name",
"addressStreet",
Expand All @@ -33,7 +45,7 @@ export default function Home() {
});

return fuse.search(trimmedSearchTerm).map((result) => result.item);
}, [data, searchTerm]);
}, [shelters, searchTerm]);

const handleSearch = (event: { target: { value: string } }) => {
setSearchTerm(event.target.value);
Expand All @@ -50,35 +62,37 @@ export default function Home() {
abrigo.
</AlertDescription>
</Alert>

<div className="mb-6 flex w-full max-w-7xl items-center justify-between space-x-4">
<SearchInput handleSearch={handleSearch} />
</div>
{!isLoading && !filteredShelters?.length && (
<div className="text-center text-gray-600">
<span className="mb-2 font-bold">
Desculpe, nenhum resultado encontrado.
</span>
<p>
Não encontramos nenhum resultado correspondente à sua busca. Por
favor, revise os critérios de pesquisa e tente novamente.
</p>
</div>
)}
<div className="grid w-full max-w-7xl grid-cols-1 gap-5 md:grid-cols-2">
{isLoading ? (
<>
<Suspense
fallback={
<Grid>
<Skeleton className="h-[306px] w-full rounded-xl" />
<Skeleton className="h-[306px] w-full rounded-xl" />
<Skeleton className="h-[306px] w-full rounded-xl" />
<Skeleton className="h-[306px] w-full rounded-xl" />
</>
</Grid>
}
>
{!!filteredShelters?.length ? (
<Grid>
{filteredShelters?.map((shelter) => (
<ShelterCard key={shelter.id} shelter={shelter} />
))}
</Grid>
) : (
filteredShelters?.map((shelter) => (
<Card key={shelter.id} shelter={shelter} />
))
<div className="max-w-md pt-5 text-center text-gray-600">
<span className="mb-2 font-bold">
Desculpe, nenhum resultado encontrado.
</span>
<p>
Não encontramos nenhum resultado correspondente à sua busca. Por
favor, revise os critérios de pesquisa e tente novamente.
</p>
</div>
)}
</div>
</Suspense>
</main>
);
}
14 changes: 12 additions & 2 deletions src/app/privacy-policy/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
export default function TermsOfUse() {
import { type Metadata } from "next";

export const metadata: Metadata = {
title: "Políticas de Privacidade",
description: "Políticas de privacidade do SOS Pet",
keywords: "políticas de privacidade, SOS Pet, animais, abrigos",
};

export const dynamic = "force-static";
export const revalidate = 60 * 60 * 24; // Revalidate almost once a day

export default function PrivacyPolicy() {
return (
<main className="container mb-8 max-w-5xl pt-16">
<h1 className="mb-10 text-3xl font-bold">Políticas de privacidade</h1>

<span className="mb-6 block font-light text-zinc-700">
Data de Atualização: 11 de maio de 2024
</span>

<div className="space-y-3 text-base font-light leading-7">
<p>
Esta Política de Privacidade explica como coletamos, usamos,
Expand Down
10 changes: 10 additions & 0 deletions src/app/terms-of-use/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { type Metadata } from "next";
import Link from "next/link";

export const metadata: Metadata = {
title: "Termos de Uso",
description: "Termos de Uso do SOS Pet",
keywords: "Termos de Uso, SOS Pet, animais, abrigos",
};

export const dynamic = "force-static";
export const revalidate = 60 * 60 * 24; // Revalidate almost once a day

export default function TermsOfUse() {
return (
<main className="container mb-8 max-w-5xl pt-16">
Expand Down
22 changes: 14 additions & 8 deletions src/components/map/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { useEffect } from "react";
import { MapContainer, TileLayer, Marker, Popup, useMap } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import "leaflet-defaulticon-compatibility";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";

import { useEffect } from "react";
import { MapContainer, TileLayer, Marker, Popup, useMap } from "react-leaflet";
import { type LatLngTuple } from "leaflet";
import { type Shelter } from "@prisma/client";
import { Card } from "../card";
import { ShelterCard } from "../shelter-card";

function UserLocationMap({ userLocation }: { userLocation: LatLngTuple }) {
const map = useMap();

useEffect(() => {
if (userLocation) {
map.setView(userLocation, 13);
map.setView(userLocation, 8);
}
}, [userLocation, map]);

Expand All @@ -28,9 +29,14 @@ export default function Map({
}) {
return (
<MapContainer
style={{ height: "800px", width: "100%", maxWidth: "1280px" }}
style={{
height: "75vh",
width: "100%",
maxWidth: "1280px",
borderRadius: "6px",
}}
center={userLocation}
zoom={13}
zoom={8}
>
<UserLocationMap userLocation={userLocation} />
<TileLayer
Expand All @@ -46,7 +52,7 @@ export default function Map({
position={[shelter.latitude, shelter.longitude]}
>
<Popup minWidth={390}>
<Card
<ShelterCard
shelter={shelter}
className="border-none p-2 text-black shadow-none"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Props = {
shelter: Shelter;
} & React.ComponentProps<typeof CardBase>;

export function Card({ shelter, ...otherProps }: Props) {
export function ShelterCard({ shelter, ...otherProps }: Props) {
const fullAddress = `${shelter.addressStreet} ${shelter.addressNumber} ${shelter.addressNeighborhood}, ${shelter.addressCity}, ${shelter.addressState}`;

const availableVacancies = shelter.capacity - shelter.occupancy;
Expand Down
Loading