diff --git a/package.json b/package.json index b311dd4..aa7aa2c 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,11 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@neondatabase/serverless": "^1.0.0", "@t3-oss/env-nextjs": "^0.12.0", + "@tanstack/react-query": "^5.72.2", + "@trpc/client": "^11.1.0", + "@trpc/next": "^11.1.0", + "@trpc/react-query": "^11.1.0", + "@trpc/server": "^11.1.0", "drizzle-orm": "^0.41.0", "next": "^15.2.3", "postgres": "^3.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae150a6..3dc9008 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,21 @@ importers: '@t3-oss/env-nextjs': specifier: ^0.12.0 version: 0.12.0(typescript@5.8.3)(zod@3.24.2) + '@tanstack/react-query': + specifier: ^5.72.2 + version: 5.72.2(react@19.1.0) + '@trpc/client': + specifier: ^11.1.0 + version: 11.1.0(@trpc/server@11.1.0(typescript@5.8.3))(typescript@5.8.3) + '@trpc/next': + specifier: ^11.1.0 + version: 11.1.0(@tanstack/react-query@5.72.2(react@19.1.0))(@trpc/client@11.1.0(@trpc/server@11.1.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/react-query@11.1.0(@tanstack/react-query@5.72.2(react@19.1.0))(@trpc/client@11.1.0(@trpc/server@11.1.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.1.0(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3))(@trpc/server@11.1.0(typescript@5.8.3))(next@15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) + '@trpc/react-query': + specifier: ^11.1.0 + version: 11.1.0(@tanstack/react-query@5.72.2(react@19.1.0))(@trpc/client@11.1.0(@trpc/server@11.1.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.1.0(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) + '@trpc/server': + specifier: ^11.1.0 + version: 11.1.0(typescript@5.8.3) drizzle-orm: specifier: ^0.41.0 version: 0.41.0(@neondatabase/serverless@1.0.0)(@types/pg@8.11.11)(gel@2.0.2)(postgres@3.4.5) @@ -750,6 +765,52 @@ packages: '@tailwindcss/postcss@4.1.3': resolution: {integrity: sha512-6s5nJODm98F++QT49qn8xJKHQRamhYHfMi3X7/ltxiSQ9dyRsaFSfFkfaMsanWzf+TMYQtbk8mt5f6cCVXJwfg==} + '@tanstack/query-core@5.72.2': + resolution: {integrity: sha512-fxl9/0yk3mD/FwTmVEf1/H6N5B975H0luT+icKyX566w6uJG0x6o+Yl+I38wJRCaogiMkstByt+seXfDbWDAcA==} + + '@tanstack/react-query@5.72.2': + resolution: {integrity: sha512-SVNHzyBUYiis+XiCl+8yiPZmMYei2AKYY94wM/zpvB5l1jxqOo82FQTziSJ4pBi96jtYqvYrTMxWynmbQh3XKw==} + peerDependencies: + react: ^18 || ^19 + + '@trpc/client@11.1.0': + resolution: {integrity: sha512-Q3pL4p7AddxI/ZJTEFo1utKSdasDFjZPECIPsKDkthEt52k530JkYVltTdLkYFKrNWXKKBo8MN7NwchelczoRw==} + peerDependencies: + '@trpc/server': 11.1.0 + typescript: '>=5.7.2' + + '@trpc/next@11.1.0': + resolution: {integrity: sha512-P8/qpfvRs7IIDdFBrcyMfxXumgf5p7K+dig6NpxpNYs4bqVJfBnAbATYEplmLhw/Dcksqo5ZoI0+0A19wLm8Ug==} + peerDependencies: + '@tanstack/react-query': ^5.59.15 + '@trpc/client': 11.1.0 + '@trpc/react-query': 11.1.0 + '@trpc/server': 11.1.0 + next: '*' + react: '>=16.8.0' + react-dom: '>=16.8.0' + typescript: '>=5.7.2' + peerDependenciesMeta: + '@tanstack/react-query': + optional: true + '@trpc/react-query': + optional: true + + '@trpc/react-query@11.1.0': + resolution: {integrity: sha512-qdqKdFM8hVy/YSBCg1/3VO+IgB6Nbul3Fk1SA3lefGf0bkYZdWVVyKab8HBAfOWlMsuRufhVLPdKYmnjzBrK9g==} + peerDependencies: + '@tanstack/react-query': ^5.67.1 + '@trpc/client': 11.1.0 + '@trpc/server': 11.1.0 + react: '>=18.2.0' + react-dom: '>=18.2.0' + typescript: '>=5.7.2' + + '@trpc/server@11.1.0': + resolution: {integrity: sha512-uAJ7ikejeujVkf53XFJ/0W8nr7bDjul+Szk5Rsepq97Hb/WS1RkRXdyX4KqAyCE9b1vDFCJVJwSxiIZdRtbTZQ==} + peerDependencies: + typescript: '>=5.7.2' + '@types/node@20.17.30': resolution: {integrity: sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==} @@ -1697,6 +1758,43 @@ snapshots: postcss: 8.5.3 tailwindcss: 4.1.3 + '@tanstack/query-core@5.72.2': {} + + '@tanstack/react-query@5.72.2(react@19.1.0)': + dependencies: + '@tanstack/query-core': 5.72.2 + react: 19.1.0 + + '@trpc/client@11.1.0(@trpc/server@11.1.0(typescript@5.8.3))(typescript@5.8.3)': + dependencies: + '@trpc/server': 11.1.0(typescript@5.8.3) + typescript: 5.8.3 + + '@trpc/next@11.1.0(@tanstack/react-query@5.72.2(react@19.1.0))(@trpc/client@11.1.0(@trpc/server@11.1.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/react-query@11.1.0(@tanstack/react-query@5.72.2(react@19.1.0))(@trpc/client@11.1.0(@trpc/server@11.1.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.1.0(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3))(@trpc/server@11.1.0(typescript@5.8.3))(next@15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)': + dependencies: + '@trpc/client': 11.1.0(@trpc/server@11.1.0(typescript@5.8.3))(typescript@5.8.3) + '@trpc/server': 11.1.0(typescript@5.8.3) + next: 15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + typescript: 5.8.3 + optionalDependencies: + '@tanstack/react-query': 5.72.2(react@19.1.0) + '@trpc/react-query': 11.1.0(@tanstack/react-query@5.72.2(react@19.1.0))(@trpc/client@11.1.0(@trpc/server@11.1.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.1.0(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) + + '@trpc/react-query@11.1.0(@tanstack/react-query@5.72.2(react@19.1.0))(@trpc/client@11.1.0(@trpc/server@11.1.0(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.1.0(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)': + dependencies: + '@tanstack/react-query': 5.72.2(react@19.1.0) + '@trpc/client': 11.1.0(@trpc/server@11.1.0(typescript@5.8.3))(typescript@5.8.3) + '@trpc/server': 11.1.0(typescript@5.8.3) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + typescript: 5.8.3 + + '@trpc/server@11.1.0(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + '@types/node@20.17.30': dependencies: undici-types: 6.19.8 diff --git a/src/app/_trpc/Client.ts b/src/app/_trpc/Client.ts new file mode 100644 index 0000000..33819b3 --- /dev/null +++ b/src/app/_trpc/Client.ts @@ -0,0 +1,3 @@ +import { createTRPCReact } from "@trpc/react-query"; +import type { TrpcRouter } from "~/server/routers/_app"; +export const trpc = createTRPCReact({}) diff --git a/src/app/_trpc/ServerClient.ts b/src/app/_trpc/ServerClient.ts new file mode 100644 index 0000000..045addf --- /dev/null +++ b/src/app/_trpc/ServerClient.ts @@ -0,0 +1,22 @@ +import { httpBatchLink } from "@trpc/client"; +import { trpcRouter } from "~/server/routers/_app"; + +function getBaseUrl() { + if (typeof window !== 'undefined') + // browser should use relative path + return ''; + if (process.env.VERCEL_URL) + // reference for vercel.com + return `https://${process.env.VERCEL_URL}`; + if (process.env.RENDER_INTERNAL_HOSTNAME) + // reference for render.com + return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`; + // assume localhost + return `http://localhost:${process.env.PORT ?? 3000}`; +}; + +export const servTrpc = trpcRouter.createCaller({ + links: [ + httpBatchLink({url: `${getBaseUrl()}/api/trpc`}), + ], +}); diff --git a/src/app/_trpc/TrpcProvider.tsx b/src/app/_trpc/TrpcProvider.tsx new file mode 100644 index 0000000..57b161e --- /dev/null +++ b/src/app/_trpc/TrpcProvider.tsx @@ -0,0 +1,38 @@ +'use client' +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { httpBatchLink } from "@trpc/client"; +import React, { useState } from "react" +import { trpc } from "./Client"; + +function getBaseUrl() { + if (typeof window !== 'undefined') + // browser should use relative path + return ''; + if (process.env.VERCEL_URL) + // reference for vercel.com + return `https://${process.env.VERCEL_URL}`; + if (process.env.RENDER_INTERNAL_HOSTNAME) + // reference for render.com + return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`; + // assume localhost + return `http://localhost:${process.env.PORT ?? 3000}`; +} + +export default function TrpcProvider({children}:{children: React.ReactNode}) { + const [queryClient] = useState(() => new QueryClient({})); + const [trpcClient] = useState(() => { + return trpc.createClient({ + links: [ + httpBatchLink({ + url: `${getBaseUrl()}/api/trpc`, + }), + ], + }) + }); + + return ( + + {children} + + ) +} diff --git a/src/app/api/trpc/[trpc]/route.ts b/src/app/api/trpc/[trpc]/route.ts new file mode 100644 index 0000000..b0ec77e --- /dev/null +++ b/src/app/api/trpc/[trpc]/route.ts @@ -0,0 +1,10 @@ +import { fetchRequestHandler } from '@trpc/server/adapters/fetch' +import { trpcRouter } from '~/server/routers/_app' +const handler = (req: Request) => fetchRequestHandler({ + endpoint: "/api/trpc", + req, + router: trpcRouter, + createContext: () => ({}), +}); + +export {handler as GET, handler as POST} diff --git a/src/app/cv/_components/CvCategory.tsx b/src/app/cv/_components/CvCategory.tsx new file mode 100644 index 0000000..45b8f72 --- /dev/null +++ b/src/app/cv/_components/CvCategory.tsx @@ -0,0 +1,26 @@ +'use client' +import { trpc } from "~/app/_trpc/Client" +import CvEntry, { type CvEntryProps } from "./CvEntry" +import type { servTrpc } from "~/app/_trpc/ServerClient" +type CvCategoryProps = { + initialData: Awaited>, + children?: React.ReactElement +} +export default function CvCategory(props:CvCategoryProps) { + const cvCategories = trpc.cvCategory.get.useQuery(undefined,{ + initialData: props.initialData, + refetchOnMount: false, + refetchOnReconnect: false, + }); + return ( +
+ {cvCategories.data.map((cat) => { + return ( +
+ {cat.name} +
+ ) + })} +
+ ) +} diff --git a/src/app/cv/_components/CvEntry.tsx b/src/app/cv/_components/CvEntry.tsx new file mode 100644 index 0000000..96a2c73 --- /dev/null +++ b/src/app/cv/_components/CvEntry.tsx @@ -0,0 +1,7 @@ +import type { cvEntry } from "~/server/db/schema" + +export type CvEntryProps = typeof cvEntry + +export default function CvEntry(cvEntry: CvEntryProps) { + return (<>) +} diff --git a/src/app/cv/_components/CvLayout.tsx b/src/app/cv/_components/CvLayout.tsx new file mode 100644 index 0000000..fe8af57 --- /dev/null +++ b/src/app/cv/_components/CvLayout.tsx @@ -0,0 +1,3 @@ +export default function CvLayout({children,}: Readonly<{ children: React.ReactNode }>) { + return (<>) +} diff --git a/src/app/cv/page.tsx b/src/app/cv/page.tsx index 4b97bf0..3068e30 100644 --- a/src/app/cv/page.tsx +++ b/src/app/cv/page.tsx @@ -1,18 +1,10 @@ -import { getCvCategories } from "~/server/db/query" - +import { servTrpc } from "~/app/_trpc/ServerClient" +import CvCategory from "./_components/CvCategory"; export default async function CvPage() { - const cvCategories = await getCvCategories(); + const cvCategories = await servTrpc.cvCategory.get(); return ( -
- {cvCategories.map((category) => { - return ( -
-
- {category.name} -
-
- ) - })} -
+ + <> + ) } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5cd5320..71fbbc5 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,7 @@ import { ClerkProvider } from "@clerk/nextjs"; import { config } from "@fortawesome/fontawesome-svg-core" import "@fortawesome/fontawesome-svg-core/styles.css" import TopNav from "./_components/TopNav"; +import TrpcProvider from "./_trpc/TrpcProvider"; config.autoAddCss = false; export const metadata: Metadata = { title: "Gregor Lohaus", @@ -24,13 +25,15 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode, modal: React.ReactNode }>) { return ( - - - - {children} - {modal} - - + + + + + {children} + {modal} + + + ); } diff --git a/src/server/db/query.ts b/src/server/db/query.ts deleted file mode 100644 index f889ad5..0000000 --- a/src/server/db/query.ts +++ /dev/null @@ -1,9 +0,0 @@ -import "server-only" -import { db } from "." - -export async function getCvCategories() { - const categories = await db.query.cvCategory.findMany({ - orderBy: (model, {desc} ) => desc(model.name) - }) - return categories; -} diff --git a/src/server/routers/_app.ts b/src/server/routers/_app.ts new file mode 100644 index 0000000..36b099a --- /dev/null +++ b/src/server/routers/_app.ts @@ -0,0 +1,10 @@ +import { router } from "../trpc"; +import { CvCategoryRouter } from "./cvCategory"; +import { publicProcedure } from "~/server/trpc"; + +export const trpcRouter = router({ + hello: publicProcedure.query(async () => "world"), + cvCategory: CvCategoryRouter +}) + +export type TrpcRouter = typeof trpcRouter diff --git a/src/server/routers/cvCategory/index.ts b/src/server/routers/cvCategory/index.ts new file mode 100644 index 0000000..d1d740b --- /dev/null +++ b/src/server/routers/cvCategory/index.ts @@ -0,0 +1,12 @@ +import { db } from "~/server/db"; +import { publicProcedure, router } from "~/server/trpc"; + +export const CvCategoryRouter = router({ + get: publicProcedure.query(async () => { + const categories = await db.query.cvCategory.findMany({ + orderBy: (model, {desc} ) => desc(model.name) + }); + return categories; + }) +}) + diff --git a/src/server/trpc.ts b/src/server/trpc.ts new file mode 100644 index 0000000..e5769c6 --- /dev/null +++ b/src/server/trpc.ts @@ -0,0 +1,5 @@ +import { initTRPC } from "@trpc/server" + +const t = initTRPC.create(); +export const router = t.router; +export const publicProcedure = t.procedure;