From 1c44958386ad1d371c37bb8abd5f6f58675bdf55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81mi=20Peron?= Date: Fri, 15 Sep 2023 14:39:29 +0200 Subject: [PATCH] First commit --- .env.example | 14 + .eslintrc.cjs | 85 + .github/PULL_REQUEST_TEMPLATE.md | 15 + .github/workflows/deploy.yml | 181 + .gitignore | 22 + .npmrc | 2 + .prettierignore | 15 + .prettierrc.js | 30 + .vscode/extensions.json | 11 + .vscode/settings.json | 19 + COMMUNITY.md | 12 + README.md | 54 + app/components/confetti.tsx | 21 + app/components/error-boundary.tsx | 45 + app/components/floating-toolbar.tsx | 2 + app/components/forms.tsx | 148 + app/components/search-bar.tsx | 62 + app/components/spacer.tsx | 57 + app/components/spinner.tsx | 33 + app/components/toaster.tsx | 22 + app/components/ui/README.md | 7 + app/components/ui/button.tsx | 58 + app/components/ui/checkbox.tsx | 41 + app/components/ui/dropdown-menu.tsx | 206 + app/components/ui/icon.tsx | 65 + app/components/ui/icons/README.md | 5 + app/components/ui/icons/name.d.ts | 30 + app/components/ui/icons/sprite.svg | 195 + app/components/ui/input.tsx | 25 + app/components/ui/label.tsx | 24 + app/components/ui/status-button.tsx | 65 + app/components/ui/textarea.tsx | 24 + app/components/ui/tooltip.tsx | 28 + app/entry.client.tsx | 11 + app/entry.server.tsx | 87 + app/root.tsx | 449 + app/routes/$.tsx | 43 + .../_auth+/auth.$provider.callback.test.ts | 259 + app/routes/_auth+/auth.$provider.callback.ts | 179 + app/routes/_auth+/auth.$provider.ts | 33 + app/routes/_auth+/forgot-password.tsx | 183 + app/routes/_auth+/login.tsx | 359 + app/routes/_auth+/logout.tsx | 10 + app/routes/_auth+/onboarding.tsx | 251 + app/routes/_auth+/onboarding_.$provider.tsx | 288 + app/routes/_auth+/reset-password.tsx | 172 + app/routes/_auth+/signup.tsx | 169 + app/routes/_auth+/verify.tsx | 330 + app/routes/_marketing+/about.tsx | 3 + app/routes/_marketing+/index.tsx | 84 + app/routes/_marketing+/logos/docker.png | Bin 0 -> 90758 bytes app/routes/_marketing+/logos/eslint.svg | 17 + app/routes/_marketing+/logos/faker.svg | 736 + app/routes/_marketing+/logos/fly.svg | 1 + app/routes/_marketing+/logos/github.svg | 1 + app/routes/_marketing+/logos/logos.ts | 131 + app/routes/_marketing+/logos/msw.svg | 13 + app/routes/_marketing+/logos/playwright.svg | 9 + app/routes/_marketing+/logos/prettier.svg | 76 + app/routes/_marketing+/logos/prisma.svg | 9 + app/routes/_marketing+/logos/radix.svg | 1 + app/routes/_marketing+/logos/react-email.svg | 1 + app/routes/_marketing+/logos/remix.png | Bin 0 -> 204228 bytes app/routes/_marketing+/logos/resend.svg | 31 + app/routes/_marketing+/logos/sentry.svg | 6 + app/routes/_marketing+/logos/shadcn-ui.svg | 1 + app/routes/_marketing+/logos/sqlite.svg | 67 + app/routes/_marketing+/logos/stars.jpg | Bin 0 -> 391161 bytes app/routes/_marketing+/logos/tailwind.svg | 1 + .../_marketing+/logos/testing-library.png | Bin 0 -> 5803 bytes app/routes/_marketing+/logos/typescript.svg | 6 + app/routes/_marketing+/logos/vitest.svg | 5 + app/routes/_marketing+/logos/zod.svg | 46 + app/routes/_marketing+/privacy.tsx | 3 + app/routes/_marketing+/support.tsx | 3 + app/routes/_marketing+/tos.tsx | 3 + app/routes/admin+/cache.tsx | 235 + app/routes/admin+/cache_.lru.$cacheKey.ts | 28 + app/routes/admin+/cache_.sqlite.$cacheKey.ts | 28 + app/routes/admin+/cache_.sqlite.tsx | 55 + app/routes/me.tsx | 18 + app/routes/resources+/download-user-data.tsx | 62 + app/routes/resources+/healthcheck.tsx | 3 + .../resources+/note-images.$imageId.tsx | 22 + .../resources+/user-images.$imageId.tsx | 22 + app/routes/settings+/profile.change-email.tsx | 242 + app/routes/settings+/profile.connections.tsx | 206 + app/routes/settings+/profile.index.tsx | 353 + app/routes/settings+/profile.password.tsx | 160 + .../settings+/profile.password_.create.tsx | 128 + app/routes/settings+/profile.photo.tsx | 223 + app/routes/settings+/profile.tsx | 65 + .../settings+/profile.two-factor.disable.tsx | 59 + .../settings+/profile.two-factor.index.tsx | 86 + app/routes/settings+/profile.two-factor.tsx | 13 + .../settings+/profile.two-factor.verify.tsx | 197 + app/routes/users+/$username.tsx | 126 + .../users+/$username_+/__note-editor.tsx | 401 + .../users+/$username_+/notes.$noteId.tsx | 227 + .../$username_+/notes.$noteId_.edit.tsx | 37 + app/routes/users+/$username_+/notes.index.tsx | 29 + app/routes/users+/$username_+/notes.new.tsx | 12 + app/routes/users+/$username_+/notes.tsx | 99 + app/routes/users+/index.tsx | 113 + app/styles/font.css | 328 + app/styles/tailwind.css | 89 + app/utils/auth.server.ts | 235 + app/utils/cache.server.ts | 167 + app/utils/client-hints.tsx | 141 + app/utils/confetti.server.ts | 42 + app/utils/connections.server.ts | 31 + app/utils/connections.tsx | 57 + app/utils/db.server.ts | 36 + app/utils/email.server.ts | 98 + app/utils/env.server.ts | 62 + app/utils/extended-theme.ts | 104 + app/utils/litefs.server.ts | 5 + app/utils/misc.error-message.test.ts | 24 + app/utils/misc.tsx | 345 + app/utils/misc.use-double-check.test.tsx | 82 + app/utils/monitoring.client.tsx | 30 + app/utils/monitoring.server.ts | 10 + app/utils/nonce-provider.ts | 5 + app/utils/permissions.ts | 101 + app/utils/providers/github.server.ts | 64 + app/utils/providers/provider.ts | 19 + app/utils/redirect-cookie.server.ts | 17 + app/utils/request-info.ts | 13 + app/utils/session.server.ts | 38 + app/utils/theme.server.ts | 19 + app/utils/timing.server.ts | 122 + app/utils/toast.server.ts | 66 + app/utils/user-validation.ts | 27 + app/utils/user.ts | 25 + app/utils/verification.server.ts | 13 + components.json | 15 + fly.toml | 59 + index.js | 24 + other/.dockerignore | 9 + other/Dockerfile | 78 + other/README.md | 9 + other/build-icons.ts | 153 + other/build-server.ts | 50 + other/litefs.yml | 37 + other/sentry-create-release.js | 19 + other/setup-swap.js | 13 + other/sly/sly.json | 11 + other/sly/transform-icon.ts | 19 + other/svg-icons/README.md | 11 + other/svg-icons/arrow-left.svg | 11 + other/svg-icons/arrow-right.svg | 11 + other/svg-icons/avatar.svg | 11 + other/svg-icons/camera.svg | 11 + other/svg-icons/check.svg | 11 + other/svg-icons/clock.svg | 11 + other/svg-icons/cross-1.svg | 11 + other/svg-icons/dots-horizontal.svg | 13 + other/svg-icons/download.svg | 13 + other/svg-icons/envelope-closed.svg | 13 + other/svg-icons/exit.svg | 11 + other/svg-icons/file-text.svg | 11 + other/svg-icons/github-logo.svg | 13 + other/svg-icons/laptop.svg | 11 + other/svg-icons/link-2.svg | 13 + other/svg-icons/lock-closed.svg | 11 + other/svg-icons/lock-open-1.svg | 11 + other/svg-icons/magnifying-glass.svg | 11 + other/svg-icons/moon.svg | 11 + other/svg-icons/pencil-1.svg | 11 + other/svg-icons/pencil-2.svg | 11 + other/svg-icons/plus.svg | 11 + other/svg-icons/question-mark-circled.svg | 13 + other/svg-icons/reset.svg | 11 + other/svg-icons/sun.svg | 11 + other/svg-icons/trash.svg | 11 + other/svg-icons/update.svg | 11 + package-lock.json | 21515 ++++++++++++++++ package.json | 160 + playwright.config.ts | 41 + postcss.config.js | 7 + .../20230914194400_init/migration.sql | 170 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 169 + prisma/seed.ts | 270 + public/favicon.ico | Bin 0 -> 15406 bytes public/favicons/README.md | 9 + public/favicons/android-chrome-192x192.png | Bin 0 -> 10041 bytes public/favicons/android-chrome-512x512.png | Bin 0 -> 27787 bytes public/favicons/apple-touch-icon.png | Bin 0 -> 8986 bytes public/favicons/favicon-16x16.png | Bin 0 -> 669 bytes public/favicons/favicon-32x32.png | Bin 0 -> 1435 bytes public/favicons/favicon.svg | 13 + public/favicons/mask-icon.svg | 1 + .../nunito-sans-v12-latin_latin-ext-200.woff | Bin 0 -> 31044 bytes .../nunito-sans-v12-latin_latin-ext-200.woff2 | Bin 0 -> 24764 bytes ...to-sans-v12-latin_latin-ext-200italic.woff | Bin 0 -> 32404 bytes ...o-sans-v12-latin_latin-ext-200italic.woff2 | Bin 0 -> 25736 bytes .../nunito-sans-v12-latin_latin-ext-300.woff | Bin 0 -> 32524 bytes .../nunito-sans-v12-latin_latin-ext-300.woff2 | Bin 0 -> 25984 bytes ...to-sans-v12-latin_latin-ext-300italic.woff | Bin 0 -> 34132 bytes ...o-sans-v12-latin_latin-ext-300italic.woff2 | Bin 0 -> 27140 bytes .../nunito-sans-v12-latin_latin-ext-600.woff | Bin 0 -> 32864 bytes .../nunito-sans-v12-latin_latin-ext-600.woff2 | Bin 0 -> 26304 bytes ...to-sans-v12-latin_latin-ext-600italic.woff | Bin 0 -> 34332 bytes ...o-sans-v12-latin_latin-ext-600italic.woff2 | Bin 0 -> 27380 bytes .../nunito-sans-v12-latin_latin-ext-700.woff | Bin 0 -> 32772 bytes .../nunito-sans-v12-latin_latin-ext-700.woff2 | Bin 0 -> 26176 bytes ...to-sans-v12-latin_latin-ext-700italic.woff | Bin 0 -> 34076 bytes ...o-sans-v12-latin_latin-ext-700italic.woff2 | Bin 0 -> 27240 bytes .../nunito-sans-v12-latin_latin-ext-800.woff | Bin 0 -> 33036 bytes .../nunito-sans-v12-latin_latin-ext-800.woff2 | Bin 0 -> 26400 bytes ...to-sans-v12-latin_latin-ext-800italic.woff | Bin 0 -> 34300 bytes ...o-sans-v12-latin_latin-ext-800italic.woff2 | Bin 0 -> 27320 bytes .../nunito-sans-v12-latin_latin-ext-900.woff | Bin 0 -> 33156 bytes .../nunito-sans-v12-latin_latin-ext-900.woff2 | Bin 0 -> 26632 bytes ...to-sans-v12-latin_latin-ext-900italic.woff | Bin 0 -> 34396 bytes ...o-sans-v12-latin_latin-ext-900italic.woff2 | Bin 0 -> 27584 bytes ...unito-sans-v12-latin_latin-ext-italic.woff | Bin 0 -> 34216 bytes ...nito-sans-v12-latin_latin-ext-italic.woff2 | Bin 0 -> 27156 bytes ...nito-sans-v12-latin_latin-ext-regular.woff | Bin 0 -> 32664 bytes ...ito-sans-v12-latin_latin-ext-regular.woff2 | Bin 0 -> 26012 bytes public/img/user.png | Bin 0 -> 3012 bytes public/robots.txt | 2 + public/site.webmanifest | 20 + remix.config.js | 32 + server/dev-server.js | 19 + server/index.ts | 269 + tailwind.config.ts | 26 + tests/db-utils.ts | 115 + tests/e2e/2fa.test.ts | 52 + tests/e2e/error-boundary.test.ts | 8 + tests/e2e/onboarding.test.ts | 224 + tests/e2e/settings-profile.test.ts | 115 + tests/fixtures/github/ghost.jpg | Bin 0 -> 16411 bytes .../fixtures/images/kody-notes/cute-koala.png | Bin 0 -> 306392 bytes .../images/kody-notes/koala-coder.png | Bin 0 -> 406767 bytes .../images/kody-notes/koala-cuddle.png | Bin 0 -> 337477 bytes .../images/kody-notes/koala-eating.png | Bin 0 -> 454917 bytes .../images/kody-notes/koala-mentor.png | Bin 0 -> 309275 bytes .../images/kody-notes/koala-soccer.png | Bin 0 -> 340830 bytes tests/fixtures/images/kody-notes/mountain.png | Bin 0 -> 520758 bytes tests/fixtures/images/notes/0.png | Bin 0 -> 596706 bytes tests/fixtures/images/notes/1.png | Bin 0 -> 690425 bytes tests/fixtures/images/notes/2.png | Bin 0 -> 445199 bytes tests/fixtures/images/notes/3.png | Bin 0 -> 395815 bytes tests/fixtures/images/notes/4.png | Bin 0 -> 322099 bytes tests/fixtures/images/notes/5.png | Bin 0 -> 290108 bytes tests/fixtures/images/notes/6.png | Bin 0 -> 573271 bytes tests/fixtures/images/notes/7.png | Bin 0 -> 503933 bytes tests/fixtures/images/notes/8.png | Bin 0 -> 493499 bytes tests/fixtures/images/notes/9.png | Bin 0 -> 439449 bytes tests/fixtures/images/user/0.jpg | Bin 0 -> 24127 bytes tests/fixtures/images/user/1.jpg | Bin 0 -> 18801 bytes tests/fixtures/images/user/2.jpg | Bin 0 -> 20850 bytes tests/fixtures/images/user/3.jpg | Bin 0 -> 25106 bytes tests/fixtures/images/user/4.jpg | Bin 0 -> 20368 bytes tests/fixtures/images/user/5.jpg | Bin 0 -> 23240 bytes tests/fixtures/images/user/6.jpg | Bin 0 -> 33405 bytes tests/fixtures/images/user/7.jpg | Bin 0 -> 22600 bytes tests/fixtures/images/user/8.jpg | Bin 0 -> 23605 bytes tests/fixtures/images/user/9.jpg | Bin 0 -> 27519 bytes tests/fixtures/images/user/README.md | 4 + tests/fixtures/images/user/kody.png | Bin 0 -> 347294 bytes tests/mocks/README.md | 9 + tests/mocks/github.ts | 181 + tests/mocks/index.ts | 27 + tests/mocks/resend.ts | 22 + tests/mocks/utils.ts | 65 + tests/playwright-utils.ts | 126 + tests/setup/custom-matchers.ts | 174 + tests/setup/db-setup.ts | 28 + tests/setup/global-setup.ts | 22 + tests/setup/setup-test-env.ts | 30 + tests/utils.ts | 33 + tsconfig.json | 27 + types/deps.d.ts | 9 + types/icon-name.d.ts | 3 + types/remix.env.d.ts | 2 + types/reset.d.ts | 2 + vitest.config.ts | 20 + 280 files changed, 36204 insertions(+) create mode 100644 .env.example create mode 100644 .eslintrc.cjs create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/deploy.yml create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc.js create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 COMMUNITY.md create mode 100644 README.md create mode 100644 app/components/confetti.tsx create mode 100644 app/components/error-boundary.tsx create mode 100644 app/components/floating-toolbar.tsx create mode 100644 app/components/forms.tsx create mode 100644 app/components/search-bar.tsx create mode 100644 app/components/spacer.tsx create mode 100644 app/components/spinner.tsx create mode 100644 app/components/toaster.tsx create mode 100644 app/components/ui/README.md create mode 100644 app/components/ui/button.tsx create mode 100644 app/components/ui/checkbox.tsx create mode 100644 app/components/ui/dropdown-menu.tsx create mode 100644 app/components/ui/icon.tsx create mode 100644 app/components/ui/icons/README.md create mode 100644 app/components/ui/icons/name.d.ts create mode 100644 app/components/ui/icons/sprite.svg create mode 100644 app/components/ui/input.tsx create mode 100644 app/components/ui/label.tsx create mode 100644 app/components/ui/status-button.tsx create mode 100644 app/components/ui/textarea.tsx create mode 100644 app/components/ui/tooltip.tsx create mode 100644 app/entry.client.tsx create mode 100644 app/entry.server.tsx create mode 100644 app/root.tsx create mode 100644 app/routes/$.tsx create mode 100644 app/routes/_auth+/auth.$provider.callback.test.ts create mode 100644 app/routes/_auth+/auth.$provider.callback.ts create mode 100644 app/routes/_auth+/auth.$provider.ts create mode 100644 app/routes/_auth+/forgot-password.tsx create mode 100644 app/routes/_auth+/login.tsx create mode 100644 app/routes/_auth+/logout.tsx create mode 100644 app/routes/_auth+/onboarding.tsx create mode 100644 app/routes/_auth+/onboarding_.$provider.tsx create mode 100644 app/routes/_auth+/reset-password.tsx create mode 100644 app/routes/_auth+/signup.tsx create mode 100644 app/routes/_auth+/verify.tsx create mode 100644 app/routes/_marketing+/about.tsx create mode 100644 app/routes/_marketing+/index.tsx create mode 100644 app/routes/_marketing+/logos/docker.png create mode 100644 app/routes/_marketing+/logos/eslint.svg create mode 100644 app/routes/_marketing+/logos/faker.svg create mode 100644 app/routes/_marketing+/logos/fly.svg create mode 100644 app/routes/_marketing+/logos/github.svg create mode 100644 app/routes/_marketing+/logos/logos.ts create mode 100644 app/routes/_marketing+/logos/msw.svg create mode 100644 app/routes/_marketing+/logos/playwright.svg create mode 100644 app/routes/_marketing+/logos/prettier.svg create mode 100644 app/routes/_marketing+/logos/prisma.svg create mode 100644 app/routes/_marketing+/logos/radix.svg create mode 100644 app/routes/_marketing+/logos/react-email.svg create mode 100644 app/routes/_marketing+/logos/remix.png create mode 100644 app/routes/_marketing+/logos/resend.svg create mode 100644 app/routes/_marketing+/logos/sentry.svg create mode 100644 app/routes/_marketing+/logos/shadcn-ui.svg create mode 100644 app/routes/_marketing+/logos/sqlite.svg create mode 100644 app/routes/_marketing+/logos/stars.jpg create mode 100644 app/routes/_marketing+/logos/tailwind.svg create mode 100644 app/routes/_marketing+/logos/testing-library.png create mode 100644 app/routes/_marketing+/logos/typescript.svg create mode 100644 app/routes/_marketing+/logos/vitest.svg create mode 100644 app/routes/_marketing+/logos/zod.svg create mode 100644 app/routes/_marketing+/privacy.tsx create mode 100644 app/routes/_marketing+/support.tsx create mode 100644 app/routes/_marketing+/tos.tsx create mode 100644 app/routes/admin+/cache.tsx create mode 100644 app/routes/admin+/cache_.lru.$cacheKey.ts create mode 100644 app/routes/admin+/cache_.sqlite.$cacheKey.ts create mode 100644 app/routes/admin+/cache_.sqlite.tsx create mode 100644 app/routes/me.tsx create mode 100644 app/routes/resources+/download-user-data.tsx create mode 100644 app/routes/resources+/healthcheck.tsx create mode 100644 app/routes/resources+/note-images.$imageId.tsx create mode 100644 app/routes/resources+/user-images.$imageId.tsx create mode 100644 app/routes/settings+/profile.change-email.tsx create mode 100644 app/routes/settings+/profile.connections.tsx create mode 100644 app/routes/settings+/profile.index.tsx create mode 100644 app/routes/settings+/profile.password.tsx create mode 100644 app/routes/settings+/profile.password_.create.tsx create mode 100644 app/routes/settings+/profile.photo.tsx create mode 100644 app/routes/settings+/profile.tsx create mode 100644 app/routes/settings+/profile.two-factor.disable.tsx create mode 100644 app/routes/settings+/profile.two-factor.index.tsx create mode 100644 app/routes/settings+/profile.two-factor.tsx create mode 100644 app/routes/settings+/profile.two-factor.verify.tsx create mode 100644 app/routes/users+/$username.tsx create mode 100644 app/routes/users+/$username_+/__note-editor.tsx create mode 100644 app/routes/users+/$username_+/notes.$noteId.tsx create mode 100644 app/routes/users+/$username_+/notes.$noteId_.edit.tsx create mode 100644 app/routes/users+/$username_+/notes.index.tsx create mode 100644 app/routes/users+/$username_+/notes.new.tsx create mode 100644 app/routes/users+/$username_+/notes.tsx create mode 100644 app/routes/users+/index.tsx create mode 100644 app/styles/font.css create mode 100644 app/styles/tailwind.css create mode 100644 app/utils/auth.server.ts create mode 100644 app/utils/cache.server.ts create mode 100644 app/utils/client-hints.tsx create mode 100644 app/utils/confetti.server.ts create mode 100644 app/utils/connections.server.ts create mode 100644 app/utils/connections.tsx create mode 100644 app/utils/db.server.ts create mode 100644 app/utils/email.server.ts create mode 100644 app/utils/env.server.ts create mode 100644 app/utils/extended-theme.ts create mode 100644 app/utils/litefs.server.ts create mode 100644 app/utils/misc.error-message.test.ts create mode 100644 app/utils/misc.tsx create mode 100644 app/utils/misc.use-double-check.test.tsx create mode 100644 app/utils/monitoring.client.tsx create mode 100644 app/utils/monitoring.server.ts create mode 100644 app/utils/nonce-provider.ts create mode 100644 app/utils/permissions.ts create mode 100644 app/utils/providers/github.server.ts create mode 100644 app/utils/providers/provider.ts create mode 100644 app/utils/redirect-cookie.server.ts create mode 100644 app/utils/request-info.ts create mode 100644 app/utils/session.server.ts create mode 100644 app/utils/theme.server.ts create mode 100644 app/utils/timing.server.ts create mode 100644 app/utils/toast.server.ts create mode 100644 app/utils/user-validation.ts create mode 100644 app/utils/user.ts create mode 100644 app/utils/verification.server.ts create mode 100644 components.json create mode 100644 fly.toml create mode 100644 index.js create mode 100644 other/.dockerignore create mode 100644 other/Dockerfile create mode 100644 other/README.md create mode 100644 other/build-icons.ts create mode 100644 other/build-server.ts create mode 100644 other/litefs.yml create mode 100644 other/sentry-create-release.js create mode 100644 other/setup-swap.js create mode 100644 other/sly/sly.json create mode 100644 other/sly/transform-icon.ts create mode 100644 other/svg-icons/README.md create mode 100644 other/svg-icons/arrow-left.svg create mode 100644 other/svg-icons/arrow-right.svg create mode 100644 other/svg-icons/avatar.svg create mode 100644 other/svg-icons/camera.svg create mode 100644 other/svg-icons/check.svg create mode 100644 other/svg-icons/clock.svg create mode 100644 other/svg-icons/cross-1.svg create mode 100644 other/svg-icons/dots-horizontal.svg create mode 100644 other/svg-icons/download.svg create mode 100644 other/svg-icons/envelope-closed.svg create mode 100644 other/svg-icons/exit.svg create mode 100644 other/svg-icons/file-text.svg create mode 100644 other/svg-icons/github-logo.svg create mode 100644 other/svg-icons/laptop.svg create mode 100644 other/svg-icons/link-2.svg create mode 100644 other/svg-icons/lock-closed.svg create mode 100644 other/svg-icons/lock-open-1.svg create mode 100644 other/svg-icons/magnifying-glass.svg create mode 100644 other/svg-icons/moon.svg create mode 100644 other/svg-icons/pencil-1.svg create mode 100644 other/svg-icons/pencil-2.svg create mode 100644 other/svg-icons/plus.svg create mode 100644 other/svg-icons/question-mark-circled.svg create mode 100644 other/svg-icons/reset.svg create mode 100644 other/svg-icons/sun.svg create mode 100644 other/svg-icons/trash.svg create mode 100644 other/svg-icons/update.svg create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 playwright.config.ts create mode 100644 postcss.config.js create mode 100644 prisma/migrations/20230914194400_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 prisma/seed.ts create mode 100644 public/favicon.ico create mode 100644 public/favicons/README.md create mode 100644 public/favicons/android-chrome-192x192.png create mode 100644 public/favicons/android-chrome-512x512.png create mode 100644 public/favicons/apple-touch-icon.png create mode 100644 public/favicons/favicon-16x16.png create mode 100644 public/favicons/favicon-32x32.png create mode 100644 public/favicons/favicon.svg create mode 100644 public/favicons/mask-icon.svg create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-200.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-200.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-200italic.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-200italic.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-300.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-300.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-300italic.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-300italic.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-600.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-600.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-600italic.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-600italic.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-700.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-700.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-700italic.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-700italic.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-800.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-800.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-800italic.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-800italic.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-900.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-900.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-900italic.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-900italic.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-italic.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-italic.woff2 create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-regular.woff create mode 100644 public/fonts/nunito-sans/nunito-sans-v12-latin_latin-ext-regular.woff2 create mode 100644 public/img/user.png create mode 100644 public/robots.txt create mode 100644 public/site.webmanifest create mode 100644 remix.config.js create mode 100644 server/dev-server.js create mode 100644 server/index.ts create mode 100644 tailwind.config.ts create mode 100644 tests/db-utils.ts create mode 100644 tests/e2e/2fa.test.ts create mode 100644 tests/e2e/error-boundary.test.ts create mode 100644 tests/e2e/onboarding.test.ts create mode 100644 tests/e2e/settings-profile.test.ts create mode 100644 tests/fixtures/github/ghost.jpg create mode 100644 tests/fixtures/images/kody-notes/cute-koala.png create mode 100644 tests/fixtures/images/kody-notes/koala-coder.png create mode 100644 tests/fixtures/images/kody-notes/koala-cuddle.png create mode 100644 tests/fixtures/images/kody-notes/koala-eating.png create mode 100644 tests/fixtures/images/kody-notes/koala-mentor.png create mode 100644 tests/fixtures/images/kody-notes/koala-soccer.png create mode 100644 tests/fixtures/images/kody-notes/mountain.png create mode 100644 tests/fixtures/images/notes/0.png create mode 100644 tests/fixtures/images/notes/1.png create mode 100644 tests/fixtures/images/notes/2.png create mode 100644 tests/fixtures/images/notes/3.png create mode 100644 tests/fixtures/images/notes/4.png create mode 100644 tests/fixtures/images/notes/5.png create mode 100644 tests/fixtures/images/notes/6.png create mode 100644 tests/fixtures/images/notes/7.png create mode 100644 tests/fixtures/images/notes/8.png create mode 100644 tests/fixtures/images/notes/9.png create mode 100644 tests/fixtures/images/user/0.jpg create mode 100644 tests/fixtures/images/user/1.jpg create mode 100644 tests/fixtures/images/user/2.jpg create mode 100644 tests/fixtures/images/user/3.jpg create mode 100644 tests/fixtures/images/user/4.jpg create mode 100644 tests/fixtures/images/user/5.jpg create mode 100644 tests/fixtures/images/user/6.jpg create mode 100644 tests/fixtures/images/user/7.jpg create mode 100644 tests/fixtures/images/user/8.jpg create mode 100644 tests/fixtures/images/user/9.jpg create mode 100644 tests/fixtures/images/user/README.md create mode 100644 tests/fixtures/images/user/kody.png create mode 100644 tests/mocks/README.md create mode 100644 tests/mocks/github.ts create mode 100644 tests/mocks/index.ts create mode 100644 tests/mocks/resend.ts create mode 100644 tests/mocks/utils.ts create mode 100644 tests/playwright-utils.ts create mode 100644 tests/setup/custom-matchers.ts create mode 100644 tests/setup/db-setup.ts create mode 100644 tests/setup/global-setup.ts create mode 100644 tests/setup/setup-test-env.ts create mode 100644 tests/utils.ts create mode 100644 tsconfig.json create mode 100644 types/deps.d.ts create mode 100644 types/icon-name.d.ts create mode 100644 types/remix.env.d.ts create mode 100644 types/reset.d.ts create mode 100644 vitest.config.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d117323 --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +LITEFS_DIR="/litefs/data" +DATABASE_PATH="./prisma/data.db" +DATABASE_URL="file:./data.db?connection_limit=1" +CACHE_DATABASE_PATH="./other/cache.db" +SESSION_SECRET="super-duper-s3cret" +INTERNAL_COMMAND_TOKEN="some-made-up-token" +RESEND_API_KEY="re_blAh_blaHBlaHblahBLAhBlAh" +SENTRY_DSN="your-dsn" + +# the mocks and some code rely on these two being prefixed with "MOCK_" +# if they aren't then the real github api will be attempted +GITHUB_CLIENT_ID="MOCK_GITHUB_CLIENT_ID" +GITHUB_CLIENT_SECRET="MOCK_GITHUB_CLIENT_SECRET" +GITHUB_TOKEN="MOCK_GITHUB_TOKEN" diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..5dae40c --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,85 @@ +const vitestFiles = ['app/**/__tests__/**/*', 'app/**/*.{spec,test}.*'] +const testFiles = ['**/tests/**', ...vitestFiles] +const appFiles = ['app/**'] + +/** @type {import('@types/eslint').Linter.BaseConfig} */ +module.exports = { + extends: [ + '@remix-run/eslint-config', + '@remix-run/eslint-config/node', + 'prettier', + ], + rules: { + // playwright requires destructuring in fixtures even if you don't use anything 🤷‍♂️ + 'no-empty-pattern': 'off', + '@typescript-eslint/consistent-type-imports': [ + 'warn', + { + prefer: 'type-imports', + disallowTypeAnnotations: true, + fixStyle: 'inline-type-imports', + }, + ], + 'import/no-duplicates': ['warn', { 'prefer-inline': true }], + 'import/consistent-type-specifier-style': ['warn', 'prefer-inline'], + 'import/order': [ + 'warn', + { + alphabetize: { order: 'asc', caseInsensitive: true }, + groups: [ + 'builtin', + 'external', + 'internal', + 'parent', + 'sibling', + 'index', + ], + }, + ], + }, + overrides: [ + { + plugins: ['remix-react-routes'], + files: appFiles, + excludedFiles: testFiles, + rules: { + 'remix-react-routes/use-link-for-routes': 'error', + 'remix-react-routes/require-valid-paths': 'error', + // disable this one because it doesn't appear to work with our + // route convention. Someone should dig deeper into this... + 'remix-react-routes/no-relative-paths': [ + 'off', + { allowLinksToSelf: true }, + ], + 'remix-react-routes/no-urls': 'error', + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: testFiles, + message: 'Do not import test files in app files', + }, + ], + }, + ], + }, + }, + { + extends: ['@remix-run/eslint-config/jest-testing-library'], + files: vitestFiles, + rules: { + 'testing-library/no-await-sync-events': 'off', + 'jest-dom/prefer-in-document': 'off', + }, + // we're using vitest which has a very similar API to jest + // (so the linting plugins work nicely), but it means we have to explicitly + // set the jest version. + settings: { + jest: { + version: 28, + }, + }, + }, + ], +} diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..84a2084 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ + + +## Test Plan + + + +## Checklist + +- [ ] Tests updated +- [ ] Docs updated + +## Screenshots + + diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..7ab7ae7 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,181 @@ +name: 🚀 Deploy +on: + push: + branches: + - main + - dev + pull_request: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + actions: write + contents: read + +jobs: + lint: + name: ⬣ ESLint + runs-on: ubuntu-latest + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v3 + + - name: ⎔ Setup node + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: 📥 Download deps + uses: bahmutov/npm-install@v1 + + - name: 🖼 Build icons + run: npm run build:icons + + - name: 🔬 Lint + run: npm run lint + + typecheck: + name: ʦ TypeScript + runs-on: ubuntu-latest + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v3 + + - name: ⎔ Setup node + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: 📥 Download deps + uses: bahmutov/npm-install@v1 + + - name: 🖼 Build icons + run: npm run build:icons + + - name: 🔎 Type check + run: npm run typecheck --if-present + + vitest: + name: ⚡ Vitest + runs-on: ubuntu-latest + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v3 + + - name: ⎔ Setup node + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: 📥 Download deps + uses: bahmutov/npm-install@v1 + + - name: 🏄 Copy test env vars + run: cp .env.example .env + + - name: 🖼 Build icons + run: npm run build:icons + + - name: ⚡ Run vitest + run: npm run test -- --coverage + + playwright: + name: 🎭 Playwright + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v3 + + - name: 🏄 Copy test env vars + run: cp .env.example .env + + - name: ⎔ Setup node + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: 📥 Download deps + uses: bahmutov/npm-install@v1 + + - name: 📥 Install Playwright Browsers + run: npm run test:e2e:install + + - name: 🛠 Setup Database + run: npx prisma migrate deploy + + - name: 🏦 Cache Database + id: db-cache + uses: actions/cache@v3 + with: + path: prisma/data.db + key: + db-cache-schema_${{ hashFiles('./prisma/schema.prisma') + }}-migrations_${{ hashFiles('./prisma/migrations/*/migration.sql') + }} + + - name: 🌱 Seed Database + if: steps.db-cache.outputs.cache-hit != 'true' + run: npx prisma db seed + env: + MINIMAL_SEED: true + + - name: 🏗 Build + run: npm run build + + - name: 🎭 Playwright tests + run: npx playwright test + + - name: 📊 Upload report + uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 + + # deploy: + # name: 🚀 Deploy + # runs-on: ubuntu-latest + # needs: [lint, typecheck, vitest, playwright] + # # only build/deploy main branch on pushes + # if: + # ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && + # github.event_name == 'push' }} + + # steps: + # - name: ⬇️ Checkout repo + # uses: actions/checkout@v3 + + # - name: 👀 Read app name + # uses: SebRollen/toml-action@v1.0.2 + # id: app_name + # with: + # file: 'fly.toml' + # field: 'app' + + # # move Dockerfile to root + # - name: 🚚 Move Dockerfile + # run: | + # mv ./other/Dockerfile ./Dockerfile + # mv ./other/.dockerignore ./.dockerignore + + # - name: 🎈 Setup Fly + # uses: superfly/flyctl-actions/setup-flyctl@v1.4 + + # - name: 🚀 Deploy Staging + # if: ${{ github.ref == 'refs/heads/dev' }} + # run: + # flyctl deploy --remote-only --build-arg COMMIT_SHA=${{ github.sha }} + # --app ${{ steps.app_name.outputs.value }}-staging + # env: + # FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} + + # - name: 🚀 Deploy Production + # if: ${{ github.ref == 'refs/heads/main' }} + # run: + # flyctl deploy --remote-only --build-arg COMMIT_SHA=${{ github.sha }} + # env: + # FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f9d5ad8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +node_modules +.DS_store + +/build +/public/build +/server-build +.env + +/prisma/data.db +/prisma/data.db-journal +/tests/prisma + +/test-results/ +/playwright-report/ +/playwright/.cache/ +/tests/fixtures/email/ +/coverage + +/other/cache.db + +# Easy way to create temporary files/folders that won't accidentally be added to git +*.local.* diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..668efa1 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +legacy-peer-deps=true +registry=https://registry.npmjs.org/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f022d02 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,15 @@ +node_modules + +/build +/public/build +/server-build +.env + +/test-results/ +/playwright-report/ +/playwright/.cache/ +/tests/fixtures/email/*.json +/coverage +/prisma/migrations + +package-lock.json diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..b0ffa1c --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,30 @@ +/** @type {import("prettier").Options} */ +export default { + arrowParens: 'avoid', + bracketSameLine: false, + bracketSpacing: true, + embeddedLanguageFormatting: 'auto', + endOfLine: 'lf', + htmlWhitespaceSensitivity: 'css', + insertPragma: false, + jsxSingleQuote: false, + printWidth: 80, + proseWrap: 'always', + quoteProps: 'as-needed', + requirePragma: false, + semi: false, + singleAttributePerLine: false, + singleQuote: true, + tabWidth: 2, + trailingComma: 'all', + useTabs: true, + overrides: [ + { + files: ['**/*.json'], + options: { + useTabs: false, + }, + }, + ], + plugins: ['prettier-plugin-tailwindcss'], +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..7619ac2 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + "recommendations": [ + "bradlc.vscode-tailwindcss", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "prisma.prisma", + "qwtel.sqlite-viewer", + "yoavbls.pretty-ts-errors", + "github.vscode-github-actions" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..374cf10 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "typescript.preferences.autoImportFileExcludePatterns": [ + "@remix-run/server-runtime", + "@remix-run/router", + "express", + "@radix-ui/**", + "@react-email/**", + "react-router-dom", + "react-router", + "stream/consumers", + "node:stream/consumers", + "node:test", + "console", + "node:console" + ], + "workbench.editorAssociations": { + "*.db": "sqlite-viewer.view" + } +} diff --git a/COMMUNITY.md b/COMMUNITY.md new file mode 100644 index 0000000..269d108 --- /dev/null +++ b/COMMUNITY.md @@ -0,0 +1,12 @@ +# Community + +Here you can find useful learning resources and tools built and maintained by +the community, such as libraries, examples, articles, and videos. + +## Learning resources + +### Videos + +- **Dark Mode Toggling using Client-preference cookies** by + [@rajeshdavidbabu](https://github.com/rajeshdavidbabu) - Youtube + [link](https://www.youtube.com/watch?v=UND-kib_iw4) diff --git a/README.md b/README.md new file mode 100644 index 0000000..b554c2b --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +
+

The Epic Stack 🚀

+ + Ditch analysis paralysis and start shipping Epic Web apps. + +

+ This is an opinionated project starter and reference that allows teams to + ship their ideas to production faster and on a more stable foundation based + on the experience of Kent C. Dodds and + contributors. +

+
+ +```sh +npx create-remix@latest --typescript --install --template epicweb-dev/epic-stack +``` + +[![The Epic Stack](https://github-production-user-asset-6210df.s3.amazonaws.com/1500684/246885449-1b00286c-aa3d-44b2-9ef2-04f694eb3592.png)](https://www.epicweb.dev/epic-stack) + +[The Epic Stack](https://www.epicweb.dev/epic-stack) + +
+ +## Watch Kent's Introduction to The Epic Stack + +[![screenshot of a YouTube video](https://github-production-user-asset-6210df.s3.amazonaws.com/1500684/242088051-6beafa78-41c6-47e1-b999-08d3d3e5cb57.png)](https://www.youtube.com/watch?v=yMK5SVRASxM) + +["The Epic Stack" by Kent C. Dodds at #RemixConf 2023 💿](https://www.youtube.com/watch?v=yMK5SVRASxM) + +## Docs + +[Read the docs](https://github.com/epicweb-dev/epic-stack/blob/main/docs) +(please 🙏). + +## Support + +- 🆘 Join the + [discussion on GitHub](https://github.com/epicweb-dev/epic-stack/discussions) + and the [KCD Community on Discord](https://kcd.im/discord). +- 💡 Create an + [idea discussion](https://github.com/epicweb-dev/epic-stack/discussions/new?category=ideas) + for suggestions. +- 🐛 Open a [GitHub issue](https://github.com/epicweb-dev/epic-stack/issues) to + report a bug. + +## Branding + +Want to talk about the Epic Stack in a blog post or talk? Great! Here are some +assets you can use in your material: +[EpicWeb.dev/brand](https://epicweb.dev/brand) + +## Thanks + +You rock 🪨 diff --git a/app/components/confetti.tsx b/app/components/confetti.tsx new file mode 100644 index 0000000..69fbece --- /dev/null +++ b/app/components/confetti.tsx @@ -0,0 +1,21 @@ +import { Index as ConfettiShower } from 'confetti-react' +import { ClientOnly } from 'remix-utils' + +export function Confetti({ id }: { id?: string | null }) { + if (!id) return null + + return ( + + {() => ( + + )} + + ) +} diff --git a/app/components/error-boundary.tsx b/app/components/error-boundary.tsx new file mode 100644 index 0000000..e0f7f43 --- /dev/null +++ b/app/components/error-boundary.tsx @@ -0,0 +1,45 @@ +import { + isRouteErrorResponse, + useParams, + useRouteError, +} from '@remix-run/react' +import { type ErrorResponse } from '@remix-run/router' +import { getErrorMessage } from '#app/utils/misc.tsx' + +type StatusHandler = (info: { + error: ErrorResponse + params: Record +}) => JSX.Element | null + +export function GeneralErrorBoundary({ + defaultStatusHandler = ({ error }) => ( +

+ {error.status} {error.data} +

+ ), + statusHandlers, + unexpectedErrorHandler = error =>

{getErrorMessage(error)}

, +}: { + defaultStatusHandler?: StatusHandler + statusHandlers?: Record + unexpectedErrorHandler?: (error: unknown) => JSX.Element | null +}) { + const error = useRouteError() + const params = useParams() + + if (typeof document !== 'undefined') { + console.error(error) + } + + return ( +
+ {isRouteErrorResponse(error) + ? (statusHandlers?.[error.status] ?? defaultStatusHandler)({ + // @ts-expect-error, pretty sure this is a bug in Remix + error, + params, + }) + : unexpectedErrorHandler(error)} +
+ ) +} diff --git a/app/components/floating-toolbar.tsx b/app/components/floating-toolbar.tsx new file mode 100644 index 0000000..41b5be0 --- /dev/null +++ b/app/components/floating-toolbar.tsx @@ -0,0 +1,2 @@ +export const floatingToolbarClassName = + 'absolute bottom-3 left-3 right-3 flex items-center gap-2 rounded-lg bg-muted/80 p-4 pl-5 shadow-xl shadow-accent backdrop-blur-sm md:gap-4 md:pl-7 justify-end' diff --git a/app/components/forms.tsx b/app/components/forms.tsx new file mode 100644 index 0000000..0d362fc --- /dev/null +++ b/app/components/forms.tsx @@ -0,0 +1,148 @@ +import { useInputEvent } from '@conform-to/react' +import React, { useId, useRef } from 'react' +import { Checkbox, type CheckboxProps } from './ui/checkbox.tsx' +import { Input } from './ui/input.tsx' +import { Label } from './ui/label.tsx' +import { Textarea } from './ui/textarea.tsx' + +export type ListOfErrors = Array | null | undefined + +export function ErrorList({ + id, + errors, +}: { + errors?: ListOfErrors + id?: string +}) { + const errorsToRender = errors?.filter(Boolean) + if (!errorsToRender?.length) return null + return ( +
    + {errorsToRender.map(e => ( +
  • + {e} +
  • + ))} +
+ ) +} + +export function Field({ + labelProps, + inputProps, + errors, + className, +}: { + labelProps: React.LabelHTMLAttributes + inputProps: React.InputHTMLAttributes + errors?: ListOfErrors + className?: string +}) { + const fallbackId = useId() + const id = inputProps.id ?? fallbackId + const errorId = errors?.length ? `${id}-error` : undefined + return ( +
+
+ ) +} + +export function TextareaField({ + labelProps, + textareaProps, + errors, + className, +}: { + labelProps: React.LabelHTMLAttributes + textareaProps: React.InputHTMLAttributes + errors?: ListOfErrors + className?: string +}) { + const fallbackId = useId() + const id = textareaProps.id ?? textareaProps.name ?? fallbackId + const errorId = errors?.length ? `${id}-error` : undefined + return ( +
+