Carson 11 mesi fa
parent
commit
1799a46170

+ 16 - 0
app/api/[...trpc]/route.ts

@@ -0,0 +1,16 @@
+import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
+import { appRouter } from "@/server/router";
+import { createContext } from "@/server/context";
+
+export const revalidate = 0;
+export const dynamic = "force-dynamic";
+
+const handler = (req: Request) =>
+  fetchRequestHandler({
+    endpoint: "/api",
+    req,
+    router: appRouter,
+    createContext,
+  });
+
+export { handler as GET, handler as POST };

+ 6 - 1
app/layout.tsx

@@ -1,6 +1,7 @@
 import type { Metadata } from "next";
 import { Inter } from "next/font/google";
 import "./globals.css";
+import { TrpcContextProvider } from "@/components/providers/TrpcContextProvider";
 
 const inter = Inter({ subsets: ["latin"] });
 
@@ -16,7 +17,11 @@ export default function RootLayout({
 }>) {
   return (
     <html lang="en">
-      <body className={inter.className}>{children}</body>
+      <body className={inter.className}>
+        <TrpcContextProvider>
+          {children}
+        </TrpcContextProvider>
+      </body>
     </html>
   );
 }

+ 2 - 0
app/page.tsx

@@ -1,3 +1,5 @@
+"use client";
+
 import Image from "next/image";
 
 export default function Home() {

+ 32 - 0
components/providers/TrpcContextProvider.tsx

@@ -0,0 +1,32 @@
+"use client";
+
+import { useState, type ReactNode } from "react";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
+import { trpc } from "@/utils/trpc";
+import { httpBatchLink } from "@trpc/client";
+
+export function TrpcContextProvider({ children }: { children: ReactNode }) {
+  const [queryClient] = useState(() => new QueryClient({
+    defaultOptions: {
+      queries: {
+        staleTime: 60_000, // 30 seconds
+      },
+    },
+  }))
+  const [trpcQueryClient] = useState(() => trpc.createClient({
+    links: [
+      httpBatchLink({
+        url: "/api",
+      }),
+    ],
+  }));
+  return (
+    <trpc.Provider client={trpcQueryClient} queryClient={queryClient}>
+      <QueryClientProvider client={queryClient}>
+        {children}
+        <ReactQueryDevtools />
+      </QueryClientProvider>
+    </trpc.Provider>
+  );
+}

+ 239 - 4
package-lock.json

@@ -8,14 +8,27 @@
       "name": "coco-app-platform",
       "version": "0.1.0",
       "dependencies": {
+        "@tanstack/react-query": "^5.52.0",
+        "@trpc/client": "^11.0.0-rc.482",
+        "@trpc/react-query": "^11.0.0-rc.482",
+        "@trpc/server": "^11.0.0-rc.482",
+        "jotai": "^2.9.3",
         "next": "14.2.5",
+        "ramda": "^0.30.1",
         "react": "^18",
-        "react-dom": "^18"
+        "react-dom": "^18",
+        "react-hook-form": "^7.52.2",
+        "react-icons": "^5.3.0",
+        "tailwind-merge": "^2.5.2",
+        "zod": "^3.23.8"
       },
       "devDependencies": {
+        "@tanstack/react-query-devtools": "^5.52.0",
         "@types/node": "^20",
+        "@types/ramda": "^0.30.1",
         "@types/react": "^18",
         "@types/react-dom": "^18",
+        "daisyui": "^4.12.10",
         "eslint": "^8",
         "eslint-config-next": "14.2.5",
         "postcss": "^8",
@@ -432,6 +445,91 @@
         "tslib": "^2.4.0"
       }
     },
+    "node_modules/@tanstack/query-core": {
+      "version": "5.52.0",
+      "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.52.0.tgz",
+      "integrity": "sha512-U1DOEgltjUwalN6uWYTewSnA14b+tE7lSylOiASKCAO61ENJeCq9VVD/TXHA6O5u9+6v5+UgGYBSccTKDoyMqw==",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      }
+    },
+    "node_modules/@tanstack/query-devtools": {
+      "version": "5.51.16",
+      "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.51.16.tgz",
+      "integrity": "sha512-ajwuq4WnkNCMj/Hy3KR8d3RtZ6PSKc1dD2vs2T408MdjgKzQ3klVoL6zDgVO7X+5jlb5zfgcO3thh4ojPhfIaw==",
+      "dev": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      }
+    },
+    "node_modules/@tanstack/react-query": {
+      "version": "5.52.0",
+      "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.52.0.tgz",
+      "integrity": "sha512-T8tLZdPEopSD3A1EBZ/sq7WkI76pKLKKiT82F486K8wf26EPgYCdeiSnJfuayssdQjWwLQMQVl/ROUBNmlWgCQ==",
+      "dependencies": {
+        "@tanstack/query-core": "5.52.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      },
+      "peerDependencies": {
+        "react": "^18.0.0"
+      }
+    },
+    "node_modules/@tanstack/react-query-devtools": {
+      "version": "5.52.0",
+      "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.52.0.tgz",
+      "integrity": "sha512-oq8bDxMQ95edOlcWFaGsnSFzH11s06M1uiNiLtgsm+UG6Y5w+K6BJp0GeXN8q7sBNVBw/WPyeFdUH8rj9RT2Aw==",
+      "dev": true,
+      "dependencies": {
+        "@tanstack/query-devtools": "5.51.16"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      },
+      "peerDependencies": {
+        "@tanstack/react-query": "^5.52.0",
+        "react": "^18 || ^19"
+      }
+    },
+    "node_modules/@trpc/client": {
+      "version": "11.0.0-rc.482",
+      "resolved": "https://registry.npmjs.org/@trpc/client/-/client-11.0.0-rc.482.tgz",
+      "integrity": "sha512-G08bQe8zSSK6baavNTzlsGIXUuUJyRmbC7YCwIg/eYh01nah/loxh4Cg8NDtTLzOvwmUOyQaU3XcMyhywwC4/g==",
+      "funding": [
+        "https://trpc.io/sponsor"
+      ],
+      "peerDependencies": {
+        "@trpc/server": "11.0.0-rc.482+930e652bf"
+      }
+    },
+    "node_modules/@trpc/react-query": {
+      "version": "11.0.0-rc.482",
+      "resolved": "https://registry.npmjs.org/@trpc/react-query/-/react-query-11.0.0-rc.482.tgz",
+      "integrity": "sha512-pmXStCkoxs7AJoP8oM9hnEXoYYYQbp/5kbA507R4Hd/P2tniBFe6aKFg4bXqeV+bWOi7qPNGSa5+IDH64WPRBg==",
+      "funding": [
+        "https://trpc.io/sponsor"
+      ],
+      "peerDependencies": {
+        "@tanstack/react-query": "^5.49.2",
+        "@trpc/client": "11.0.0-rc.482+930e652bf",
+        "@trpc/server": "11.0.0-rc.482+930e652bf",
+        "react": ">=18.2.0",
+        "react-dom": ">=18.2.0"
+      }
+    },
+    "node_modules/@trpc/server": {
+      "version": "11.0.0-rc.482",
+      "resolved": "https://registry.npmjs.org/@trpc/server/-/server-11.0.0-rc.482.tgz",
+      "integrity": "sha512-6Wq3T9V6B6Tv63qLV6qc2rt71AyrwBgzGniywMeddy1k2U2h6O9u7aVopzIpJJsNd7XqheYq830tU4VENysJjQ==",
+      "funding": [
+        "https://trpc.io/sponsor"
+      ]
+    },
     "node_modules/@types/json5": {
       "version": "0.0.29",
       "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -451,13 +549,22 @@
       "version": "15.7.12",
       "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
       "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
-      "dev": true
+      "devOptional": true
+    },
+    "node_modules/@types/ramda": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.1.tgz",
+      "integrity": "sha512-aoyF/ADPL6N+/NXXfhPWF+Qj6w1Cql59m9wX0Gi15uyF+bpzXeLd63HPdiTDE2bmLXfNcVufsDPKmbfOrOzTBA==",
+      "dev": true,
+      "dependencies": {
+        "types-ramda": "^0.30.1"
+      }
     },
     "node_modules/@types/react": {
       "version": "18.3.4",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.4.tgz",
       "integrity": "sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw==",
-      "dev": true,
+      "devOptional": true,
       "dependencies": {
         "@types/prop-types": "*",
         "csstype": "^3.0.2"
@@ -1115,6 +1222,16 @@
         "node": ">= 8"
       }
     },
+    "node_modules/css-selector-tokenizer": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
+      "integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
+      "dev": true,
+      "dependencies": {
+        "cssesc": "^3.0.0",
+        "fastparse": "^1.1.2"
+      }
+    },
     "node_modules/cssesc": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -1131,7 +1248,35 @@
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
-      "dev": true
+      "devOptional": true
+    },
+    "node_modules/culori": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
+      "integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==",
+      "dev": true,
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      }
+    },
+    "node_modules/daisyui": {
+      "version": "4.12.10",
+      "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.10.tgz",
+      "integrity": "sha512-jp1RAuzbHhGdXmn957Z2XsTZStXGHzFfF0FgIOZj3Wv9sH7OZgLfXTRZNfKVYxltGUOBsG1kbWAdF5SrqjebvA==",
+      "dev": true,
+      "dependencies": {
+        "css-selector-tokenizer": "^0.8",
+        "culori": "^3",
+        "picocolors": "^1",
+        "postcss-js": "^4"
+      },
+      "engines": {
+        "node": ">=16.9.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/daisyui"
+      }
     },
     "node_modules/damerau-levenshtein": {
       "version": "1.0.8",
@@ -1988,6 +2133,12 @@
       "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
       "dev": true
     },
+    "node_modules/fastparse": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
+      "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
+      "dev": true
+    },
     "node_modules/fastq": {
       "version": "1.17.1",
       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@@ -2902,6 +3053,26 @@
         "jiti": "bin/jiti.js"
       }
     },
+    "node_modules/jotai": {
+      "version": "2.9.3",
+      "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.9.3.tgz",
+      "integrity": "sha512-IqMWKoXuEzWSShjd9UhalNsRGbdju5G2FrqNLQJT+Ih6p41VNYe2sav5hnwQx4HJr25jq9wRqvGSWGviGG6Gjw==",
+      "engines": {
+        "node": ">=12.20.0"
+      },
+      "peerDependencies": {
+        "@types/react": ">=17.0.0",
+        "react": ">=17.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -3744,6 +3915,15 @@
         }
       ]
     },
+    "node_modules/ramda": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz",
+      "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/ramda"
+      }
+    },
     "node_modules/react": {
       "version": "18.3.1",
       "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
@@ -3767,6 +3947,29 @@
         "react": "^18.3.1"
       }
     },
+    "node_modules/react-hook-form": {
+      "version": "7.52.2",
+      "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.2.tgz",
+      "integrity": "sha512-pqfPEbERnxxiNMPd0bzmt1tuaPcVccywFDpyk2uV5xCIBphHV5T8SVnX9/o3kplPE1zzKt77+YIoq+EMwJp56A==",
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/react-hook-form"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17 || ^18 || ^19"
+      }
+    },
+    "node_modules/react-icons": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz",
+      "integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==",
+      "peerDependencies": {
+        "react": "*"
+      }
+    },
     "node_modules/react-is": {
       "version": "16.13.1",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -4387,6 +4590,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/tailwind-merge": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.2.tgz",
+      "integrity": "sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/dcastil"
+      }
+    },
     "node_modules/tailwindcss": {
       "version": "3.4.10",
       "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
@@ -4490,6 +4702,12 @@
       "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
       "dev": true
     },
+    "node_modules/ts-toolbelt": {
+      "version": "9.6.0",
+      "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz",
+      "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==",
+      "dev": true
+    },
     "node_modules/tsconfig-paths": {
       "version": "3.15.0",
       "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@@ -4604,6 +4822,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/types-ramda": {
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz",
+      "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==",
+      "dev": true,
+      "dependencies": {
+        "ts-toolbelt": "^9.6.0"
+      }
+    },
     "node_modules/typescript": {
       "version": "5.5.4",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
@@ -4879,6 +5106,14 @@
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
+    },
+    "node_modules/zod": {
+      "version": "3.23.8",
+      "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
+      "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+      "funding": {
+        "url": "https://github.com/sponsors/colinhacks"
+      }
     }
   }
 }

+ 17 - 4
package.json

@@ -9,18 +9,31 @@
     "lint": "next lint"
   },
   "dependencies": {
+    "@tanstack/react-query": "^5.52.0",
+    "@trpc/client": "^11.0.0-rc.482",
+    "@trpc/react-query": "^11.0.0-rc.482",
+    "@trpc/server": "^11.0.0-rc.482",
+    "jotai": "^2.9.3",
+    "next": "14.2.5",
+    "ramda": "^0.30.1",
     "react": "^18",
     "react-dom": "^18",
-    "next": "14.2.5"
+    "react-hook-form": "^7.52.2",
+    "react-icons": "^5.3.0",
+    "tailwind-merge": "^2.5.2",
+    "zod": "^3.23.8"
   },
   "devDependencies": {
-    "typescript": "^5",
+    "@tanstack/react-query-devtools": "^5.52.0",
     "@types/node": "^20",
+    "@types/ramda": "^0.30.1",
     "@types/react": "^18",
     "@types/react-dom": "^18",
+    "daisyui": "^4.12.10",
+    "eslint": "^8",
+    "eslint-config-next": "14.2.5",
     "postcss": "^8",
     "tailwindcss": "^3.4.1",
-    "eslint": "^8",
-    "eslint-config-next": "14.2.5"
+    "typescript": "^5"
   }
 }

+ 8 - 0
server/context.ts

@@ -0,0 +1,8 @@
+import { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
+export function createContext({
+  req,
+  resHeaders,
+}: FetchCreateContextFnOptions) {
+  // const user = { name: req.headers.get('username') ?? 'anonymous' };
+  return { req, resHeaders };
+}

+ 24 - 0
server/router.ts

@@ -0,0 +1,24 @@
+import { t, publicProcedure, router } from "./trpc";
+import { z } from "zod";
+
+export const appRouter = router({
+  ping: publicProcedure.query(async () => {
+    return { hello: "world" };
+  }),
+  hello: publicProcedure
+    .input(
+      z.object({
+        text: z.string(),
+      })
+    )
+    .query((opts) => {
+      return {
+        greeting: `hello ${opts.input.text}`,
+      };
+    }),
+  // ...
+});
+
+// Export type router type signature,
+// NOT the router itself.
+export type AppRouter = typeof appRouter;

+ 9 - 0
server/trpc.ts

@@ -0,0 +1,9 @@
+import { initTRPC } from '@trpc/server';
+ 
+/**
+ * Initialization of tRPC backend
+ * Should be done only once per backend!
+ */
+export const t = initTRPC.create();
+export const publicProcedure = t.procedure
+export const router = t.router

+ 2 - 8
tailwind.config.ts

@@ -7,14 +7,8 @@ const config: Config = {
     "./app/**/*.{js,ts,jsx,tsx,mdx}",
   ],
   theme: {
-    extend: {
-      backgroundImage: {
-        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
-        "gradient-conic":
-          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
-      },
-    },
+    extend: {},
   },
-  plugins: [],
+  plugins: [require("daisyui")],
 };
 export default config;

+ 5 - 0
utils/trpc.ts

@@ -0,0 +1,5 @@
+
+import { createTRPCReact } from "@trpc/react-query";
+import type { AppRouter } from "@/server/router";
+
+export const trpc = createTRPCReact<AppRouter>();