diff --git a/.bumrc b/.bumrc new file mode 100644 index 0000000..cb174d5 --- /dev/null +++ b/.bumrc @@ -0,0 +1 @@ +1.2.1 \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..dbce4f4 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..c27d889 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +lint-staged diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..f817d8f --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,8 @@ +{ + "default": true, + "MD013": false, + "MD026": false, + "MD033": false, + "MD040": false, + "MD041": false +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..56c0f81 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +node_modules +.git +dist +build +.react-router +pnpm-lock.yaml \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..f7dc52e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "tabWidth": 2, + "useTabs": false, + "semi": false, + "singleQuote": true, + "singleAttributePerLine": true, + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..e84222c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + "recommendations": [ + "streetsidesoftware.code-spell-checker", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "wayou.vscode-todo-highlight", + "vivaxy.vscode-conventional-commits", + "bradlc.vscode-tailwindcss", + "usernamehw.errorlens" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0db6929 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,31 @@ +{ + "todohighlight.keywords": [ + { + "text": "WARNING:", + "backgroundColor": "#e74c3c", + "color": "#fff" + } + ], + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true, + "css.lint.unknownAtRules": "ignore", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "editor.tabSize": 2, + "editor.renderWhitespace": "all", + "tailwindCSS.classAttributes": [ + "class", + "className", + "ngClass", + "class:list", + "containerClassName", + "inputClassName", + "labelClassName", + "buttonClassName", + "leftNodeClassName", + "rightNodeClassName" + ], + "cSpell.words": [] +} diff --git a/app/entry.client.tsx b/app/entry.client.tsx new file mode 100644 index 0000000..7e8a937 --- /dev/null +++ b/app/entry.client.tsx @@ -0,0 +1,12 @@ +import { startTransition, StrictMode } from 'react' +import { hydrateRoot } from 'react-dom/client' +import { HydratedRouter } from 'react-router/dom' + +startTransition(() => { + hydrateRoot( + document, + + + , + ) +}) diff --git a/app/entry.server.tsx b/app/entry.server.tsx new file mode 100644 index 0000000..84e7f0f --- /dev/null +++ b/app/entry.server.tsx @@ -0,0 +1,71 @@ +import { PassThrough } from 'node:stream' + +import { createReadableStreamFromReadable } from '@react-router/node' +import { isbot } from 'isbot' +import type { RenderToPipeableStreamOptions } from 'react-dom/server' +import { renderToPipeableStream } from 'react-dom/server' +import { ServerRouter } from 'react-router' +import type { AppLoadContext, EntryContext } from 'react-router' + +export const streamTimeout = 5000 + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + routerContext: EntryContext, + _loadContext: AppLoadContext, +) { + return new Promise((resolve, reject) => { + let shellRendered = false + const userAgent = request.headers.get('user-agent') + + // Ensure requests from bots and SPA Mode renders wait for all content to load before responding + // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation + const readyOption: keyof RenderToPipeableStreamOptions = + (userAgent && isbot(userAgent)) || routerContext.isSpaMode + ? 'onAllReady' + : 'onShellReady' + + const { pipe, abort } = renderToPipeableStream( + , + { + [readyOption]() { + shellRendered = true + const body = new PassThrough() + const stream = createReadableStreamFromReadable(body) + + responseHeaders.set('Content-Type', 'text/html') + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ) + + pipe(body) + }, + onShellError(error: unknown) { + reject(error) + }, + onError(error: unknown) { + responseStatusCode = 500 + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error) // eslint-disable-line no-console + } + }, + }, + ) + + // Abort the rendering stream after the `streamTimeout` so it has time to + // flush down the rejected boundaries + setTimeout(abort, streamTimeout + 1000) + }) +} diff --git a/app/welcome/welcome.tsx b/app/layouts/home/welcome/index.tsx similarity index 87% rename from app/welcome/welcome.tsx rename to app/layouts/home/welcome/index.tsx index 8ac6e1d..235f77b 100644 --- a/app/welcome/welcome.tsx +++ b/app/layouts/home/welcome/index.tsx @@ -1,10 +1,10 @@ -import logoDark from "./logo-dark.svg"; -import logoLight from "./logo-light.svg"; +import logoDark from './logo-dark.svg' +import logoLight from './logo-light.svg' export function Welcome() { return ( - + - - - + + + What's next? @@ -43,13 +43,13 @@ export function Welcome() { - ); + ) } const resources = [ { - href: "https://reactrouter.com/docs", - text: "React Router Docs", + href: 'https://reactrouter.com/docs', + text: 'React Router Docs', icon: ( ), }, -]; +] diff --git a/app/welcome/logo-dark.svg b/app/layouts/home/welcome/logo-dark.svg similarity index 100% rename from app/welcome/logo-dark.svg rename to app/layouts/home/welcome/logo-dark.svg diff --git a/app/welcome/logo-light.svg b/app/layouts/home/welcome/logo-light.svg similarity index 100% rename from app/welcome/logo-light.svg rename to app/layouts/home/welcome/logo-light.svg diff --git a/app/root.tsx b/app/root.tsx index 9fc6636..ea63567 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -5,30 +5,33 @@ import { Outlet, Scripts, ScrollRestoration, -} from "react-router"; +} from 'react-router' -import type { Route } from "./+types/root"; -import "./app.css"; +import type { Route } from './+types/root' +import './app.css' export const links: Route.LinksFunction = () => [ - { rel: "preconnect", href: "https://fonts.googleapis.com" }, + { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, { - rel: "preconnect", - href: "https://fonts.gstatic.com", - crossOrigin: "anonymous", + rel: 'preconnect', + href: 'https://fonts.gstatic.com', + crossOrigin: 'anonymous', }, { - rel: "stylesheet", - href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", + rel: 'stylesheet', + href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', }, -]; +] export function Layout({ children }: { children: React.ReactNode }) { return ( - + @@ -38,38 +41,38 @@ export function Layout({ children }: { children: React.ReactNode }) {
+
What's next?