├── .env.example ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── deploy.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .react-router └── types │ └── app │ ├── +types │ └── root.ts │ └── routes │ ├── +types │ └── $.ts │ ├── _marketing+ │ └── +types │ │ ├── about.ts │ │ ├── index.ts │ │ ├── privacy.ts │ │ ├── support.ts │ │ ├── tailwind-preset.ts │ │ └── tos.ts │ ├── _seo+ │ └── +types │ │ ├── robots[.]txt.ts │ │ └── sitemap[.]xml.ts │ └── resources+ │ └── +types │ ├── healthcheck.ts │ └── theme-switch.ts ├── .vscode ├── extensions.json ├── remix.code-snippets └── settings.json ├── README.md ├── app ├── assets │ └── favicons │ │ ├── apple-touch-icon.png │ │ └── favicon.svg ├── components │ ├── error-boundary.tsx │ ├── forms.tsx │ ├── progress-bar.tsx │ ├── toaster.tsx │ └── ui │ │ ├── README.md │ │ ├── button.tsx │ │ ├── checkbox.tsx │ │ ├── dropdown-menu.tsx │ │ ├── icon.tsx │ │ ├── icons │ │ ├── README.md │ │ ├── name.d.ts │ │ └── sprite.svg │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── sonner.tsx │ │ ├── status-button.tsx │ │ ├── textarea.tsx │ │ └── tooltip.tsx ├── entry.client.tsx ├── entry.server.tsx ├── root.tsx ├── routes.ts ├── routes │ ├── $.tsx │ ├── _marketing+ │ │ ├── about.tsx │ │ ├── index.tsx │ │ ├── logos │ │ │ ├── docker.svg │ │ │ ├── eslint.svg │ │ │ ├── faker.svg │ │ │ ├── fly.svg │ │ │ ├── github.svg │ │ │ ├── logos.ts │ │ │ ├── msw.svg │ │ │ ├── playwright.svg │ │ │ ├── prettier.svg │ │ │ ├── prisma.svg │ │ │ ├── radix.svg │ │ │ ├── react-email.svg │ │ │ ├── remix.svg │ │ │ ├── resend.svg │ │ │ ├── sentry.svg │ │ │ ├── shadcn-ui.svg │ │ │ ├── sqlite.svg │ │ │ ├── stars.jpg │ │ │ ├── tailwind.svg │ │ │ ├── testing-library.png │ │ │ ├── typescript.svg │ │ │ ├── vitest.svg │ │ │ └── zod.svg │ │ ├── privacy.tsx │ │ ├── support.tsx │ │ ├── tailwind-preset.ts │ │ └── tos.tsx │ ├── _seo+ │ │ ├── robots[.]txt.ts │ │ └── sitemap[.]xml.ts │ └── resources+ │ │ ├── healthcheck.tsx │ │ └── theme-switch.tsx ├── styles │ └── tailwind.css └── utils │ ├── client-hints.tsx │ ├── email.server.ts │ ├── env.server.ts │ ├── extended-theme.ts │ ├── headers.server.ts │ ├── honeypot.server.ts │ ├── misc.tsx │ ├── monitoring.client.tsx │ ├── nonce-provider.ts │ ├── request-info.ts │ ├── theme.server.ts │ ├── timing.server.ts │ └── toast.server.ts ├── components.json ├── eslint.config.js ├── fly.toml ├── index.js ├── other ├── Dockerfile ├── Dockerfile.dockerignore ├── README.md ├── build-icons.ts ├── build-server.ts ├── sly │ ├── sly.json │ └── transform-icon.ts └── svg-icons │ ├── README.md │ ├── arrow-left.svg │ ├── arrow-right.svg │ ├── avatar.svg │ ├── camera.svg │ ├── check.svg │ ├── clock.svg │ ├── cross-1.svg │ ├── dots-horizontal.svg │ ├── download.svg │ ├── envelope-closed.svg │ ├── exit.svg │ ├── file-text.svg │ ├── github-logo.svg │ ├── laptop.svg │ ├── link-2.svg │ ├── lock-closed.svg │ ├── lock-open-1.svg │ ├── magnifying-glass.svg │ ├── moon.svg │ ├── pencil-1.svg │ ├── pencil-2.svg │ ├── plus.svg │ ├── question-mark-circled.svg │ ├── reset.svg │ ├── sun.svg │ ├── trash.svg │ └── update.svg ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── favicons │ ├── README.md │ ├── android-chrome-192x192.png │ └── android-chrome-512x512.png ├── img │ └── user.png └── site.webmanifest ├── react-router.config.ts ├── server ├── dev-server.js ├── index.ts └── utils │ └── monitoring.ts ├── tailwind.config.ts ├── tsconfig.json ├── types ├── deps.d.ts ├── env.env.d.ts ├── icon-name.d.ts └── reset.d.ts └── vite.config.ts /.env.example: -------------------------------------------------------------------------------- 1 | SESSION_SECRET="super-duper-s3cret" 2 | HONEYPOT_SECRET="super-duper-s3cret" 3 | INTERNAL_COMMAND_TOKEN="some-made-up-token" 4 | RESEND_API_KEY="re_blAh_blaHBlaHblahBLAhBlAh" 5 | SENTRY_DSN="your-dsn" 6 | 7 | # set this to false to prevent search engines from indexing the website 8 | # default to allow indexing for seo safety 9 | ALLOW_INDEXING="true" 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Test Plan 4 | 5 | 6 | 7 | ## Checklist 8 | 9 | - [ ] Tests updated 10 | - [ ] Docs updated 11 | 12 | ## Screenshots 13 | 14 | 16 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - dev 7 | pull_request: {} 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | permissions: 14 | actions: write 15 | contents: read 16 | 17 | jobs: 18 | lint: 19 | name: ⬣ ESLint 20 | runs-on: ubuntu-22.04 21 | steps: 22 | - name: ⬇️ Checkout repo 23 | uses: actions/checkout@v4 24 | 25 | - name: ⎔ Setup node 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: 22 29 | 30 | - name: 📥 Download deps 31 | uses: bahmutov/npm-install@v1 32 | 33 | - name: 🖼 Build icons 34 | run: npm run build:icons 35 | 36 | - name: 🔬 Lint 37 | run: npm run lint 38 | 39 | typecheck: 40 | name: ʦ TypeScript 41 | runs-on: ubuntu-22.04 42 | steps: 43 | - name: ⬇️ Checkout repo 44 | uses: actions/checkout@v4 45 | 46 | - name: ⎔ Setup node 47 | uses: actions/setup-node@v4 48 | with: 49 | node-version: 22 50 | 51 | - name: 📥 Download deps 52 | uses: bahmutov/npm-install@v1 53 | 54 | - name: 🖼 Build icons 55 | run: npm run build:icons 56 | 57 | - name: 🔎 Type check 58 | run: npm run typecheck --if-present 59 | 60 | # deploy: 61 | # name: 🚀 Deploy 62 | # runs-on: ubuntu-22.04 63 | # needs: [lint, typecheck] 64 | # # only build/deploy branches on pushes 65 | # if: ${{ github.event_name == 'push' }} 66 | 67 | # steps: 68 | # - name: ⬇️ Checkout repo 69 | # uses: actions/checkout@v4 70 | # with: 71 | # fetch-depth: '50' 72 | 73 | # - name: 👀 Read app name 74 | # uses: SebRollen/toml-action@v1.2.0 75 | # id: app_name 76 | # with: 77 | # file: 'fly.toml' 78 | # field: 'app' 79 | 80 | # - name: 🎈 Setup Fly 81 | # uses: superfly/flyctl-actions/setup-flyctl@1.5 82 | 83 | # - name: 🚀 Deploy Staging 84 | # if: ${{ github.ref == 'refs/heads/dev' }} 85 | # run: 86 | # flyctl deploy --remote-only --build-arg COMMIT_SHA=${{ github.sha }} 87 | # --app ${{ steps.app_name.outputs.value }}-staging 88 | # env: 89 | # FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 90 | 91 | # - name: 🚀 Deploy Production 92 | # if: ${{ github.ref == 'refs/heads/main' }} 93 | # run: 94 | # flyctl deploy --remote-only --build-arg COMMIT_SHA=${{ github.sha }} 95 | # --build-secret SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} 96 | # env: 97 | # FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 98 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_store 3 | 4 | /build 5 | /server-build 6 | .env 7 | .cache 8 | 9 | # Easy way to create temporary files/folders that won't accidentally be added to git 10 | *.local.* 11 | 12 | # generated files 13 | /app/components/ui/icons 14 | .react-router/ 15 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | registry=https://registry.npmjs.org/ 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /build 4 | /public/build 5 | /server-build 6 | .env 7 | 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /.react-router/types/app/+types/root.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // root.tsx 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | 7 | 8 | type Module = typeof import("../root.js") 9 | 10 | export type Info = { 11 | parents: [], 12 | id: "root" 13 | file: "root.tsx" 14 | path: "" 15 | params: {} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.react-router/types/app/routes/+types/$.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // routes/$.tsx 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | import type { Info as Parent0 } from "../../+types/root.js" 7 | 8 | type Module = typeof import("../$.js") 9 | 10 | export type Info = { 11 | parents: [Parent0], 12 | id: "routes/$" 13 | file: "routes/$.tsx" 14 | path: "*" 15 | params: {"*": string} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.react-router/types/app/routes/_marketing+/+types/about.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // routes/_marketing+/about.tsx 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | import type { Info as Parent0 } from "../../../+types/root.js" 7 | 8 | type Module = typeof import("../about.js") 9 | 10 | export type Info = { 11 | parents: [Parent0], 12 | id: "routes/_marketing+/about" 13 | file: "routes/_marketing+/about.tsx" 14 | path: "about" 15 | params: {} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.react-router/types/app/routes/_marketing+/+types/index.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // routes/_marketing+/index.tsx 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | import type { Info as Parent0 } from "../../../+types/root.js" 7 | 8 | type Module = typeof import("../index.js") 9 | 10 | export type Info = { 11 | parents: [Parent0], 12 | id: "routes/_marketing+/index" 13 | file: "routes/_marketing+/index.tsx" 14 | path: "undefined" 15 | params: {} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.react-router/types/app/routes/_marketing+/+types/privacy.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // routes/_marketing+/privacy.tsx 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | import type { Info as Parent0 } from "../../../+types/root.js" 7 | 8 | type Module = typeof import("../privacy.js") 9 | 10 | export type Info = { 11 | parents: [Parent0], 12 | id: "routes/_marketing+/privacy" 13 | file: "routes/_marketing+/privacy.tsx" 14 | path: "privacy" 15 | params: {} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.react-router/types/app/routes/_marketing+/+types/support.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // routes/_marketing+/support.tsx 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | import type { Info as Parent0 } from "../../../+types/root.js" 7 | 8 | type Module = typeof import("../support.js") 9 | 10 | export type Info = { 11 | parents: [Parent0], 12 | id: "routes/_marketing+/support" 13 | file: "routes/_marketing+/support.tsx" 14 | path: "support" 15 | params: {} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.react-router/types/app/routes/_marketing+/+types/tailwind-preset.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // routes/_marketing+/tailwind-preset.ts 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | import type { Info as Parent0 } from "../../../+types/root.js" 7 | 8 | type Module = typeof import("../tailwind-preset.js") 9 | 10 | export type Info = { 11 | parents: [Parent0], 12 | id: "routes/_marketing+/tailwind-preset" 13 | file: "routes/_marketing+/tailwind-preset.ts" 14 | path: "tailwind-preset" 15 | params: {} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.react-router/types/app/routes/_marketing+/+types/tos.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // routes/_marketing+/tos.tsx 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | import type { Info as Parent0 } from "../../../+types/root.js" 7 | 8 | type Module = typeof import("../tos.js") 9 | 10 | export type Info = { 11 | parents: [Parent0], 12 | id: "routes/_marketing+/tos" 13 | file: "routes/_marketing+/tos.tsx" 14 | path: "tos" 15 | params: {} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.react-router/types/app/routes/_seo+/+types/robots[.]txt.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // routes/_seo+/robots[.]txt.ts 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | import type { Info as Parent0 } from "../../../+types/root.js" 7 | 8 | type Module = typeof import("../robots[.]txt.js") 9 | 10 | export type Info = { 11 | parents: [Parent0], 12 | id: "routes/_seo+/robots[.]txt" 13 | file: "routes/_seo+/robots[.]txt.ts" 14 | path: "robots.txt" 15 | params: {} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.react-router/types/app/routes/_seo+/+types/sitemap[.]xml.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // routes/_seo+/sitemap[.]xml.ts 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | import type { Info as Parent0 } from "../../../+types/root.js" 7 | 8 | type Module = typeof import("../sitemap[.]xml.js") 9 | 10 | export type Info = { 11 | parents: [Parent0], 12 | id: "routes/_seo+/sitemap[.]xml" 13 | file: "routes/_seo+/sitemap[.]xml.ts" 14 | path: "sitemap.xml" 15 | params: {} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.react-router/types/app/routes/resources+/+types/healthcheck.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // routes/resources+/healthcheck.tsx 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | import type { Info as Parent0 } from "../../../+types/root.js" 7 | 8 | type Module = typeof import("../healthcheck.js") 9 | 10 | export type Info = { 11 | parents: [Parent0], 12 | id: "routes/resources+/healthcheck" 13 | file: "routes/resources+/healthcheck.tsx" 14 | path: "resources/healthcheck" 15 | params: {} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.react-router/types/app/routes/resources+/+types/theme-switch.ts: -------------------------------------------------------------------------------- 1 | // React Router generated types for route: 2 | // routes/resources+/theme-switch.tsx 3 | 4 | import type * as T from "react-router/route-module" 5 | 6 | import type { Info as Parent0 } from "../../../+types/root.js" 7 | 8 | type Module = typeof import("../theme-switch.js") 9 | 10 | export type Info = { 11 | parents: [Parent0], 12 | id: "routes/resources+/theme-switch" 13 | file: "routes/resources+/theme-switch.tsx" 14 | path: "resources/theme-switch" 15 | params: {} & { [key: string]: string | undefined } 16 | module: Module 17 | loaderData: T.CreateLoaderData 18 | actionData: T.CreateActionData 19 | } 20 | 21 | export namespace Route { 22 | export type LinkDescriptors = T.LinkDescriptors 23 | export type LinksFunction = () => LinkDescriptors 24 | 25 | export type MetaArgs = T.CreateMetaArgs 26 | export type MetaDescriptors = T.MetaDescriptors 27 | export type MetaFunction = (args: MetaArgs) => MetaDescriptors 28 | 29 | export type HeadersArgs = T.HeadersArgs 30 | export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit 31 | 32 | export type LoaderArgs = T.CreateServerLoaderArgs 33 | export type ClientLoaderArgs = T.CreateClientLoaderArgs 34 | export type ActionArgs = T.CreateServerActionArgs 35 | export type ClientActionArgs = T.CreateClientActionArgs 36 | 37 | export type HydrateFallbackProps = T.CreateHydrateFallbackProps 38 | export type ComponentProps = T.CreateComponentProps 39 | export type ErrorBoundaryProps = T.CreateErrorBoundaryProps 40 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "bradlc.vscode-tailwindcss", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "yoavbls.pretty-ts-errors", 7 | "github.vscode-github-actions" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/remix.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "loader": { 3 | "prefix": "/loader", 4 | "scope": "typescriptreact,javascriptreact,typescript,javascript", 5 | "body": [ 6 | "import { type Route } from \"./+types/${TM_FILENAME_BASE}.ts\"", 7 | "", 8 | "export async function loader({ request }: Route.LoaderArgs) {", 9 | " return {}", 10 | "}", 11 | ], 12 | }, 13 | "action": { 14 | "prefix": "/action", 15 | "scope": "typescriptreact,javascriptreact,typescript,javascript", 16 | "body": [ 17 | "import { type Route } from \"./+types/${TM_FILENAME_BASE}.ts\"", 18 | "", 19 | "export async function action({ request }: Route.ActionArgs) {", 20 | " return {}", 21 | "}", 22 | ], 23 | }, 24 | "default": { 25 | "prefix": "/default", 26 | "scope": "typescriptreact,javascriptreact,typescript,javascript", 27 | "body": [ 28 | "export default function ${TM_FILENAME_BASE/[^a-zA-Z0-9]*([a-zA-Z0-9])([a-zA-Z0-9]*)/${1:/capitalize}${2}/g}() {", 29 | " return (", 30 | "
", 31 | "

Unknown Route

", 32 | "
", 33 | " )", 34 | "}", 35 | ], 36 | }, 37 | "headers": { 38 | "prefix": "/headers", 39 | "scope": "typescriptreact,javascriptreact,typescript,javascript", 40 | "body": [ 41 | "import { type Route } from \"./+types/${TM_FILENAME_BASE}.ts\"", 42 | "export const headers: Route.HeadersFunction = ({ loaderHeaders }) => ({", 43 | " 'Cache-Control': loaderHeaders.get('Cache-Control') ?? '',", 44 | "})", 45 | ], 46 | }, 47 | "links": { 48 | "prefix": "/links", 49 | "scope": "typescriptreact,javascriptreact,typescript,javascript", 50 | "body": [ 51 | "import { type Route } from \"./+types/${TM_FILENAME_BASE}.ts\"", 52 | "", 53 | "export const links: Route.LinksFunction = () => {", 54 | " return []", 55 | "}", 56 | ], 57 | }, 58 | "meta": { 59 | "prefix": "/meta", 60 | "scope": "typescriptreact,javascriptreact,typescript,javascript", 61 | "body": [ 62 | "import { type Route } from \"./+types/${TM_FILENAME_BASE}.ts\"", 63 | "", 64 | "export const meta: Route.MetaFunction = ({ data }) => [{", 65 | " title: 'Title',", 66 | "}]", 67 | ], 68 | }, 69 | "shouldRevalidate": { 70 | "prefix": "/shouldRevalidate", 71 | "scope": "typescriptreact,javascriptreact,typescript,javascript", 72 | "body": [ 73 | "import { type ShouldRevalidateFunctionArgs } from 'react-router'", 74 | "", 75 | "export function shouldRevalidate({ defaultShouldRevalidate }: ShouldRevalidateFunctionArgs) {", 76 | " return defaultShouldRevalidate", 77 | "}", 78 | ], 79 | }, 80 | } 81 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.preferences.autoImportFileExcludePatterns": [ 3 | "@remix-run/server-runtime", 4 | "express", 5 | "@radix-ui/**", 6 | "@react-email/**", 7 | "node:stream/consumers", 8 | "node:console" 9 | ], 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Epic Stack for content sites 2 | 3 | ## Summary 4 | 1. Commit [f964cbf](https://github.com/arpitdalal/epic-content-stack/commit/f964cbf0f4809016b41a7f9f95e9591bc2bcbfe4) uninstalls `playwright`, removes the `mocks`, `fixtures`, and `e2e` directories from `tests` directory. Also removes `app/routes/_auth+/auth.$provider.callback.test.ts` and `app/routes/users+/$username.test.tsx` e2e test files. Also modifies a few files, please see the commit history for more details. 5 | 2. Commit [829014d](https://github.com/arpitdalal/epic-content-stack/commit/829014d41db6bdfc4b0e8fd7490bba98f0934e75) uninstalls a few packages, removes all the authentication related code, and modifies a few files, please see the commit history for more details. 6 | 3. Commit [7b325840](https://github.com/arpitdalal/epic-content-stack/commit/7b32584037faa61da65b0404b5a4de86e2b41cdb) updates to the latest epic-stack and remove unnecessary code and utilities. 7 | 4. Commit [659cba49](https://github.com/arpitdalal/epic-content-stack/commit/659cba49671084751a764450bd8a23032dd8d1af) removes unused dependencies and updates the configuration files that were missed in the previous commit. 8 | 9 | > [!IMPORTANT] 10 | > Note that `fly.toml` has a different `internal_port` value. This is because the `litefs.yml` file was proxying the ports but since that is not the case anymore, we need to update the `internal_port` value in the `fly.toml` file. 11 | 12 | ## FYI 13 | - Toast, Honeypot, Conform, Email, and Timing utils are not removed so they can be used if needed. -------------------------------------------------------------------------------- /app/assets/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arpitdalal/epic-content-stack/80171e2f58c6215ad7814108508b7ec9585967c0/app/assets/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /app/assets/favicons/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/components/error-boundary.tsx: -------------------------------------------------------------------------------- 1 | import { captureException } from '@sentry/react' 2 | import { useEffect, type ReactElement } from 'react' 3 | import { 4 | type ErrorResponse, 5 | isRouteErrorResponse, 6 | useParams, 7 | useRouteError, 8 | } from 'react-router' 9 | import { getErrorMessage } from '#app/utils/misc' 10 | 11 | type StatusHandler = (info: { 12 | error: ErrorResponse 13 | params: Record 14 | }) => ReactElement | null 15 | 16 | export function GeneralErrorBoundary({ 17 | defaultStatusHandler = ({ error }) => ( 18 |

19 | {error.status} {error.data} 20 |

21 | ), 22 | statusHandlers, 23 | unexpectedErrorHandler = (error) =>

{getErrorMessage(error)}

, 24 | }: { 25 | defaultStatusHandler?: StatusHandler 26 | statusHandlers?: Record 27 | unexpectedErrorHandler?: (error: unknown) => ReactElement | null 28 | }) { 29 | const error = useRouteError() 30 | const params = useParams() 31 | const isResponse = isRouteErrorResponse(error) 32 | 33 | if (typeof document !== 'undefined') { 34 | console.error(error) 35 | } 36 | 37 | useEffect(() => { 38 | if (isResponse) return 39 | 40 | captureException(error) 41 | }, [error, isResponse]) 42 | 43 | return ( 44 |
45 | {isResponse 46 | ? (statusHandlers?.[error.status] ?? defaultStatusHandler)({ 47 | error, 48 | params, 49 | }) 50 | : unexpectedErrorHandler(error)} 51 |
52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /app/components/forms.tsx: -------------------------------------------------------------------------------- 1 | import { useInputControl } from '@conform-to/react' 2 | import React, { useId } from 'react' 3 | import { Checkbox, type CheckboxProps } from './ui/checkbox.tsx' 4 | import { Input } from './ui/input.tsx' 5 | import { Label } from './ui/label.tsx' 6 | import { Textarea } from './ui/textarea.tsx' 7 | 8 | export type ListOfErrors = Array | null | undefined 9 | 10 | export function ErrorList({ 11 | id, 12 | errors, 13 | }: { 14 | errors?: ListOfErrors 15 | id?: string 16 | }) { 17 | const errorsToRender = errors?.filter(Boolean) 18 | if (!errorsToRender?.length) return null 19 | return ( 20 |
    21 | {errorsToRender.map((e) => ( 22 |
  • 23 | {e} 24 |
  • 25 | ))} 26 |
27 | ) 28 | } 29 | 30 | export function Field({ 31 | labelProps, 32 | inputProps, 33 | errors, 34 | className, 35 | }: { 36 | labelProps: React.LabelHTMLAttributes 37 | inputProps: React.InputHTMLAttributes 38 | errors?: ListOfErrors 39 | className?: string 40 | }) { 41 | const fallbackId = useId() 42 | const id = inputProps.id ?? fallbackId 43 | const errorId = errors?.length ? `${id}-error` : undefined 44 | return ( 45 |
46 |
57 | ) 58 | } 59 | 60 | export function TextareaField({ 61 | labelProps, 62 | textareaProps, 63 | errors, 64 | className, 65 | }: { 66 | labelProps: React.LabelHTMLAttributes 67 | textareaProps: React.TextareaHTMLAttributes 68 | errors?: ListOfErrors 69 | className?: string 70 | }) { 71 | const fallbackId = useId() 72 | const id = textareaProps.id ?? textareaProps.name ?? fallbackId 73 | const errorId = errors?.length ? `${id}-error` : undefined 74 | return ( 75 |
76 |