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

Bump to React 19 and Next 15, implement NUQS as also to manage query params #49

Merged
merged 3 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 21 additions & 25 deletions app/[locale]/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import { QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { SpeedInsights } from "@vercel/speed-insights/next";
import { useSearchParams } from "next/navigation";
import { type AbstractIntlMessages, NextIntlClientProvider } from "next-intl";
import { ThemeProvider } from "next-themes";
import type { ReactNode } from "react";
import { NuqsAdapter } from "nuqs/adapters/next/app";
import { Suspense, type ReactNode } from "react";

import { Toaster } from "@/components/ui/sonner";
import { getQueryClient } from "@/lib/query";
Expand All @@ -18,33 +18,29 @@ interface Props {
}

function Providers({ locale, messages, children }: Props) {
const searchParams = useSearchParams();
const queryClient = getQueryClient();
const now = new Date();

const forcedThemeFromSearchParams =
searchParams.get("view") === "map" ? "light" : undefined;

return (
<QueryClientProvider client={queryClient}>
<NextIntlClientProvider
locale={locale}
messages={messages}
now={now}
timeZone="America/Sao_Paulo"
>
<ThemeProvider
attribute="class"
defaultTheme="system"
forcedTheme={forcedThemeFromSearchParams}
>
<Toaster />
<SpeedInsights />
{children}
</ThemeProvider>
</NextIntlClientProvider>
<ReactQueryDevtools />
</QueryClientProvider>
<Suspense>
<QueryClientProvider client={queryClient}>
<NuqsAdapter>
<NextIntlClientProvider
locale={locale}
messages={messages}
now={now}
timeZone="America/Sao_Paulo"
>
<ThemeProvider attribute="class" defaultTheme="system">
<Toaster />
<SpeedInsights />
{children}
</ThemeProvider>
</NextIntlClientProvider>
</NuqsAdapter>
<ReactQueryDevtools />
</QueryClientProvider>
</Suspense>
);
}

Expand Down
50 changes: 22 additions & 28 deletions app/[locale]/[id]/_components/highline-tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
"use client";

import { motion } from "framer-motion";
import { useSearchParams } from "next/navigation";
import { motion } from "motion/react";
import { useTranslations } from "next-intl";
import { useMemo } from "react";
import { useQueryState } from "nuqs";

import type { Highline } from "@/app/actions/getHighline";
import { Ranking } from "@/components/Ranking";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useQueryString } from "@/hooks/useQueryString";

import Comments from "./Comments";
import Info from "./Info";
Expand All @@ -19,38 +17,34 @@ interface Props {

export const HighlineTabs = ({ highline }: Props) => {
const t = useTranslations("highline.tabs");
const searchParams = useSearchParams();
const { replaceQueryParam } = useQueryString();
const [tab, setTab] = useQueryState("tab");

const selectedTab = searchParams.get("tab") || "info";
const selectedTab = tab || "info";

const tabs = useMemo(
() => [
{
id: "info",
label: t("informations.label"),
content: <Info highline={highline} />,
},
{
id: "comments",
label: t("comments"),
content: <Comments highline={highline} />,
},
{
id: "ranking",
label: "Ranking",
content: <Ranking highlines_ids={[highline.id]} />,
},
],
[t, highline]
);
const tabs = [
{
id: "info",
label: t("informations.label"),
content: <Info highline={highline} />,
},
{
id: "comments",
label: t("comments"),
content: <Comments highline={highline} />,
},
{
id: "ranking",
label: "Ranking",
content: <Ranking highlines_ids={[highline.id]} />,
},
];

return (
<>
<Tabs
value={selectedTab}
onValueChange={(value) => {
replaceQueryParam("tab", value);
setTab(value);
}}
>
<TabsList className="my-2 w-full gap-2">
Expand Down
15 changes: 9 additions & 6 deletions app/[locale]/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import HighlineCard from "./_components/HighlineCard";
export const dynamic = "force-dynamic";

type Props = {
params: { id: string };
searchParams: { [key: string]: string | undefined };
params: Promise<{ id: string }>;
searchParams: Promise<{ [key: string]: string | undefined }>;
};

const getHigh = cache(async ({ id }: { id: string }) => {
Expand All @@ -19,10 +19,13 @@ const getHigh = cache(async ({ id }: { id: string }) => {
return result.data;
});

export default async function Highline({
params: { id },
searchParams,
}: Props) {
export default async function Highline(props: Props) {
const params = await props.params;

const {
id
} = params;

const highlines = await getHigh({ id });

if (!highlines || highlines.length === 0) return notFound();
Expand Down
13 changes: 8 additions & 5 deletions app/[locale]/_components/HighlineList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client";

import { useInfiniteQuery } from "@tanstack/react-query";
import { motion } from "framer-motion";
import { useSearchParams } from "next/navigation";
import { motion } from "motion/react";
import { useQueryState } from "nuqs";

import { getHighline } from "@/app/actions/getHighline";

Expand All @@ -12,13 +12,16 @@ import { HighlineListSkeleton } from "./HighlineListSkeleton";
const PAGE_SIZE = 6;

export function HighlineList() {
const searchParams = useSearchParams();
const searchValue = searchParams.get("q") || "";
const [searchValue = ""] = useQueryState("q");

const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteQuery({
queryKey: ["highlines", { searchValue }],
queryFn: ({ pageParam }) =>
getHighline({ pageParam, searchValue, pageSize: PAGE_SIZE }),
getHighline({
pageParam,
searchValue: searchValue ?? undefined,
pageSize: PAGE_SIZE,
}),
initialPageParam: 1,
getNextPageParam: (lastPage, pages) => {
const nextPage = pages.length + 1;
Expand Down
16 changes: 8 additions & 8 deletions app/[locale]/_components/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@

import { SearchIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useQueryState } from "nuqs";

import { Input } from "@/components/ui/input";
import { useQueryString } from "@/hooks/useQueryString";

export default function Search() {
const t = useTranslations("home");
const { searchParams, pushQueryParam, deleteQueryParam } = useQueryString();
const [search, setSearch] = useQueryState("q");

function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const val = e.target as HTMLFormElement;
const search = val.search as HTMLInputElement;
if (search.value) {
pushQueryParam("q", search.value);
const searchInput = val.search as HTMLInputElement;
if (searchInput.value) {
setSearch(searchInput.value);
} else {
deleteQueryParam("q");
setSearch(null);
}
}

Expand All @@ -27,12 +27,12 @@ export default function Search() {
<SearchIcon className="h-6 w-6 text-muted-foreground" />
</span>
<Input
key={searchParams?.get("q")}
key={search}
type="search"
name="search"
placeholder={t("searchPlaceholder")}
autoComplete="off"
defaultValue={searchParams?.get("q") || ""}
defaultValue={search || ""}
className="bg-transparent pl-10 text-base"
/>
</form>
Expand Down
4 changes: 1 addition & 3 deletions app/[locale]/auth/callback/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Refer to the following documentation for more context
// https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-sign-in-with-code-exchange

import { cookies } from "next/headers";
import { NextResponse } from "next/server";

import { useSupabaseServer } from "@/utils/supabase/server";
Expand All @@ -16,9 +15,8 @@ export async function GET(request: Request) {
const redirectTo = requestUrl.searchParams.get("redirect_to");

if (code) {
const cookieStore = cookies();
// eslint-disable-next-line react-hooks/rules-of-hooks
const supabase = useSupabaseServer(cookieStore);
const supabase = await useSupabaseServer();

await supabase.auth.exchangeCodeForSession(code);
}
Expand Down
5 changes: 2 additions & 3 deletions app/[locale]/festival/_components/festival-tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { cookies } from "next/headers";
import React from "react";

import { getHighline } from "@/app/actions/getHighline";
Expand All @@ -9,8 +8,8 @@ import { useSupabaseServer } from "@/utils/supabase/server";
import { Highline } from "../../_components/Highline";

export const FestivalTabs = async () => {
const cookieStore = cookies();
const supabase = useSupabaseServer(cookieStore);
// eslint-disable-next-line react-hooks/rules-of-hooks
const supabase = await useSupabaseServer();

const { data: sectors } = await supabase
.from("sector")
Expand Down
21 changes: 13 additions & 8 deletions app/[locale]/festival/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { Loader2 } from "lucide-react";
import Image from "next/image";
import { useTranslations } from "next-intl";
import { Suspense } from "react";
import { unstable_setRequestLocale } from "next-intl/server";
import { Suspense, use } from "react";

import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";

import { FestivalTabs } from "./_components/festival-tabs";
import { Loader2 } from "lucide-react";

type Props = {
params: { locale: string; username: string };
searchParams: { [key: string]: string | undefined };
params: Promise<{ locale: string; username: string }>;
searchParams: Promise<{ [key: string]: string | undefined }>;
};

export default function Festival({
params: { username },
searchParams,
}: Props) {
export default function Festival(props: Props) {
const params = use(props.params);

const {
locale
} = params;

unstable_setRequestLocale(locale);
const t = useTranslations("festival");

return (
Expand Down
36 changes: 26 additions & 10 deletions app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { GeistSans } from "geist/font/sans";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { useMessages } from "next-intl";
import { unstable_setRequestLocale } from "next-intl/server";
import { use } from "react";

import Footer from "@/components/Footer";
// import Footer from "@/components/Footer";
import NavBar from "@/components/layout/navbar";
import { locales } from "@/navigation";

Expand Down Expand Up @@ -59,21 +61,35 @@ export const metadata: Metadata = {
},
};

export default function RootLayout({
children,
params: { locale },
}: {
children: React.ReactNode;
params: { locale: "en" | "pt" };
}) {
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}

export default function RootLayout(
props: {
children: React.ReactNode;
params: Promise<{ locale: "en" | "pt" }>;
}
) {
const params = use(props.params);

const {
locale
} = params;

const {
children
} = props;

unstable_setRequestLocale(locale);
// Validate that the incoming `locale` parameter is valid
if (!locales.includes(locale)) notFound();
const messages = useMessages();

return (
// suppressHydrationWarning because of `next-themes`
// refer to https://github.com/pacocoursey/next-themes#with-app
<html lang={locale} suppressHydrationWarning>
(<html lang={locale} suppressHydrationWarning>
<body className={`min-h-screen md:px-0 ${GeistSans.variable} font-sans`}>
<Providers locale={locale} messages={messages}>
<div className="relative flex h-full min-h-screen flex-col">
Expand All @@ -88,6 +104,6 @@ export default function RootLayout({
</Providers>
<Analytics />
</body>
</html>
</html>)
);
}
Loading
Loading