├── .dockerignore ├── .env.example ├── .eslintrc.js ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── app ├── entry.client.tsx ├── entry.server.tsx ├── root.tsx ├── routes │ ├── about.tsx │ ├── forgot-password.tsx │ ├── healthcheck.tsx │ ├── images_+ │ │ └── $fileId.tsx │ ├── index.tsx │ ├── login.tsx │ ├── logout.tsx │ ├── me.tsx │ ├── onboarding.tsx │ ├── privacy.tsx │ ├── reset-password.tsx │ ├── settings.profile.tsx │ ├── signup.tsx │ ├── support.tsx │ ├── tos.tsx │ └── users_+ │ │ └── $username.tsx └── utils │ ├── auth.server.ts │ ├── db.server.ts │ ├── email.server.ts │ ├── encryption.server.ts │ ├── env.server.ts │ ├── fly.server.test.ts │ ├── fly.server.ts │ ├── forms.tsx │ ├── misc.server.ts │ ├── misc.ts │ ├── session.server.ts │ └── user-validation.ts ├── fly.toml ├── index.js ├── mocks ├── README.md ├── fixtures │ └── README.md ├── index.ts └── utils.ts ├── other ├── build-server.ts ├── litefs.yml └── start.js ├── package-lock.json ├── package.json ├── playwright.config.ts ├── prisma ├── migrations │ ├── 20230123230704_init │ │ └── migration.sql │ └── migration_lock.toml ├── schema.prisma ├── seed-utils.ts └── seed.ts ├── public ├── favicon.ico └── img │ ├── brands │ └── starstruck.png │ ├── ships │ └── the-starstruck.jpg │ └── starports │ └── slc.jpg ├── remix.config.js ├── remix.env.d.ts ├── remix.init ├── index.js ├── init.ts ├── package-lock.json └── package.json ├── server └── index.ts ├── tailwind.config.js ├── test ├── global-setup.ts ├── setup-env-vars.ts ├── setup-test-env.ts └── utils.ts ├── tests ├── .eslintrc ├── onboarding.test.ts └── test.ts ├── tsconfig.json └── vitest.config.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | *.log 3 | .DS_Store 4 | .env 5 | /.cache 6 | /public/build 7 | /build 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_PATH="./prisma/data.db" 2 | DATABASE_URL="file:./data.db?connection_limit=1" 3 | IMAGE_DATABASE_PATH="./other/image.db" 4 | SESSION_SECRET="super-duper-s3cret" 5 | ENCRYPTION_SECRET="very-very-secret" 6 | MAILGUN_DOMAIN="mg.example.com" 7 | MAILGUN_SENDING_KEY="some-api-token-with-dashes" 8 | FLY_LITEFS_DIR="/litefs/data" 9 | 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@types/eslint').Linter.BaseConfig} */ 2 | module.exports = { 3 | extends: [ 4 | '@remix-run/eslint-config', 5 | '@remix-run/eslint-config/node', 6 | '@remix-run/eslint-config/jest-testing-library', 7 | 'prettier', 8 | ], 9 | rules: { 10 | '@typescript-eslint/consistent-type-imports': [ 11 | 'warn', 12 | { 13 | prefer: 'type-imports', 14 | disallowTypeAnnotations: true, 15 | fixStyle: 'inline-type-imports', 16 | }, 17 | ], 18 | }, 19 | // we're using vitest which has a very similar API to jest 20 | // (so the linting plugins work nicely), but it means we have to explicitly 21 | // set the jest version. 22 | settings: { 23 | jest: { 24 | version: 28, 25 | }, 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - dev 7 | pull_request: {} 8 | 9 | permissions: 10 | actions: write 11 | contents: read 12 | 13 | jobs: 14 | lint: 15 | name: ⬣ ESLint 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: 🛑 Cancel Previous Runs 19 | uses: styfle/cancel-workflow-action@0.10.0 20 | 21 | - name: ⬇️ Checkout repo 22 | uses: actions/checkout@v3 23 | 24 | - name: ⎔ Setup node 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: 18 28 | 29 | - name: 📥 Download deps 30 | uses: bahmutov/npm-install@v1 31 | with: 32 | useLockFile: false 33 | 34 | - name: 🔬 Lint 35 | run: npm run lint 36 | 37 | typecheck: 38 | name: ʦ TypeScript 39 | runs-on: ubuntu-latest 40 | steps: 41 | - name: 🛑 Cancel Previous Runs 42 | uses: styfle/cancel-workflow-action@0.10.0 43 | 44 | - name: ⬇️ Checkout repo 45 | uses: actions/checkout@v3 46 | 47 | - name: ⎔ Setup node 48 | uses: actions/setup-node@v3 49 | with: 50 | node-version: 18 51 | 52 | - name: 📥 Download deps 53 | uses: bahmutov/npm-install@v1 54 | with: 55 | useLockFile: false 56 | 57 | - name: 🔎 Type check 58 | run: npm run typecheck --if-present 59 | 60 | vitest: 61 | name: ⚡ Vitest 62 | runs-on: ubuntu-latest 63 | steps: 64 | - name: 🛑 Cancel Previous Runs 65 | uses: styfle/cancel-workflow-action@0.10.0 66 | 67 | - name: ⬇️ Checkout repo 68 | uses: actions/checkout@v3 69 | 70 | - name: ⎔ Setup node 71 | uses: actions/setup-node@v3 72 | with: 73 | node-version: 18 74 | 75 | - name: 📥 Download deps 76 | uses: bahmutov/npm-install@v1 77 | with: 78 | useLockFile: false 79 | 80 | - name: 🏄 Copy test env vars 81 | run: cp .env.example .env 82 | 83 | - name: ⚡ Run vitest 84 | run: npm run test -- --coverage 85 | 86 | playwright: 87 | name: 🎭 Playwright 88 | runs-on: ubuntu-latest 89 | timeout-minutes: 60 90 | steps: 91 | - name: 🛑 Cancel Previous Runs 92 | uses: styfle/cancel-workflow-action@0.10.0 93 | 94 | - name: ⬇️ Checkout repo 95 | uses: actions/checkout@v3 96 | 97 | - name: 🏄 Copy test env vars 98 | run: cp .env.example .env 99 | 100 | - name: ⎔ Setup node 101 | uses: actions/setup-node@v3 102 | with: 103 | node-version: 18 104 | 105 | - name: 📥 Download deps 106 | uses: bahmutov/npm-install@v1 107 | with: 108 | useLockFile: false 109 | 110 | - name: 📥 Install Playwright Browsers 111 | run: npx playwright install --with-deps 112 | 113 | - name: 🛠 Setup Database 114 | run: npx prisma migrate reset --force 115 | 116 | - name: 🏗 Build 117 | run: npm run build 118 | 119 | - name: 🎭 Playwright tests 120 | run: npx playwright test 121 | 122 | - name: 📊 Upload report 123 | uses: actions/upload-artifact@v2 124 | if: always() 125 | with: 126 | name: playwright-report 127 | path: playwright-report/ 128 | retention-days: 30 129 | 130 | build: 131 | name: 🐳 Build 132 | # only build/deploy main branch on pushes 133 | if: 134 | ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && 135 | github.event_name == 'push' }} 136 | runs-on: ubuntu-latest 137 | steps: 138 | - name: 🛑 Cancel Previous Runs 139 | uses: styfle/cancel-workflow-action@0.10.0 140 | 141 | - name: ⬇️ Checkout repo 142 | uses: actions/checkout@v3 143 | 144 | - name: 👀 Read app name 145 | uses: SebRollen/toml-action@v1.0.0 146 | id: app_name 147 | with: 148 | file: 'fly.toml' 149 | field: 'app' 150 | 151 | - name: 🐳 Set up Docker Buildx 152 | uses: docker/setup-buildx-action@v2 153 | 154 | # Setup cache 155 | - name: ⚡️ Cache Docker layers 156 | uses: actions/cache@v3 157 | with: 158 | path: /tmp/.buildx-cache 159 | key: ${{ runner.os }}-buildx-${{ github.sha }} 160 | restore-keys: | 161 | ${{ runner.os }}-buildx- 162 | 163 | - name: 🔑 Fly Registry Auth 164 | uses: docker/login-action@v2 165 | with: 166 | registry: registry.fly.io 167 | username: x 168 | password: ${{ secrets.FLY_API_TOKEN }} 169 | 170 | - name: 🐳 Docker build 171 | uses: docker/build-push-action@v3 172 | with: 173 | context: . 174 | push: true 175 | tags: 176 | registry.fly.io/${{ steps.app_name.outputs.value }}:${{ 177 | github.ref_name }}-${{ github.sha }} 178 | build-args: | 179 | COMMIT_SHA=${{ github.sha }} 180 | cache-from: type=local,src=/tmp/.buildx-cache 181 | cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new 182 | 183 | # This ugly bit is necessary if you don't want your cache to grow forever 184 | # till it hits GitHub's limit of 5GB. 185 | # Temp fix 186 | # https://github.com/docker/build-push-action/issues/252 187 | # https://github.com/moby/buildkit/issues/1896 188 | - name: 🚚 Move cache 189 | run: | 190 | rm -rf /tmp/.buildx-cache 191 | mv /tmp/.buildx-cache-new /tmp/.buildx-cache 192 | 193 | deploy: 194 | name: 🚀 Deploy 195 | runs-on: ubuntu-latest 196 | needs: [lint, typecheck, vitest, playwright, build] 197 | # only build/deploy main branch on pushes 198 | if: 199 | ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && 200 | github.event_name == 'push' }} 201 | 202 | steps: 203 | - name: 🛑 Cancel Previous Runs 204 | uses: styfle/cancel-workflow-action@0.10.0 205 | 206 | - name: ⬇️ Checkout repo 207 | uses: actions/checkout@v3 208 | 209 | - name: 👀 Read app name 210 | uses: SebRollen/toml-action@v1.0.0 211 | id: app_name 212 | with: 213 | file: 'fly.toml' 214 | field: 'app' 215 | 216 | - name: 🚀 Deploy Staging 217 | if: ${{ github.ref == 'refs/heads/dev' }} 218 | uses: superfly/flyctl-actions@1.3 219 | with: 220 | args: 221 | 'deploy --app ${{ steps.app_name.outputs.value }}-staging --image 222 | registry.fly.io/${{ steps.app_name.outputs.value }}:${{ 223 | github.ref_name }}-${{ github.sha }}' 224 | env: 225 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 226 | 227 | - name: 🚀 Deploy Production 228 | if: ${{ github.ref == 'refs/heads/main' }} 229 | uses: superfly/flyctl-actions@1.3 230 | with: 231 | args: 232 | 'deploy --image registry.fly.io/${{ steps.app_name.outputs.value 233 | }}:${{ github.ref_name }}-${{ github.sha }}' 234 | env: 235 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 236 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /build 4 | /server-build 5 | /public/build 6 | .env 7 | 8 | /prisma/data.db 9 | /prisma/data.db-journal 10 | /prisma/data.test.db 11 | /prisma/data.test.db-journal 12 | 13 | /app/styles/tailwind.css 14 | *.local.* 15 | /test-results/ 16 | /playwright-report/ 17 | /playwright/.cache/ 18 | /mocks/fixtures/*.json 19 | /coverage 20 | /prisma/data.db.bkp 21 | 22 | prisma/test -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | registry=https://registry.npmjs.org/ 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /build 4 | /public/build 5 | .env 6 | 7 | /app/styles/tailwind.css 8 | package.json 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your willingness to contribute! Please make sure to check with me 4 | before doing a bunch of work on something. 5 | 6 | ## Project setup 7 | 8 | If you do need to set the project up locally yourself, feel free to follow these 9 | instructions: 10 | 11 | ### System Requirements 12 | 13 | - [Node.js](https://nodejs.org/) >= 18.0.0 14 | - [npm](https://npmjs.com/) >= 8.18.0 15 | - [git](https://git-scm.com/) >= 2.38.0 16 | 17 | ### Setup steps 18 | 19 | 1. Fork and clone the repo 20 | 2. Copy `.env.example` into `.env` 21 | 3. Run `npm run setup -s` to install dependencies and run validation 22 | 4. Create a branch for your PR with `git checkout -b pr/your-branch-name` 23 | 24 | > Tip: Keep your `main` branch pointing at the original repository and make pull 25 | > requests from branches on your fork. To do this, run: 26 | > 27 | > ``` 28 | > git remote add upstream https://github.com/epicweb-dev/rocket-rental.git 29 | > git fetch upstream 30 | > git branch --set-upstream-to=upstream/main main 31 | > ``` 32 | > 33 | > This will add the original repository as a "remote" called "upstream," Then 34 | > fetch the git information from that remote, then set your local `main` branch 35 | > to use the upstream main branch whenever you run `git pull`. Then you can make 36 | > all of your pull request branches based on this `main` branch. Whenever you 37 | > want to update your version of `main`, do a regular `git pull`. 38 | 39 | If the setup script doesn't work, you can try to run the commands manually: 40 | 41 | ```sh 42 | git clone 43 | cd ./rocket-rental 44 | 45 | # copy the .env.example to .env 46 | # everything's mocked out during development so you shouldn't need to 47 | # change any of these values unless you want to hit real environments. 48 | cp .env.example .env 49 | 50 | # Install deps 51 | npm install 52 | 53 | # setup database 54 | prisma migrate reset --force 55 | 56 | # Install playwright browsers 57 | npm run test:e2e:install 58 | 59 | # run build, typecheck, linting 60 | npm run validate 61 | ``` 62 | 63 | If that all worked without trouble, you should be able to start development 64 | with: 65 | 66 | ```sh 67 | npm run dev 68 | ``` 69 | 70 | And open up `http://localhost:3000` and rock! 71 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # base node image 2 | FROM node:16-bullseye-slim as base 3 | 4 | # set for base and all layer that inherit from it 5 | ENV NODE_ENV production 6 | 7 | # Install openssl for Prisma 8 | RUN apt-get update && apt-get install -y fuse openssl sqlite3 ca-certificates 9 | 10 | # Install all node_modules, including dev dependencies 11 | FROM base as deps 12 | 13 | WORKDIR /myapp 14 | 15 | ADD package.json package-lock.json .npmrc ./ 16 | RUN npm install --production=false 17 | 18 | # Setup production node_modules 19 | FROM base as production-deps 20 | 21 | WORKDIR /myapp 22 | 23 | COPY --from=deps /myapp/node_modules /myapp/node_modules 24 | ADD package.json package-lock.json .npmrc ./ 25 | RUN npm prune --production 26 | 27 | # Build the app 28 | FROM base as build 29 | 30 | WORKDIR /myapp 31 | 32 | COPY --from=deps /myapp/node_modules /myapp/node_modules 33 | 34 | ADD prisma . 35 | RUN npx prisma generate 36 | 37 | ADD . . 38 | RUN npm run build 39 | 40 | # Finally, build the production image with minimal footprint 41 | FROM base 42 | 43 | ENV FLY="true" 44 | ENV FLY_LITEFS_DIR="/litefs/data" 45 | ENV DATABASE_PATH="$FLY_LITEFS_DIR/sqlite.db" 46 | ENV IMAGE_DATABASE_PATH="$FLY_LITEFS_DIR/image.db" 47 | ENV DATABASE_URL="file:$FLY_LITEFS_DIR/sqlite.db" 48 | ENV PORT="8080" 49 | ENV NODE_ENV="production" 50 | 51 | # add shortcut for connecting to database CLI 52 | RUN echo "#!/bin/sh\nset -x\nsqlite3 \$DATABASE_URL" > /usr/local/bin/database-cli && chmod +x /usr/local/bin/database-cli 53 | 54 | WORKDIR /myapp 55 | 56 | COPY --from=production-deps /myapp/node_modules /myapp/node_modules 57 | COPY --from=build /myapp/node_modules/.prisma /myapp/node_modules/.prisma 58 | 59 | COPY --from=build /myapp/build /myapp/build 60 | COPY --from=build /myapp/public /myapp/public 61 | COPY --from=build /myapp/package.json /myapp/package.json 62 | COPY --from=build /myapp/other/start.js /myapp/other/start.js 63 | COPY --from=build /myapp/prisma /myapp/prisma 64 | 65 | # prepare for litefs 66 | COPY --from=flyio/litefs:sha-d70e353 /usr/local/bin/litefs /usr/local/bin/litefs 67 | ADD other/litefs.yml /etc/litefs.yml 68 | RUN mkdir -p /data ${FLY_LITEFS_DIR} 69 | 70 | CMD ["litefs", "mount", "--", "node", "./other/start.js"] 71 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This material is available for private, non-commercial use under the 2 | [GPL version 3](http://www.gnu.org/licenses/gpl-3.0-standalone.html). If you 3 | would like to use this material to conduct your own workshop, please contact me 4 | at me@kentcdodds.com 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REPLACE_WITH_APP_NAME 2 | 3 | Let's launch together! 4 | 5 | ## Development 6 | 7 | - Initial setup: 8 | 9 | ```sh 10 | npm run setup 11 | ``` 12 | 13 | - Start dev server: 14 | 15 | ```sh 16 | npm run dev 17 | ``` 18 | 19 | This starts your app in development mode, rebuilding assets on file changes. 20 | 21 | The database seed script creates a new user with some data you can use to get 22 | started: 23 | 24 | - Email: `kody@kcd.dev` 25 | - Password: `kodylovesyou` 26 | 27 | ### Connecting to your database 28 | 29 | The sqlite database lives at `/data/sqlite.db` in the deployed application. You 30 | can connect to the live database by running `fly ssh console -C database-cli`. 31 | 32 | ## GitHub Actions 33 | 34 | We use GitHub Actions for continuous integration and deployment. Anything that 35 | gets into the `main` branch will be deployed to production after running 36 | tests/build/etc. Anything in the `dev` branch will be deployed to staging. 37 | 38 | ## Testing 39 | 40 | ### Playwright 41 | 42 | We use Playwright for our End-to-End tests in this project. You'll find those in 43 | the `tests` directory. As you make changes, add to an existing file or create a 44 | new file in the `tests` directory to test your changes. 45 | 46 | To run these tests in development, run `npm run test:e2e:dev` which will start 47 | the dev server for the app and run Playwright on it. 48 | 49 | We have a fixture for testing authenticated features without having to go 50 | through the login flow: 51 | 52 | ```ts 53 | test('my test', async ({ page, login }) => { 54 | const user = await login() 55 | // you are now logged in 56 | }) 57 | ``` 58 | 59 | We also auto-delete the user at the end of your test. That way, we can keep your 60 | local db clean and keep your tests isolated from one another. 61 | 62 | ### Vitest 63 | 64 | For lower level tests of utilities and individual components, we use `vitest`. 65 | We have DOM-specific assertion helpers via 66 | [`@testing-library/jest-dom`](https://testing-library.com/jest-dom). 67 | 68 | ### Type Checking 69 | 70 | This project uses TypeScript. It's recommended to get TypeScript set up for your 71 | editor to get a really great in-editor experience with type checking and 72 | auto-complete. To run type checking across the whole project, run 73 | `npm run typecheck`. 74 | 75 | ### Linting 76 | 77 | This project uses ESLint for linting. That is configured in `.eslintrc.js`. 78 | 79 | ### Formatting 80 | 81 | We use [Prettier](https://prettier.io/) for auto-formatting in this project. 82 | It's recommended to install an editor plugin (like the 83 | [VSCode Prettier plugin](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)) 84 | to get auto-formatting on save. There's also a `npm run format` script you can 85 | run to format all files in the project. 86 | -------------------------------------------------------------------------------- /app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | import { RemixBrowser } from '@remix-run/react' 2 | import { hydrateRoot } from 'react-dom/client' 3 | 4 | hydrateRoot(document, ) 5 | -------------------------------------------------------------------------------- /app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | import { PassThrough } from 'stream' 2 | import type { EntryContext, HandleDataRequestFunction } from '@remix-run/node' 3 | import { Response } from '@remix-run/node' 4 | import { RemixServer } from '@remix-run/react' 5 | import isbot from 'isbot' 6 | import { renderToPipeableStream } from 'react-dom/server' 7 | import { getInstanceInfo, handleTXID } from './utils/fly.server' 8 | import { init, getEnv } from './utils/env.server' 9 | 10 | const ABORT_DELAY = 5000 11 | 12 | init() 13 | global.ENV = getEnv() 14 | 15 | export default async function handleRequest( 16 | request: Request, 17 | responseStatusCode: number, 18 | responseHeaders: Headers, 19 | remixContext: EntryContext, 20 | ) { 21 | const { currentInstance, primaryInstance } = await getInstanceInfo() 22 | 23 | responseHeaders.set('fly-region', process.env.FLY_REGION ?? 'unknown') 24 | responseHeaders.set('fly-app', process.env.FLY_APP_NAME ?? 'unknown') 25 | responseHeaders.set('fly-primary-instance', primaryInstance) 26 | responseHeaders.set('fly-instance', currentInstance) 27 | 28 | const maybeResponse = await handleTXID(request, responseHeaders) 29 | if (maybeResponse) return maybeResponse 30 | 31 | const callbackName = isbot(request.headers.get('user-agent')) 32 | ? 'onAllReady' 33 | : 'onShellReady' 34 | 35 | return new Promise((resolve, reject) => { 36 | let didError = false 37 | 38 | const { pipe, abort } = renderToPipeableStream( 39 | , 40 | { 41 | [callbackName]: () => { 42 | const body = new PassThrough() 43 | 44 | responseHeaders.set('Content-Type', 'text/html') 45 | resolve( 46 | new Response(body, { 47 | headers: responseHeaders, 48 | status: didError ? 500 : responseStatusCode, 49 | }), 50 | ) 51 | 52 | pipe(body) 53 | }, 54 | onShellError: (err: unknown) => { 55 | reject(err) 56 | }, 57 | onError: (error: unknown) => { 58 | didError = true 59 | 60 | console.error(error) 61 | }, 62 | }, 63 | ) 64 | 65 | setTimeout(abort, ABORT_DELAY) 66 | }) 67 | } 68 | 69 | export async function handleDataRequest( 70 | response: Response, 71 | { request }: Parameters[1], 72 | ) { 73 | const { currentInstance, primaryInstance } = await getInstanceInfo() 74 | response.headers.set('fly-region', process.env.FLY_REGION ?? 'unknown') 75 | response.headers.set('fly-app', process.env.FLY_APP_NAME ?? 'unknown') 76 | response.headers.set('fly-primary-instance', primaryInstance) 77 | response.headers.set('fly-instance', currentInstance) 78 | 79 | const maybeResponse = await handleTXID(request, response.headers) 80 | if (maybeResponse) return maybeResponse 81 | 82 | return response 83 | } 84 | -------------------------------------------------------------------------------- /app/root.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | LinksFunction, 3 | DataFunctionArgs, 4 | V2_MetaFunction, 5 | } from '@remix-run/node' 6 | import { json } from '@remix-run/node' 7 | import { 8 | Links, 9 | LiveReload, 10 | Meta, 11 | Outlet, 12 | Scripts, 13 | ScrollRestoration, 14 | useLoaderData, 15 | } from '@remix-run/react' 16 | import { authenticator } from './utils/auth.server' 17 | 18 | import tailwindStylesheetUrl from './styles/tailwind.css' 19 | import { getEnv } from './utils/env.server' 20 | import { prisma } from './utils/db.server' 21 | 22 | export const links: LinksFunction = () => { 23 | return [{ rel: 'stylesheet', href: tailwindStylesheetUrl }] 24 | } 25 | 26 | export const meta: V2_MetaFunction = () => { 27 | return [ 28 | { title: 'REPLACE_WITH_APP_NAME' }, 29 | { charSet: 'utf-8' }, 30 | { name: 'viewport', content: 'width=device-width,initial-scale=1' }, 31 | ] 32 | } 33 | 34 | export async function getUserById(id: string) { 35 | return prisma.user.findUnique({ 36 | where: { id }, 37 | select: { id: true, name: true }, 38 | }) 39 | } 40 | 41 | export async function loader({ request }: DataFunctionArgs) { 42 | const userId = await authenticator.isAuthenticated(request) 43 | 44 | let user: Awaited> | null = null 45 | if (userId) { 46 | user = await getUserById(userId) 47 | if (!user) { 48 | console.log('something weird happened') 49 | // something weird happened... The user is authenticated but we can't find 50 | // them in the database. Maybe they were deleted? Let's log them out. 51 | await authenticator.logout(request, { redirectTo: '/' }) 52 | } 53 | } 54 | 55 | return json({ user, ENV: getEnv() }) 56 | } 57 | 58 | export default function App() { 59 | const data = useLoaderData() 60 | return ( 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |