Skip to content

Commit

Permalink
Switch to Next.js App router with SSR
Browse files Browse the repository at this point in the history
  • Loading branch information
sshader committed Sep 23, 2024
1 parent 9713518 commit 884badd
Show file tree
Hide file tree
Showing 18 changed files with 539 additions and 367 deletions.
15 changes: 15 additions & 0 deletions app/ConvexClientProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import { ConvexAuthNextjsProvider } from "@convex-dev/auth/nextjs";
import { ConvexReactClient } from "convex/react";
import { ReactNode } from "react";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: ReactNode }) {
return (
<ConvexAuthNextjsProvider client={convex}>
{children}
</ConvexAuthNextjsProvider>
);
}
51 changes: 51 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "../styles/globals.css";
import { ConvexAuthNextjsServerProvider } from "@convex-dev/auth/nextjs/server";
import { ConvexClientProvider } from "./ConvexClientProvider";

import { SearchBar } from "../components/SearchBar";
import { UserBadge } from "../components/UserBadge";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Convex + Next.js + Convex Auth",
description: "Generated by npm create convex",
icons: {
icon: "/convex.svg",
},
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<ConvexAuthNextjsServerProvider verbose={true}>
{/* `suppressHydrationWarning` only affects the html tag,
and is needed by `ThemeProvider` which sets the theme
class attribute on it */}
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<main
style={{
display: "flex",
flexDirection: "column",
width: "100vw",
height: "100vh",
}}
>
<ConvexClientProvider>
<SearchBar />
<h1>Convex Chess</h1>
<UserBadge />
{children}
</ConvexClientProvider>
</main>
</body>
</html>
</ConvexAuthNextjsServerProvider>
);
}
14 changes: 14 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { preloadQuery } from "convex/nextjs";
import { GameList } from "../components/GameList";
import { Topbar } from "../components/Topbar";
import { api } from "../convex/_generated/api";

export default async function Home() {
const preloadedGames = await preloadQuery(api.games.ongoingGames);
return (
<>
<Topbar />
<GameList preloadedGames={preloadedGames} />
</>
);
}
26 changes: 14 additions & 12 deletions pages/play/[id].tsx → app/play/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { api } from "../../convex/_generated/api";
import { useRouter } from "next/router";
"use client";
import { api } from "../../../convex/_generated/api";
import { useRouter, useSearchParams } from "next/navigation";
import { Chess, Move, Square } from "chess.js";
import { Chessboard } from "react-chessboard";

import { useMutation, useQuery } from "convex/react";
import { Id } from "../../convex/_generated/dataModel";
import { validateMove, isOpen, playerEquals } from "../../convex/utils";
import { gameTitle } from "../../common";
import { Id } from "../../../convex/_generated/dataModel";
import { validateMove, isOpen, playerEquals } from "../../../convex/utils";
import { gameTitle } from "../../../common";
import { useEffect, useState } from "react";
import { Piece } from "react-chessboard/dist/chessboard/types";

export default function () {
const router = useRouter();
const gameId = router.query.id as Id<"games">;
const moveIdx = router.query.moveIndex
? Number(router.query.moveIndex)
: undefined;
export default function Game({ params }: { params: { id: string } }) {
const gameId = params.id as Id<"games">;
const searchParams = useSearchParams();
const moveIdx =
searchParams.get("moveIndex") !== undefined
? Number(searchParams.get("moveIndex"))
: undefined;

const gameState = useQuery(api.games.get, { id: gameId });
const user = useQuery(api.users.getMyUser) ?? null;
Expand Down Expand Up @@ -132,7 +134,7 @@ export default function () {
: "white";

return (
<main style={mainStyle}>
<main style={{ ...mainStyle, gap: 8 }}>
<div>{gameTitle(gameState)}</div>
<div className="game">
<div className="board">
Expand Down
12 changes: 5 additions & 7 deletions pages/user/[id].tsx → app/user/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { useRouter } from "next/router";

"use client";
import { useQuery, useMutation } from "convex/react";
import { useRef, useState } from "react";
import { Id } from "../../convex/_generated/dataModel";
import { api } from "../../convex/_generated/api";
import { Id } from "../../../convex/_generated/dataModel";
import { api } from "../../../convex/_generated/api";

export default function () {
const router = useRouter();
const userId = router.query.id as Id<"users">;
export default function Profile({ params }: { params: { id: string } }) {
const userId = params.id as Id<"users">;
const user = useQuery(api.users.get, { id: userId }) ?? null;
const myUser = useQuery(api.users.getMyUser) ?? null;

Expand Down
52 changes: 52 additions & 0 deletions components/GameList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"use client";

import { Preloaded, usePreloadedQuery, useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
import { gameTitle } from "../common";
import { hasPlayer, isOpen } from "../convex/utils";
import { JoinButton } from "./JoinButton";

export function GameList({
preloadedGames,
}: {
preloadedGames: Preloaded<typeof api.games.ongoingGames>;
}) {
const ongoingGames = usePreloadedQuery(preloadedGames) || [];
const user = useQuery(api.users.getMyUser) ?? null;

return (
<div
style={{
marginLeft: "auto",
marginRight: "auto",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<b>Ongoing Games</b>
<table>
<tbody>
{ongoingGames.map((game, i) => (
<tr key={game._id.toString()}>
<td>{gameTitle(game)}</td>
<td>
<JoinButton
text={
hasPlayer(game, user?._id ?? null)
? "Rejoin"
: isOpen(game)
? "Join"
: "Watch"
}
gameId={game._id.toString()}
disabled={isOpen(game) && !user}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
36 changes: 36 additions & 0 deletions components/JoinButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";

import { FormEvent } from "react";
import { useRouter } from "next/navigation";

export function JoinButton({
text,
gameId,
disabled,
}: {
text: string;
gameId: string;
disabled: boolean;
}) {
const router = useRouter();

async function join(event: FormEvent) {
event.preventDefault();
const gameId = (event.nativeEvent as any).submitter.id ?? "";
router.push(`/play/${gameId}`);
}

return (
<form onSubmit={join} className="d-flex justify-content-center">
<input
id={gameId}
type="submit"
value={text}
className="ms-2 btn btn-primary"
// We use user instead of userId here we can join immediately
// after logging in.
disabled={disabled}
/>
</form>
);
}
73 changes: 73 additions & 0 deletions components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"use client";

import { api } from "../convex/_generated/api";
import "../styles/globals.css";

import { useQuery } from "convex/react";

import { useState } from "react";
import Link from "next/link";
import { gameTitle } from "../common";

export function SearchBar() {
const [searchInput, setSearchInput] = useState("");

const handleChange = (e: any) => {
e.preventDefault();
setSearchInput(e.target.value);
};

const searchResults = useQuery(
api.search.default,
searchInput === ""
? "skip"
: {
query: searchInput,
}
) || { users: [], games: [] };
return (
<div style={{ position: "absolute", top: "0", left: "0" }}>
<div className="convexImage">
<a href="/">
<img src="/convex.svg"></img>
</a>
<div>
<input
type="text"
placeholder="Search here"
onChange={handleChange}
value={searchInput}
/>
</div>
<table>
<tbody>
{searchResults.users.map((result) => (
<tr key={result._id}>
<td>
{
<Link href={`/user/${result._id}`}>
{(result as any).name}
</Link>
}
</td>
</tr>
))}
{searchResults.games.map((result) => (
<tr key={result._id}>
<td>
<Link
href={`/play/${result._id}?moveIndex=${
result.moveIndex ?? ""
}`}
>
{gameTitle(result)}
</Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
24 changes: 24 additions & 0 deletions components/SignInButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use client";
import { useAuthActions } from "@convex-dev/auth/react";
import { useConvexAuth, useQuery } from "convex/react";

export function SignInButtons() {
const { signIn, signOut } = useAuthActions();
const convexAuthState = useConvexAuth();

const isAuthenticated = convexAuthState.isAuthenticated;
const isUnauthenticated =
!convexAuthState.isLoading && !convexAuthState.isAuthenticated;

return (
<div>
{isUnauthenticated ? (
<button onClick={() => signIn("google")}>Login</button>
) : isAuthenticated ? (
<button onClick={() => signOut()}>Logout</button>
) : (
<button>Loading...</button>
)}
</div>
);
}
Loading

0 comments on commit 884badd

Please sign in to comment.