feat: initial bootstrappy stuff

This commit is contained in:
Aarnav Tale 2024-03-25 17:50:58 -04:00
parent 70c42751e3
commit 025cc4f6f1
No known key found for this signature in database
12 changed files with 6697 additions and 11211 deletions

3
.eslintrc Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "tale"
}

View File

@ -1,84 +0,0 @@
/**
* This is intended to be a basic starting point for linting in your app.
* It relies on recommended configs out of the box for simplicity, but you can
* and should modify this configuration to best suit your team's needs.
*/
/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
commonjs: true,
es6: true,
},
ignorePatterns: ["!**/.server", "!**/.client"],
// Base config
extends: ["eslint:recommended"],
overrides: [
// React
{
files: ["**/*.{js,jsx,ts,tsx}"],
plugins: ["react", "jsx-a11y"],
extends: [
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
],
settings: {
react: {
version: "detect",
},
formComponents: ["Form"],
linkComponents: [
{ name: "Link", linkAttribute: "to" },
{ name: "NavLink", linkAttribute: "to" },
],
"import/resolver": {
typescript: {},
},
},
},
// Typescript
{
files: ["**/*.{ts,tsx}"],
plugins: ["@typescript-eslint", "import"],
parser: "@typescript-eslint/parser",
settings: {
"import/internal-regex": "^~/",
"import/resolver": {
node: {
extensions: [".ts", ".tsx"],
},
typescript: {
alwaysTryTypes: true,
},
},
},
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
],
},
// Node
{
files: [".eslintrc.cjs"],
env: {
node: true,
},
},
],
};

View File

@ -4,15 +4,15 @@
* For more information, see https://remix.run/file-conventions/entry.client * For more information, see https://remix.run/file-conventions/entry.client
*/ */
import { RemixBrowser } from "@remix-run/react"; import { RemixBrowser } from '@remix-run/react'
import { startTransition, StrictMode } from "react"; import { startTransition, StrictMode } from 'react'
import { hydrateRoot } from "react-dom/client"; import { hydrateRoot } from 'react-dom/client'
startTransition(() => { startTransition(() => {
hydrateRoot( hydrateRoot(
document, document,
<StrictMode> <StrictMode>
<RemixBrowser /> <RemixBrowser/>
</StrictMode> </StrictMode>
); )
}); })

View File

@ -4,137 +4,134 @@
* For more information, see https://remix.run/file-conventions/entry.server * For more information, see https://remix.run/file-conventions/entry.server
*/ */
import { PassThrough } from "node:stream"; import { PassThrough } from 'node:stream'
import type { AppLoadContext, EntryContext } from "@remix-run/node"; import type { EntryContext } from '@remix-run/node'
import { createReadableStreamFromReadable } from "@remix-run/node"; import { createReadableStreamFromReadable } from '@remix-run/node'
import { RemixServer } from "@remix-run/react"; import { RemixServer } from '@remix-run/react'
import { isbot } from "isbot"; import { isbot } from 'isbot'
import { renderToPipeableStream } from "react-dom/server"; import { renderToPipeableStream } from 'react-dom/server'
const ABORT_DELAY = 5_000; // eslint-disable-next-line @typescript-eslint/naming-convention
const ABORT_DELAY = 5000
export default function handleRequest( export default async function handleRequest(
request: Request, request: Request,
responseStatusCode: number, responseStatusCode: number,
responseHeaders: Headers, responseHeaders: Headers,
remixContext: EntryContext, remixContext: EntryContext
// This is ignored so we can keep it in the template for visibility. Feel
// free to delete this parameter in your app if you're not using it!
// eslint-disable-next-line @typescript-eslint/no-unused-vars
loadContext: AppLoadContext
) { ) {
return isbot(request.headers.get("user-agent") || "") return isbot(request.headers.get('user-agent') ?? '')
? handleBotRequest( ? handleBotRequest(
request, request,
responseStatusCode, responseStatusCode,
responseHeaders, responseHeaders,
remixContext remixContext
) )
: handleBrowserRequest( : handleBrowserRequest(
request, request,
responseStatusCode, responseStatusCode,
responseHeaders, responseHeaders,
remixContext remixContext
); )
} }
function handleBotRequest( async function handleBotRequest(
request: Request, request: Request,
responseStatusCode: number, responseStatusCode: number,
responseHeaders: Headers, responseHeaders: Headers,
remixContext: EntryContext remixContext: EntryContext
) { ) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let shellRendered = false; let shellRendered = false
const { pipe, abort } = renderToPipeableStream( const { pipe, abort } = renderToPipeableStream(
<RemixServer <RemixServer
context={remixContext} context={remixContext}
url={request.url} url={request.url}
abortDelay={ABORT_DELAY} abortDelay={ABORT_DELAY}
/>, />,
{ {
onAllReady() { onAllReady() {
shellRendered = true; shellRendered = true
const body = new PassThrough(); const body = new PassThrough()
const stream = createReadableStreamFromReadable(body); const stream = createReadableStreamFromReadable(body)
responseHeaders.set("Content-Type", "text/html"); responseHeaders.set('Content-Type', 'text/html')
resolve( resolve(
new Response(stream, { new Response(stream, {
headers: responseHeaders, headers: responseHeaders,
status: responseStatusCode, status: responseStatusCode
}) })
); )
pipe(body); pipe(body)
}, },
onShellError(error: unknown) { onShellError(error: unknown) {
reject(error); reject(error)
}, },
onError(error: unknown) { onError(error: unknown) {
responseStatusCode = 500; responseStatusCode = 500
// Log streaming rendering errors from inside the shell. Don't log // Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll // errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest. // reject and get logged in handleDocumentRequest.
if (shellRendered) { if (shellRendered) {
console.error(error); console.error(error)
} }
}, }
} }
); )
setTimeout(abort, ABORT_DELAY); setTimeout(abort, ABORT_DELAY)
}); })
} }
function handleBrowserRequest( async function handleBrowserRequest(
request: Request, request: Request,
responseStatusCode: number, responseStatusCode: number,
responseHeaders: Headers, responseHeaders: Headers,
remixContext: EntryContext remixContext: EntryContext
) { ) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let shellRendered = false; let shellRendered = false
const { pipe, abort } = renderToPipeableStream( const { pipe, abort } = renderToPipeableStream(
<RemixServer <RemixServer
context={remixContext} context={remixContext}
url={request.url} url={request.url}
abortDelay={ABORT_DELAY} abortDelay={ABORT_DELAY}
/>, />,
{ {
onShellReady() { onShellReady() {
shellRendered = true; shellRendered = true
const body = new PassThrough(); const body = new PassThrough()
const stream = createReadableStreamFromReadable(body); const stream = createReadableStreamFromReadable(body)
responseHeaders.set("Content-Type", "text/html"); responseHeaders.set('Content-Type', 'text/html')
resolve( resolve(
new Response(stream, { new Response(stream, {
headers: responseHeaders, headers: responseHeaders,
status: responseStatusCode, status: responseStatusCode
}) })
); )
pipe(body); pipe(body)
}, },
onShellError(error: unknown) { onShellError(error: unknown) {
reject(error); reject(error)
}, },
onError(error: unknown) { onError(error: unknown) {
responseStatusCode = 500; responseStatusCode = 500
// Log streaming rendering errors from inside the shell. Don't log // Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll // errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest. // reject and get logged in handleDocumentRequest.
if (shellRendered) { if (shellRendered) {
console.error(error); console.error(error)
} }
}, }
} }
); )
setTimeout(abort, ABORT_DELAY); setTimeout(abort, ABORT_DELAY)
}); })
} }

3
app/tailwind.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

10928
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +1,40 @@
{ {
"name": "headplane-remix", "name": "headplane",
"private": true, "private": true,
"sideEffects": false, "sideEffects": false,
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "remix vite:build", "build": "remix vite:build",
"dev": "remix vite:dev", "dev": "remix vite:dev",
"lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
"start": "remix-serve ./build/server/index.js", "start": "remix-serve ./build/server/index.js",
"typecheck": "tsc" "typecheck": "tsc"
}, },
"dependencies": { "dependencies": {
"@remix-run/node": "^2.8.1", "@heroicons/react": "^2.1.3",
"@remix-run/react": "^2.8.1", "@remix-run/node": "^2.8.1",
"@remix-run/serve": "^2.8.1", "@remix-run/react": "^2.8.1",
"isbot": "^4.1.0", "@remix-run/serve": "^2.8.1",
"react": "^18.2.0", "clsx": "^2.1.0",
"react-dom": "^18.2.0" "isbot": "^4.1.0",
}, "oauth4webapi": "^2.10.3",
"devDependencies": { "react": "^18.2.0",
"@remix-run/dev": "^2.8.1", "react-dom": "^18.2.0"
"@types/react": "^18.2.20", },
"@types/react-dom": "^18.2.7", "devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.7.4", "@remix-run/dev": "^2.8.1",
"@typescript-eslint/parser": "^6.7.4", "@types/react": "^18.2.20",
"eslint": "^8.38.0", "@types/react-dom": "^18.2.7",
"eslint-import-resolver-typescript": "^3.6.1", "autoprefixer": "^10.4.19",
"eslint-plugin-import": "^2.28.1", "eslint": "^8.57.0",
"eslint-plugin-jsx-a11y": "^6.7.1", "eslint-config-tale": "^1.0.16",
"eslint-plugin-react": "^7.33.2", "postcss": "^8.4.38",
"eslint-plugin-react-hooks": "^4.6.0", "tailwindcss": "^3.4.1",
"typescript": "^5.1.6", "typescript": "^5.1.6",
"vite": "^5.1.0", "vite": "^5.1.0",
"vite-tsconfig-paths": "^4.2.1" "vite-tsconfig-paths": "^4.2.1"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
} }
} }

6477
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

10
tailwind.config.ts Normal file
View File

@ -0,0 +1,10 @@
import type { Config } from 'tailwindcss'
export default {
content: ['./app/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {}
},
plugins: []
} satisfies Config

View File

@ -1,32 +1,32 @@
{ {
"include": [ "include": [
"**/*.ts", "**/*.ts",
"**/*.tsx", "**/*.tsx",
"**/.server/**/*.ts", "**/.server/**/*.ts",
"**/.server/**/*.tsx", "**/.server/**/*.tsx",
"**/.client/**/*.ts", "**/.client/**/*.ts",
"**/.client/**/*.tsx" "**/.client/**/*.tsx"
], ],
"compilerOptions": { "compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"], "lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["@remix-run/node", "vite/client"], "types": ["@remix-run/node", "vite/client"],
"isolatedModules": true, "isolatedModules": true,
"esModuleInterop": true, "esModuleInterop": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"target": "ES2022", "target": "ES2022",
"strict": true, "strict": true,
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"~/*": ["./app/*"] "~/*": ["./app/*"]
}, },
// Vite takes care of building everything, not tsc. // Vite takes care of building everything, not tsc.
"noEmit": true "noEmit": true
} }
} }

View File

@ -1,10 +1,12 @@
import { vitePlugin as remix } from "@remix-run/dev"; import { vitePlugin as remix } from '@remix-run/dev'
import { installGlobals } from "@remix-run/node"; import { installGlobals } from '@remix-run/node'
import { defineConfig } from "vite"; import { defineConfig } from 'vite'
import tsconfigPaths from "vite-tsconfig-paths"; import tsconfigPaths from 'vite-tsconfig-paths'
installGlobals(); installGlobals()
export default defineConfig({ export default defineConfig({
plugins: [remix(), tsconfigPaths()], plugins: [remix({
}); basename: '/admin'
}), tsconfigPaths()]
})