mirror of
https://github.com/Xevion/100prisoners.git
synced 2025-12-16 02:11:04 -06:00
Project init
This commit is contained in:
35
src/env/client.mjs
vendored
Normal file
35
src/env/client.mjs
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// @ts-check
|
||||
import { clientEnv, clientSchema } from "./schema.mjs";
|
||||
|
||||
const _clientEnv = clientSchema.safeParse(clientEnv);
|
||||
|
||||
export const formatErrors = (
|
||||
/** @type {import('zod').ZodFormattedError<Map<string,string>,string>} */
|
||||
errors,
|
||||
) =>
|
||||
Object.entries(errors)
|
||||
.map(([name, value]) => {
|
||||
if (value && "_errors" in value)
|
||||
return `${name}: ${value._errors.join(", ")}\n`;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (!_clientEnv.success) {
|
||||
console.error(
|
||||
"❌ Invalid environment variables:\n",
|
||||
...formatErrors(_clientEnv.error.format()),
|
||||
);
|
||||
throw new Error("Invalid environment variables");
|
||||
}
|
||||
|
||||
for (let key of Object.keys(_clientEnv.data)) {
|
||||
if (!key.startsWith("NEXT_PUBLIC_")) {
|
||||
console.warn(
|
||||
`❌ Invalid public environment variable name: ${key}. It must begin with 'NEXT_PUBLIC_'`,
|
||||
);
|
||||
|
||||
throw new Error("Invalid public environment variable name");
|
||||
}
|
||||
}
|
||||
|
||||
export const env = _clientEnv.data;
|
||||
29
src/env/schema.mjs
vendored
Normal file
29
src/env/schema.mjs
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// @ts-check
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Specify your server-side environment variables schema here.
|
||||
* This way you can ensure the app isn't built with invalid env vars.
|
||||
*/
|
||||
export const serverSchema = z.object({
|
||||
NODE_ENV: z.enum(["development", "test", "production"]),
|
||||
});
|
||||
|
||||
/**
|
||||
* Specify your client-side environment variables schema here.
|
||||
* This way you can ensure the app isn't built with invalid env vars.
|
||||
* To expose them to the client, prefix them with `NEXT_PUBLIC_`.
|
||||
*/
|
||||
export const clientSchema = z.object({
|
||||
// NEXT_PUBLIC_CLIENTVAR: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* You can't destruct `process.env` as a regular object, so you have to do
|
||||
* it manually here. This is because Next.js evaluates this at build time,
|
||||
* and only used environment variables are included in the build.
|
||||
* @type {{ [k in keyof z.infer<typeof clientSchema>]: z.infer<typeof clientSchema>[k] | undefined }}
|
||||
*/
|
||||
export const clientEnv = {
|
||||
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
|
||||
};
|
||||
27
src/env/server.mjs
vendored
Normal file
27
src/env/server.mjs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// @ts-check
|
||||
/**
|
||||
* This file is included in `/next.config.mjs` which ensures the app isn't built with invalid env vars.
|
||||
* It has to be a `.mjs`-file to be imported there.
|
||||
*/
|
||||
import { serverSchema } from "./schema.mjs";
|
||||
import { env as clientEnv, formatErrors } from "./client.mjs";
|
||||
|
||||
const _serverEnv = serverSchema.safeParse(process.env);
|
||||
|
||||
if (!_serverEnv.success) {
|
||||
console.error(
|
||||
"❌ Invalid environment variables:\n",
|
||||
...formatErrors(_serverEnv.error.format()),
|
||||
);
|
||||
throw new Error("Invalid environment variables");
|
||||
}
|
||||
|
||||
for (let key of Object.keys(_serverEnv.data)) {
|
||||
if (key.startsWith("NEXT_PUBLIC_")) {
|
||||
console.warn("❌ You are exposing a server-side env-variable:", key);
|
||||
|
||||
throw new Error("You are exposing a server-side env-variable");
|
||||
}
|
||||
}
|
||||
|
||||
export const env = { ..._serverEnv.data, ...clientEnv };
|
||||
11
src/pages/_app.tsx
Normal file
11
src/pages/_app.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { type AppType } from "next/app";
|
||||
|
||||
import { trpc } from "../utils/trpc";
|
||||
|
||||
import "../styles/globals.scss";
|
||||
|
||||
const MyApp: AppType = ({ Component, pageProps }) => {
|
||||
return <Component {...pageProps} />;
|
||||
};
|
||||
|
||||
export default trpc.withTRPC(MyApp);
|
||||
17
src/pages/api/trpc/[trpc].ts
Normal file
17
src/pages/api/trpc/[trpc].ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createNextApiHandler } from "@trpc/server/adapters/next";
|
||||
|
||||
import { env } from "../../../env/server.mjs";
|
||||
import { createContext } from "../../../server/trpc/context";
|
||||
import { appRouter } from "../../../server/trpc/router/_app";
|
||||
|
||||
// export API handler
|
||||
export default createNextApiHandler({
|
||||
router: appRouter,
|
||||
createContext,
|
||||
onError:
|
||||
env.NODE_ENV === "development"
|
||||
? ({ path, error }) => {
|
||||
console.error(`❌ tRPC failed on ${path}: ${error}`);
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
53
src/pages/index.tsx
Normal file
53
src/pages/index.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import {type NextPage} from "next";
|
||||
import Head from "next/head";
|
||||
import BoxGraphic from "../components/BoxGraphic";
|
||||
import Chance from "chance";
|
||||
import {range} from "../utils/helpers";
|
||||
|
||||
const chance = new Chance();
|
||||
|
||||
const Home: NextPage = () => {
|
||||
const sources = range(0, 100);
|
||||
const destinations = chance.shuffle(sources);
|
||||
const boxes = sources.map((e, i) => [e, destinations[i]])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>100prisoners.com</title>
|
||||
<link rel="icon" href="/favicon.ico"/>
|
||||
</Head>
|
||||
<main className="flex min-h-screen flex-col items-center bg-white">
|
||||
<div className="mt-8 px-3 w-[50rem] space-y-3">
|
||||
<h1 className="text-4xl mb-2">100Prisoners.com</h1>
|
||||
<div>
|
||||
This website is dedicated to exploring the intriguing 100 prisoners problem, a mathematical
|
||||
challenge that seems astronomically impossible at first, yet can leverage mathematics to raise
|
||||
the chances one hundred octillion.
|
||||
<br/>
|
||||
<br/>
|
||||
This thought experiment presents a scenario in which a group of 100 prisoners are tasked with
|
||||
finding their own numbered slip among a collection of 100 boxes, each containing a random
|
||||
permutation of the numbers 1 through 100. The prisoners are allowed to open 50 boxes each in an
|
||||
attempt to find their own number, and all of the prisoners must be successful in order to be set
|
||||
free. This problem raises questions about strategy and probability in search of a solution.
|
||||
</div>
|
||||
<div className="grid grid-cols-10 w-full space-y-2">
|
||||
{boxes.map(([source, destination]) =>
|
||||
<div className="col-span-1 px-2">
|
||||
<div key={source} className="box aspect-square relative">
|
||||
<span className="absolute left-6 top-8 cursor-pointer">{destination}</span>
|
||||
<BoxGraphic className="transition-all cursor-pointer relative z-30">
|
||||
{source + 1}
|
||||
</BoxGraphic>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
26
src/server/trpc/context.ts
Normal file
26
src/server/trpc/context.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { type inferAsyncReturnType } from "@trpc/server";
|
||||
import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
|
||||
|
||||
/**
|
||||
* Replace this with an object if you want to pass things to createContextInner
|
||||
*/
|
||||
type CreateContextOptions = Record<string, never>;
|
||||
|
||||
/** Use this helper for:
|
||||
* - testing, so we dont have to mock Next.js' req/res
|
||||
* - trpc's `createSSGHelpers` where we don't have req/res
|
||||
* @see https://create.t3.gg/en/usage/trpc#-servertrpccontextts
|
||||
**/
|
||||
export const createContextInner = async (opts: CreateContextOptions) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the actual context you'll use in your router
|
||||
* @link https://trpc.io/docs/context
|
||||
**/
|
||||
export const createContext = async (opts: CreateNextContextOptions) => {
|
||||
return await createContextInner({});
|
||||
};
|
||||
|
||||
export type Context = inferAsyncReturnType<typeof createContext>;
|
||||
9
src/server/trpc/router/_app.ts
Normal file
9
src/server/trpc/router/_app.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { router } from "../trpc";
|
||||
import { exampleRouter } from "./example";
|
||||
|
||||
export const appRouter = router({
|
||||
example: exampleRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
export type AppRouter = typeof appRouter;
|
||||
13
src/server/trpc/router/example.ts
Normal file
13
src/server/trpc/router/example.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { router, publicProcedure } from "../trpc";
|
||||
|
||||
export const exampleRouter = router({
|
||||
hello: publicProcedure
|
||||
.input(z.object({ text: z.string().nullish() }).nullish())
|
||||
.query(({ input }) => {
|
||||
return {
|
||||
greeting: `Hello ${input?.text ?? "world"}`,
|
||||
};
|
||||
}),
|
||||
});
|
||||
15
src/server/trpc/trpc.ts
Normal file
15
src/server/trpc/trpc.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { initTRPC } from "@trpc/server";
|
||||
import superjson from "superjson";
|
||||
|
||||
import { type Context } from "./context";
|
||||
|
||||
const t = initTRPC.context<Context>().create({
|
||||
transformer: superjson,
|
||||
errorFormatter({ shape }) {
|
||||
return shape;
|
||||
},
|
||||
});
|
||||
|
||||
export const router = t.router;
|
||||
|
||||
export const publicProcedure = t.procedure;
|
||||
2
src/utils/helpers.ts
Normal file
2
src/utils/helpers.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const range = (start: number, stop: number, step = 1) =>
|
||||
Array.from({length: (stop - start) / step + 1}, (_, i) => start + i * step);
|
||||
42
src/utils/trpc.ts
Normal file
42
src/utils/trpc.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { httpBatchLink, loggerLink } from "@trpc/client";
|
||||
import { createTRPCNext } from "@trpc/next";
|
||||
import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
|
||||
import superjson from "superjson";
|
||||
|
||||
import { type AppRouter } from "../server/trpc/router/_app";
|
||||
|
||||
const getBaseUrl = () => {
|
||||
if (typeof window !== "undefined") return ""; // browser should use relative url
|
||||
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
|
||||
return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
|
||||
};
|
||||
|
||||
export const trpc = createTRPCNext<AppRouter>({
|
||||
config() {
|
||||
return {
|
||||
transformer: superjson,
|
||||
links: [
|
||||
loggerLink({
|
||||
enabled: (opts) =>
|
||||
process.env.NODE_ENV === "development" ||
|
||||
(opts.direction === "down" && opts.result instanceof Error),
|
||||
}),
|
||||
httpBatchLink({
|
||||
url: `${getBaseUrl()}/api/trpc`,
|
||||
}),
|
||||
],
|
||||
};
|
||||
},
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* Inference helper for inputs
|
||||
* @example type HelloInput = RouterInputs['example']['hello']
|
||||
**/
|
||||
export type RouterInputs = inferRouterInputs<AppRouter>;
|
||||
/**
|
||||
* Inference helper for outputs
|
||||
* @example type HelloOutput = RouterOutputs['example']['hello']
|
||||
**/
|
||||
export type RouterOutputs = inferRouterOutputs<AppRouter>;
|
||||
Reference in New Issue
Block a user