Project init

This commit is contained in:
Xevion
2022-12-17 15:57:05 -06:00
parent 35b2011040
commit ea0785cc97
24 changed files with 2710 additions and 0 deletions

35
src/env/client.mjs vendored Normal file
View 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
View 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
View 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
View 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);

View 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
View 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;

View 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>;

View 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;

View 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
View 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
View 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
View 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>;