From 50ba37a27fb45f6cc1009ce8737a0263f06f7fab Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Thu, 7 May 2026 15:12:33 +0200 Subject: [PATCH] v5.1.0 (#2970) * chore(release): v5.1.0 * feat: implement resume thumbnails * fix: remove unused mcp tools * docs: fix formatting of docs --- .dockerignore | 30 +- .env.example | 57 +- .gitattributes | 1 - .github/.well-known/funding.json | 132 +- .github/workflows/autofix.yml | 24 +- .gitignore | 66 +- .ncurc.cjs | 18 +- .vite-hooks/_/commit-msg | 71 + .vite-hooks/_/pre-commit | 71 + .vite-hooks/pre-commit | 1 - .vscode/extensions.json | 8 +- .vscode/settings.json | 69 +- AGENTS.md | 131 - CLAUDE.md | 1 - Dockerfile | 84 +- Dockerfile.dev | 21 - README.md | 35 +- apps/web/components.json | 25 + apps/web/lingui.config.ts | 77 + {locales => apps/web/locales}/af-ZA.po | 719 +- {locales => apps/web/locales}/am-ET.po | 719 +- {locales => apps/web/locales}/ar-SA.po | 719 +- {locales => apps/web/locales}/az-AZ.po | 719 +- {locales => apps/web/locales}/bg-BG.po | 719 +- {locales => apps/web/locales}/bn-BD.po | 719 +- {locales => apps/web/locales}/ca-ES.po | 719 +- {locales => apps/web/locales}/cs-CZ.po | 719 +- {locales => apps/web/locales}/da-DK.po | 719 +- {locales => apps/web/locales}/de-DE.po | 719 +- {locales => apps/web/locales}/el-GR.po | 719 +- {locales => apps/web/locales}/en-GB.po | 719 +- {locales => apps/web/locales}/en-US.po | 710 +- {locales => apps/web/locales}/es-ES.po | 719 +- {locales => apps/web/locales}/fa-IR.po | 719 +- {locales => apps/web/locales}/fi-FI.po | 719 +- {locales => apps/web/locales}/fr-FR.po | 719 +- {locales => apps/web/locales}/he-IL.po | 719 +- {locales => apps/web/locales}/hi-IN.po | 719 +- {locales => apps/web/locales}/hu-HU.po | 719 +- {locales => apps/web/locales}/id-ID.po | 719 +- {locales => apps/web/locales}/it-IT.po | 719 +- {locales => apps/web/locales}/ja-JP.po | 719 +- {locales => apps/web/locales}/km-KH.po | 719 +- {locales => apps/web/locales}/kn-IN.po | 719 +- {locales => apps/web/locales}/ko-KR.po | 719 +- {locales => apps/web/locales}/lt-LT.po | 719 +- {locales => apps/web/locales}/lv-LV.po | 719 +- {locales => apps/web/locales}/ml-IN.po | 719 +- {locales => apps/web/locales}/mr-IN.po | 719 +- {locales => apps/web/locales}/ms-MY.po | 719 +- {locales => apps/web/locales}/ne-NP.po | 719 +- {locales => apps/web/locales}/nl-NL.po | 719 +- {locales => apps/web/locales}/no-NO.po | 719 +- {locales => apps/web/locales}/or-IN.po | 719 +- {locales => apps/web/locales}/pl-PL.po | 719 +- {locales => apps/web/locales}/pt-BR.po | 719 +- {locales => apps/web/locales}/pt-PT.po | 719 +- {locales => apps/web/locales}/ro-RO.po | 719 +- {locales => apps/web/locales}/ru-RU.po | 719 +- {locales => apps/web/locales}/sk-SK.po | 719 +- {locales => apps/web/locales}/sl-SI.po | 719 +- {locales => apps/web/locales}/sq-AL.po | 719 +- {locales => apps/web/locales}/sr-SP.po | 719 +- {locales => apps/web/locales}/sv-SE.po | 719 +- {locales => apps/web/locales}/ta-IN.po | 719 +- {locales => apps/web/locales}/te-IN.po | 719 +- {locales => apps/web/locales}/th-TH.po | 719 +- {locales => apps/web/locales}/tr-TR.po | 719 +- {locales => apps/web/locales}/uk-UA.po | 719 +- {locales => apps/web/locales}/uz-UZ.po | 719 +- {locales => apps/web/locales}/vi-VN.po | 719 +- {locales => apps/web/locales}/zh-CN.po | 719 +- {locales => apps/web/locales}/zh-TW.po | 719 +- {locales => apps/web/locales}/zu-ZA.po | 704 +- apps/web/package.json | 111 + apps/web/plugins/1.migrate.ts | 40 + .../web/public}/apple-touch-icon-180x180.png | Bin {public => apps/web/public}/favicon.ico | Bin {public => apps/web/public}/favicon.svg | 0 .../public/fonts/fira-sans-condensed-500.ttf | Bin 0 -> 303960 bytes apps/web/public/fonts/ibm-plex-serif-400.ttf | Bin 0 -> 153708 bytes apps/web/public/fonts/ibm-plex-serif-600.ttf | Bin 0 -> 154976 bytes apps/web/public/funding.json | 68 + {public => apps/web/public}/icon/dark.svg | 0 {public => apps/web/public}/icon/light.svg | 0 {public => apps/web/public}/logo/dark.svg | 0 {public => apps/web/public}/logo/light.svg | 0 apps/web/public/manifest.webmanifest | 98 + .../web/public}/maskable-icon-512x512.png | Bin .../web/public}/opengraph/banner.jpg | Bin .../web/public}/opengraph/features.jpg | Bin .../web/public}/opengraph/logo.svg | 0 .../web/public}/photos/sample-picture.jpg | Bin {public => apps/web/public}/pwa-192x192.png | Bin {public => apps/web/public}/pwa-512x512.png | Bin {public => apps/web/public}/pwa-64x64.png | Bin {public => apps/web/public}/robots.txt | 0 .../screenshots/mobile/1-landing-page.webp | Bin .../mobile/2-resume-dashboard.webp | Bin .../screenshots/mobile/3-builder-screen.webp | Bin .../mobile/4-template-gallery.webp | Bin .../screenshots/web/1-landing-page.webp | Bin .../screenshots/web/2-resume-dashboard.webp | Bin .../screenshots/web/3-builder-screen.webp | Bin .../screenshots/web/4-template-gallery.webp | Bin {public => apps/web/public}/sitemap.xml | 0 .../web/public}/sounds/switch-off.mp3 | Bin .../web/public}/sounds/switch-on.mp3 | Bin .../web/public}/templates/jpg/azurill.jpg | Bin .../web/public}/templates/jpg/bronzor.jpg | Bin .../web/public}/templates/jpg/chikorita.jpg | Bin .../web/public}/templates/jpg/ditgar.jpg | Bin .../web/public}/templates/jpg/ditto.jpg | Bin .../web/public}/templates/jpg/gengar.jpg | Bin .../web/public}/templates/jpg/glalie.jpg | Bin .../web/public}/templates/jpg/kakuna.jpg | Bin .../web/public}/templates/jpg/lapras.jpg | Bin .../web/public}/templates/jpg/leafish.jpg | Bin .../web/public}/templates/jpg/meowth.jpg | Bin .../web/public}/templates/jpg/onyx.jpg | Bin .../web/public}/templates/jpg/pikachu.jpg | Bin .../web/public}/templates/jpg/rhyhorn.jpg | Bin .../web/public}/templates/pdf/azurill.pdf | Bin .../web/public}/templates/pdf/bronzor.pdf | Bin .../web/public}/templates/pdf/chikorita.pdf | Bin .../web/public}/templates/pdf/ditgar.pdf | Bin .../web/public}/templates/pdf/ditto.pdf | Bin .../web/public}/templates/pdf/gengar.pdf | Bin .../web/public}/templates/pdf/glalie.pdf | Bin .../web/public}/templates/pdf/kakuna.pdf | Bin .../web/public}/templates/pdf/lapras.pdf | Bin .../web/public}/templates/pdf/leafish.pdf | Bin .../web/public}/templates/pdf/meowth.pdf | Bin .../web/public}/templates/pdf/onyx.pdf | Bin .../web/public}/templates/pdf/pikachu.pdf | Bin .../web/public}/templates/pdf/rhyhorn.pdf | Bin .../web/public}/videos/timelapse.mp4 | Bin .../src/components/animation/comet-card.tsx | 86 + .../web/src/components/animation/count-up.tsx | 128 + .../src/components/animation/spotlight.tsx | 106 + .../src/components/animation/text-mask.tsx | 116 + .../src/components/command-palette/index.tsx | 143 + .../components/command-palette/pages/base.tsx | 22 + .../command-palette/pages/navigation.tsx | 115 + .../pages/preferences/index.tsx | 30 + .../pages/preferences/language.tsx | 26 + .../pages/preferences/theme.tsx | 30 + .../command-palette/pages/resumes.tsx | 84 + .../src/components/command-palette/store.ts | 59 + apps/web/src/components/input/chip-input.tsx | 397 + .../web/src/components/input/color-picker.tsx | 108 + .../components/input/github-stars-button.tsx | 31 + apps/web/src/components/input/icon-picker.tsx | 126 + apps/web/src/components/input/rich-input.tsx | 788 + apps/web/src/components/input/url-input.tsx | 99 + .../layout/breakpoint-indicator.tsx | 38 + .../src/components/layout/error-screen.tsx | 27 + .../src/components/layout/loading-screen.tsx | 13 + .../components/layout/not-found-screen.tsx | 28 + apps/web/src/components/level/combobox.tsx | 34 + apps/web/src/components/level/display.tsx | 63 + apps/web/src/components/locale/combobox.tsx | 36 + apps/web/src/components/resume/pdf-canvas.tsx | 198 + .../src/components/resume/preview.browser.tsx | 204 + .../src/components/resume/preview.shared.tsx | 70 + apps/web/src/components/resume/preview.tsx | 22 + apps/web/src/components/resume/use-resume.ts | 369 + apps/web/src/components/theme/combobox.tsx | 29 + apps/web/src/components/theme/provider.tsx | 51 + .../src/components/theme/toggle-button.tsx | 65 + .../src/components/typography/combobox.tsx | 78 + .../components/typography/font-display.tsx | 44 + apps/web/src/components/ui/combobox.tsx | 208 + apps/web/src/components/ui/copyright.tsx | 50 + .../web/src/components/user/dropdown-menu.tsx | 127 + apps/web/src/dialogs/api-key/create.tsx | 241 + apps/web/src/dialogs/auth/change-password.tsx | 192 + .../src/dialogs/auth/disable-two-factor.tsx | 140 + .../src/dialogs/auth/enable-two-factor.tsx | 377 + apps/web/src/dialogs/manager.tsx | 77 + apps/web/src/dialogs/resume/import.tsx | 368 + apps/web/src/dialogs/resume/index.tsx | 438 + .../web/src/dialogs/resume/sections/award.tsx | 230 + .../dialogs/resume/sections/certification.tsx | 210 + .../dialogs/resume/sections/cover-letter.tsx | 177 + .../src/dialogs/resume/sections/custom.tsx | 306 + .../src/dialogs/resume/sections/education.tsx | 222 + .../dialogs/resume/sections/experience.tsx | 382 + .../src/dialogs/resume/sections/interest.tsx | 249 + .../src/dialogs/resume/sections/language.tsx | 182 + .../src/dialogs/resume/sections/profile.tsx | 302 + .../src/dialogs/resume/sections/project.tsx | 210 + .../dialogs/resume/sections/publication.tsx | 212 + .../src/dialogs/resume/sections/reference.tsx | 210 + .../web/src/dialogs/resume/sections/skill.tsx | 287 + .../dialogs/resume/sections/summary-item.tsx | 162 + .../src/dialogs/resume/sections/volunteer.tsx | 212 + apps/web/src/dialogs/resume/template/data.ts | 112 + .../src/dialogs/resume/template/gallery.tsx | 124 + apps/web/src/dialogs/store.ts | 212 + apps/web/src/hooks/use-confirm.tsx | 98 + apps/web/src/hooks/use-controlled-state.tsx | 32 + apps/web/src/hooks/use-form-blocker.tsx | 69 + apps/web/src/hooks/use-mobile.tsx | 20 + apps/web/src/hooks/use-prompt.tsx | 142 + apps/web/src/index.css | 1 + apps/web/src/libs/auth/client.ts | 40 + apps/web/src/libs/auth/session.ts | 25 + apps/web/src/libs/error-message.ts | 36 + apps/web/src/libs/locale.ts | 200 + apps/web/src/libs/orpc/client.ts | 55 + apps/web/src/libs/pwa.ts | 144 + apps/web/src/libs/query/client.ts | 38 + apps/web/src/libs/resume/make-section-item.ts | 16 + apps/web/src/libs/resume/move-item.ts | 280 + apps/web/src/libs/resume/pdf-document.tsx | 38 + apps/web/src/libs/resume/section-actions.ts | 42 + .../src/libs/resume/section-title-locale.ts | 49 + apps/web/src/libs/resume/section-title.ts | 37 + apps/web/src/libs/resume/section.tsx | 172 + apps/web/src/libs/tanstack-form.tsx | 111 + {src/utils => apps/web/src/libs}/theme.ts | 35 +- apps/web/src/routeTree.gen.ts | 1004 + apps/web/src/router.tsx | 45 + apps/web/src/routes/$username/$slug.tsx | 41 + .../$username/-components/public-resume.tsx | 72 + .../web/src}/routes/[.]well-known/$.ts | 12 +- .../[.]well-known/mcp/server-card[.]json.ts | 23 +- .../oauth-authorization-server.$.ts | 13 +- .../oauth-authorization-server.ts | 13 +- .../oauth-protected-resource.$.ts | 24 + .../[.]well-known/oauth-protected-resource.ts | 24 + .../[.]well-known/openid-configuration.ts | 13 +- apps/web/src/routes/__root.tsx | 143 + .../web/src/routes/_home/-sections/donate.tsx | 243 + apps/web/src/routes/_home/-sections/faq.tsx | 129 + .../src/routes/_home/-sections/features.tsx | 202 + .../web/src/routes/_home/-sections/footer.tsx | 153 + .../web/src/routes/_home/-sections/header.tsx | 85 + apps/web/src/routes/_home/-sections/hero.tsx | 153 + .../src/routes/_home/-sections/prefooter.tsx | 40 + .../src/routes/_home/-sections/statistics.tsx | 105 + .../src/routes/_home/-sections/templates.tsx | 116 + .../routes/_home/-sections/testimonials.tsx | 144 + {src => apps/web/src}/routes/_home/index.tsx | 37 +- apps/web/src/routes/_home/route.tsx | 23 + apps/web/src/routes/api/auth.$.ts | 131 + apps/web/src/routes/api/health.ts | 113 + apps/web/src/routes/api/openapi.$.ts | 85 + apps/web/src/routes/api/rpc.$.ts | 33 + apps/web/src/routes/api/uploads/$userId.$.ts | 6 + .../routes/auth/-components/social-auth.tsx | 189 + apps/web/src/routes/auth/forgot-password.tsx | 148 + apps/web/src/routes/auth/index.tsx | 8 + apps/web/src/routes/auth/login.tsx | 243 + apps/web/src/routes/auth/oauth.ts | 103 + apps/web/src/routes/auth/register.tsx | 288 + apps/web/src/routes/auth/reset-password.tsx | 139 + apps/web/src/routes/auth/resume-password.tsx | 169 + apps/web/src/routes/auth/route.tsx | 16 + .../web/src/routes/auth/verify-2fa-backup.tsx | 118 + apps/web/src/routes/auth/verify-2fa.tsx | 133 + .../builder/$resumeId/-components/dock.tsx | 174 + .../builder/$resumeId/-components/edge.tsx | 19 + .../builder/$resumeId/-components/header.tsx | 187 + .../$resumeId/-components/preview-page.tsx | 36 + .../builder/$resumeId/-sidebar/left/index.tsx | 122 + .../-sidebar/left/sections/awards.tsx | 36 + .../-sidebar/left/sections/basics.tsx | 194 + .../-sidebar/left/sections/certifications.tsx | 45 + .../-sidebar/left/sections/custom-fields.tsx | 184 + .../-sidebar/left/sections/custom.tsx | 311 + .../-sidebar/left/sections/education.tsx | 36 + .../-sidebar/left/sections/experience.tsx | 45 + .../-sidebar/left/sections/interests.tsx | 36 + .../-sidebar/left/sections/languages.tsx | 36 + .../-sidebar/left/sections/picture.tsx | 554 + .../-sidebar/left/sections/profiles.tsx | 36 + .../-sidebar/left/sections/projects.tsx | 41 + .../-sidebar/left/sections/publications.tsx | 36 + .../-sidebar/left/sections/references.tsx | 36 + .../-sidebar/left/sections/skills.tsx | 36 + .../-sidebar/left/sections/summary.tsx | 21 + .../-sidebar/left/sections/volunteer.tsx | 42 + .../-sidebar/left/shared/section-base.tsx | 74 + .../-sidebar/left/shared/section-item.tsx | 348 + .../-sidebar/left/shared/section-menu.tsx | 175 + .../$resumeId/-sidebar/right/index.tsx | 102 + .../-sidebar/right/sections/design.tsx | 344 + .../-sidebar/right/sections/export.tsx | 119 + .../-sidebar/right/sections/information.tsx | 106 + .../-sidebar/right/sections/layout/index.tsx | 115 + .../-sidebar/right/sections/layout/pages.tsx | 433 + .../-sidebar/right/sections/notes.tsx | 44 + .../-sidebar/right/sections/page.tsx | 283 + .../right/sections/resume-analysis.tsx | 283 + .../-sidebar/right/sections/sharing.tsx | 176 + .../-sidebar/right/sections/statistics.tsx | 75 + .../-sidebar/right/sections/template.tsx | 62 + .../-sidebar/right/sections/typography.tsx | 332 + .../-sidebar/right/shared/section-base.tsx | 51 + .../builder/$resumeId/-store/section.ts | 55 + .../builder/$resumeId/-store/sidebar.ts | 139 + .../src/routes/builder/$resumeId/index.tsx | 6 + .../src/routes/builder/$resumeId/route.tsx | 181 + .../routes/dashboard/-components/functions.ts | 12 +- .../routes/dashboard/-components/header.tsx | 19 + .../routes/dashboard/-components/sidebar.tsx | 199 + apps/web/src/routes/dashboard/index.tsx | 7 + .../resumes/-components/cards/base-card.tsx | 40 + .../resumes/-components/cards/create-card.tsx | 20 + .../resumes/-components/cards/import-card.tsx | 20 + .../resumes/-components/cards/resume-card.tsx | 62 + .../cards/resume-thumbnail.shared.ts | 26 + .../-components/cards/resume-thumbnail.tsx | 189 + .../resumes/-components/grid-view.tsx | 57 + .../resumes/-components/list-view.tsx | 132 + .../-components/menus/context-menu.tsx | 132 + .../-components/menus/dropdown-menu.tsx | 130 + .../src/routes/dashboard/resumes/index.tsx | 122 + apps/web/src/routes/dashboard/route.tsx | 36 + .../web/src}/routes/dashboard/settings/ai.tsx | 6 +- .../routes/dashboard/settings/api-keys.tsx | 164 + .../authentication/-components/hooks.tsx | 166 + .../authentication/-components/passkeys.tsx | 172 + .../authentication/-components/password.tsx | 73 + .../-components/social-provider.tsx | 84 + .../authentication/-components/two-factor.tsx | 76 + .../settings/authentication/index.tsx | 50 + .../routes/dashboard/settings/danger-zone.tsx | 107 + .../integrations/-components/ai-section.tsx | 299 + .../dashboard/settings/integrations/route.tsx | 35 + .../routes/dashboard/settings/job-search.tsx | 6 +- .../routes/dashboard/settings/preferences.tsx | 58 + .../src/routes/dashboard/settings/profile.tsx | 280 + .../routes/mcp/-helpers/mcp-server-card.ts | 344 + .../src/routes/mcp/-helpers/mcp-tool-names.ts | 18 + apps/web/src/routes/mcp/-helpers/prompts.ts | 198 + apps/web/src/routes/mcp/-helpers/resources.ts | 70 + .../routes/mcp/-helpers/tool-annotations.ts | 86 + apps/web/src/routes/mcp/-helpers/tools.ts | 519 + apps/web/src/routes/mcp/index.ts | 133 + apps/web/src/routes/schema[.]json.ts | 28 + apps/web/src/routes/templates/$.tsx | 28 + apps/web/src/routes/uploads/$userId.$.tsx | 195 + apps/web/src/server.ts | 10 + apps/web/tsconfig.json | 27 + apps/web/vite-env.d.ts | 9 + apps/web/vite.config.ts | 73 + apps/web/vitest.config.ts | 11 + biome.json | 95 + commitlint.config.cjs | 3 + components.json | 23 - compose.dev.yml | 108 - compose.yml | 52 +- crowdin.yml | 4 +- docs/changelog/index.mdx | 48 +- docs/contributing/architecture.mdx | 7 +- docs/contributing/development.mdx | 13 +- docs/docs.json | 295 +- docs/getting-started/quickstart.mdx | 51 +- docs/guides/json-resume-schema.mdx | 4306 ++-- docs/guides/using-custom-css.mdx | 273 - docs/guides/using-the-mcp-server.mdx | 7 - .../guides/using-custom-css/screenshot-1.webp | Bin 137166 -> 0 bytes docs/legal/privacy-policy.mdx | 9 +- docs/legal/terms-of-service.mdx | 6 +- docs/self-hosting/docker.mdx | 98 +- docs/self-hosting/examples.mdx | 71 +- docs/self-hosting/migration.mdx | 8 +- docs/spec.json | 19876 +++------------- drizzle.config.ts | 10 - knip.json | 33 +- lefthook.yml | 15 + lingui.config.ts | 77 - .../snapshot.json | 3296 +-- .../snapshot.json | 3786 +-- .../snapshot.json | 3702 +-- .../20260305091016_closed_pixie/snapshot.json | 3728 +-- .../snapshot.json | 3754 +-- .../snapshot.json | 3838 +-- .../snapshot.json | 6094 ++--- .../snapshot.json | 6224 ++--- .../snapshot.json | 6468 ++--- .../snapshot.json | 6510 ++--- .../migration.sql | 56 + .../snapshot.json | 3257 +++ .../20260507055641_yellow_morg/migration.sql | 1 + .../20260507055641_yellow_morg/snapshot.json | 3270 +++ netlify.toml | 2 - package.json | 242 +- packages/ai/package.json | 36 + packages/ai/src/markdown-raw.d.ts | 4 + packages/ai/src/prompts.ts | 17 + .../ai/src}/prompts/analyze-resume-system.md | 0 .../ai/src}/prompts/chat-system.md | 0 .../ai/src}/prompts/docx-parser-system.md | 0 .../ai/src}/prompts/docx-parser-user.md | 0 .../ai/src}/prompts/pdf-parser-system.md | 0 .../ai/src}/prompts/pdf-parser-user.md | 0 packages/ai/src/resume/extraction-template.ts | 132 + packages/ai/src/resume/sanitize.ts | 262 + packages/ai/src/store.ts | 88 + .../ai/src}/tools/patch-resume.ts | 21 +- .../ai => packages/ai/src}/types.ts | 12 +- packages/ai/tsconfig.json | 6 + packages/ai/vitest.config.ts | 7 + packages/api/.gitignore | 34 + packages/api/package.json | 55 + packages/api/src/context.ts | 99 + packages/api/src/dto/resume.ts | 104 + packages/api/src/helpers/resume-access.ts | 36 + .../api/src/middleware/rate-limit/index.ts | 118 + packages/api/src/routers/ai.ts | 218 + packages/api/src/routers/auth.ts | 37 + packages/api/src/routers/flags.ts | 25 + packages/api/src/routers/index.ts | 15 + packages/api/src/routers/resume.ts | 399 + packages/api/src/routers/statistics.ts | 63 + packages/api/src/routers/storage.ts | 110 + packages/api/src/services/ai.ts | 273 + packages/api/src/services/auth.ts | 48 + packages/api/src/services/flags.ts | 13 + .../api/src/services/resume-access-policy.ts | 78 + packages/api/src/services/resume.ts | 451 + packages/api/src/services/statistics.ts | 127 + packages/api/src/services/storage.ts | 396 + packages/api/tsconfig.json | 7 + packages/api/vitest.config.ts | 7 + packages/auth/.gitignore | 34 + packages/auth/package.json | 43 + packages/auth/src/config.ts | 418 + packages/auth/src/functions.ts | 8 + .../auth => packages/auth/src}/types.ts | 7 +- packages/auth/tsconfig.json | 7 + packages/auth/vitest.config.ts | 7 + packages/config/package.json | 11 + packages/config/tsconfig.base.json | 24 + packages/config/vitest.config.ts | 7 + packages/db/.gitignore | 35 + packages/db/drizzle.config.ts | 10 + packages/db/package.json | 38 + packages/db/src/client.ts | 32 + packages/db/src/relations.ts | 129 + packages/db/src/schema/auth.ts | 357 + packages/db/src/schema/index.ts | 2 + packages/db/src/schema/resume.ts | 92 + packages/db/tsconfig.json | 3 + packages/db/vitest.config.ts | 7 + packages/email/package.json | 32 + packages/email/src/templates/auth.tsx | 187 + .../email/src}/templates/reset-password.tsx | 10 +- .../src/templates/verify-email-change.tsx | 19 + .../email/src}/templates/verify-email.tsx | 10 +- packages/email/src/transport.ts | 73 + packages/email/tsconfig.json | 7 + packages/email/vitest.config.ts | 8 + packages/env/package.json | 26 + packages/env/src/server.ts | 71 + packages/env/tsconfig.json | 3 + packages/env/vitest.config.ts | 7 + packages/fonts/package.json | 22 + packages/fonts/src/index.ts | 221 + packages/fonts/src/webfontlist.json | 7513 ++++++ packages/fonts/tsconfig.json | 3 + packages/fonts/vitest.config.ts | 7 + packages/import/package.json | 28 + packages/import/src/json-resume.tsx | 450 + packages/import/src/reactive-resume-json.tsx | 79 + .../import/src/reactive-resume-v4-json.tsx | 780 + packages/import/tsconfig.json | 7 + packages/import/vitest.config.ts | 8 + packages/pdf/package.json | 36 + packages/pdf/src/context.tsx | 28 + packages/pdf/src/document.tsx | 36 + packages/pdf/src/hooks/use-register-fonts.ts | 62 + packages/pdf/src/section-title.ts | 100 + packages/pdf/src/svg-raw.d.ts | 4 + .../pdf/src/templates/azurill/AzurillPage.tsx | 366 + .../pdf/src/templates/bronzor/BronzorPage.tsx | 285 + .../src/templates/chikorita/ChikoritaPage.tsx | 359 + .../pdf/src/templates/ditgar/DitgarPage.tsx | 337 + .../pdf/src/templates/ditto/DittoPage.tsx | 372 + .../pdf/src/templates/gengar/GengarPage.tsx | 380 + .../pdf/src/templates/glalie/GlaliePage.tsx | 295 + packages/pdf/src/templates/index.ts | 54 + .../pdf/src/templates/kakuna/KakunaPage.tsx | 306 + .../pdf/src/templates/lapras/LaprasPage.tsx | 254 + .../pdf/src/templates/leafish/LeafishPage.tsx | 280 + .../pdf/src/templates/meowth/MeowthPage.tsx | 242 + packages/pdf/src/templates/onyx/OnyxPage.tsx | 226 + .../pdf/src/templates/pikachu/PikachuPage.tsx | 297 + .../pdf/src/templates/rhyhorn/RhyhornPage.tsx | 245 + packages/pdf/src/templates/shared/columns.ts | 79 + packages/pdf/src/templates/shared/context.tsx | 121 + .../pdf/src/templates/shared/filtering.ts | 56 + .../src/templates/shared/level-display.tsx | 114 + .../pdf/src/templates/shared/meta-line.tsx | 9 + packages/pdf/src/templates/shared/metrics.ts | 31 + .../pdf/src/templates/shared/page-size.ts | 4 + packages/pdf/src/templates/shared/picture.ts | 3 + .../pdf/src/templates/shared/primitives.tsx | 62 + .../pdf/src/templates/shared/rich-text.tsx | 47 + .../pdf/src/templates/shared/sections.tsx | 901 + packages/pdf/src/templates/shared/styles.ts | 32 + packages/pdf/src/templates/shared/types.ts | 85 + packages/pdf/tsconfig.json | 7 + packages/pdf/vitest.config.ts | 7 + packages/runtime-externals/package.json | 11 + packages/schema/package.json | 28 + packages/schema/schema.json | 1837 ++ packages/schema/src/icons.ts | 1535 ++ packages/schema/src/page.ts | 21 + packages/schema/src/resume/analysis.ts | 55 + packages/schema/src/resume/data.ts | 564 + packages/schema/src/resume/default.ts | 154 + packages/schema/src/resume/sample.ts | 584 + packages/schema/src/templates.ts | 20 + packages/schema/tsconfig.json | 3 + packages/schema/vitest.config.ts | 7 + packages/scripts/database/reset.ts | 33 + packages/scripts/fonts/generate.ts | 186 + packages/scripts/fonts/types.ts | 51 + packages/scripts/package.json | 20 + packages/scripts/tsconfig.json | 7 + packages/ui/components.json | 25 + packages/ui/package.json | 44 + packages/ui/postcss.config.mjs | 5 + packages/ui/src/components/accordion.tsx | 59 + packages/ui/src/components/alert-dialog.tsx | 155 + packages/ui/src/components/alert.tsx | 50 + packages/ui/src/components/avatar.tsx | 90 + packages/ui/src/components/badge.tsx | 49 + packages/ui/src/components/brand-icon.tsx | 24 + packages/ui/src/components/button-group.tsx | 77 + packages/ui/src/components/button.tsx | 53 + packages/ui/src/components/card.tsx | 69 + packages/ui/src/components/checkbox.tsx | 25 + packages/ui/src/components/combobox.tsx | 269 + packages/ui/src/components/command.tsx | 156 + packages/ui/src/components/context-menu.tsx | 239 + packages/ui/src/components/dialog.tsx | 131 + packages/ui/src/components/direction.tsx | 4 + packages/ui/src/components/dropdown-menu.tsx | 251 + packages/ui/src/components/form.tsx | 112 + packages/ui/src/components/hover-card.tsx | 43 + packages/ui/src/components/input-group.tsx | 142 + packages/ui/src/components/input.tsx | 19 + packages/ui/src/components/kbd.tsx | 20 + packages/ui/src/components/label.tsx | 18 + packages/ui/src/components/popover.tsx | 62 + packages/ui/src/components/progress.tsx | 56 + packages/ui/src/components/resizable.tsx | 39 + packages/ui/src/components/scroll-area.tsx | 36 + packages/ui/src/components/separator.tsx | 18 + packages/ui/src/components/sheet.tsx | 101 + packages/ui/src/components/sidebar.tsx | 685 + packages/ui/src/components/skeleton.tsx | 7 + packages/ui/src/components/slider.tsx | 40 + packages/ui/src/components/sonner.tsx | 38 + packages/ui/src/components/spinner.tsx | 16 + packages/ui/src/components/switch.tsx | 29 + packages/ui/src/components/tabs.tsx | 69 + packages/ui/src/components/textarea.tsx | 17 + packages/ui/src/components/toggle.tsx | 36 + packages/ui/src/components/tooltip.tsx | 51 + packages/ui/src/globals.d.ts | 3 + packages/ui/src/hooks/use-confirm.tsx | 95 + .../ui/src/hooks/use-controlled-state.tsx | 32 + packages/ui/src/hooks/use-mobile.tsx | 20 + packages/ui/src/hooks/use-prompt.tsx | 136 + packages/ui/src/styles/globals.css | 168 + packages/ui/tsconfig.json | 12 + packages/ui/vitest.config.ts | 8 + packages/utils/package.json | 50 + packages/utils/src/color.ts | 49 + packages/utils/src/date.ts | 57 + {src/utils => packages/utils/src}/field.ts | 10 +- packages/utils/src/file.ts | 28 + packages/utils/src/html.ts | 31 + packages/utils/src/level.ts | 31 + packages/utils/src/locale.ts | 8 + packages/utils/src/network-icons.ts | 35 + packages/utils/src/rate-limit.ts | 50 + packages/utils/src/resume/docx/builder.ts | 507 + .../utils/src/resume/docx/html-to-docx.ts | 295 + packages/utils/src/resume/docx/index.ts | 9 + packages/utils/src/resume/docx/link-utils.ts | 20 + .../src/resume/docx/section-renderers.ts | 559 + packages/utils/src/resume/patch.ts | 122 + packages/utils/src/sanitize.ts | 99 + {src/utils => packages/utils/src}/string.ts | 42 +- packages/utils/src/style.ts | 7 + packages/utils/src/url-security.ts | 105 + {src/utils => packages/utils/src}/url.ts | 4 +- packages/utils/tsconfig.json | 6 + packages/utils/vitest.config.ts | 7 + plugins/1.migrate.ts | 31 - pnpm-lock.yaml | 7473 +++--- pnpm-workspace.yaml | 12 + public/funding.json | 68 - public/manifest.webmanifest | 98 - scripts/database/migrate.ts | 25 - scripts/database/reset.ts | 34 - scripts/fonts/generate.ts | 185 - scripts/fonts/types.ts | 51 - scripts/migration/resume.ts | 503 - scripts/migration/test-database.ts | 28 - scripts/migration/user.ts | 449 - skills/resume-builder/references/schema.md | 9 - src/components/animation/comet-card.tsx | 88 - src/components/animation/count-up.tsx | 128 - src/components/animation/spotlight.tsx | 106 - src/components/animation/text-mask.tsx | 116 - src/components/command-palette/index.tsx | 154 - src/components/command-palette/pages/base.tsx | 24 - .../command-palette/pages/navigation.tsx | 117 - .../pages/preferences/index.tsx | 32 - .../pages/preferences/language.tsx | 28 - .../pages/preferences/theme.tsx | 32 - .../command-palette/pages/resumes.tsx | 86 - src/components/command-palette/store.ts | 59 - src/components/input/chip-input.test.tsx | 273 - src/components/input/chip-input.tsx | 318 - src/components/input/color-picker.tsx | 109 - src/components/input/github-stars-button.tsx | 33 - src/components/input/icon-picker.tsx | 126 - src/components/input/rich-input.module.css | 178 - src/components/input/rich-input.tsx | 816 - src/components/input/url-input.test.tsx | 121 - src/components/input/url-input.tsx | 97 - .../layout/breakpoint-indicator.tsx | 38 - src/components/layout/error-screen.tsx | 30 - src/components/layout/loading-screen.tsx | 14 - src/components/layout/not-found-screen.tsx | 29 - src/components/level/combobox.tsx | 36 - src/components/level/display.test.tsx | 151 - src/components/level/display.tsx | 74 - src/components/locale/combobox.tsx | 36 - .../resume/hooks/use-css-variables.tsx | 55 - src/components/resume/hooks/use-webfonts.tsx | 90 - src/components/resume/preview.module.css | 118 - src/components/resume/preview.tsx | 199 - .../resume/shared/get-section-component.tsx | 252 - .../resume/shared/inline-header.tsx | 35 - .../resume/shared/items/awards-item.tsx | 71 - .../shared/items/certifications-item.tsx | 76 - .../resume/shared/items/cover-letter-item.tsx | 25 - .../resume/shared/items/education-item.tsx | 144 - .../resume/shared/items/experience-item.tsx | 153 - .../resume/shared/items/interests-item.tsx | 28 - .../resume/shared/items/languages-item.tsx | 27 - .../resume/shared/items/profiles-item.tsx | 37 - .../resume/shared/items/projects-item.tsx | 46 - .../resume/shared/items/publications-item.tsx | 76 - .../resume/shared/items/references-item.tsx | 75 - .../resume/shared/items/skills-item.tsx | 33 - .../resume/shared/items/summary-item.tsx | 19 - .../resume/shared/items/volunteer-item.tsx | 98 - .../resume/shared/linked-title.test.tsx | 95 - src/components/resume/shared/linked-title.tsx | 20 - src/components/resume/shared/page-icon.tsx | 19 - src/components/resume/shared/page-level.tsx | 14 - .../resume/shared/page-link.test.tsx | 34 - src/components/resume/shared/page-link.tsx | 17 - src/components/resume/shared/page-picture.tsx | 32 - src/components/resume/shared/page-section.tsx | 40 - src/components/resume/shared/page-summary.tsx | 26 - src/components/resume/store/resume.test.ts | 287 - src/components/resume/store/resume.ts | 115 - src/components/resume/templates/azurill.tsx | 146 - src/components/resume/templates/bronzor.tsx | 105 - src/components/resume/templates/chikorita.tsx | 130 - src/components/resume/templates/ditgar.tsx | 118 - src/components/resume/templates/ditto.tsx | 114 - src/components/resume/templates/gengar.tsx | 135 - src/components/resume/templates/glalie.tsx | 123 - src/components/resume/templates/kakuna.tsx | 100 - src/components/resume/templates/lapras.tsx | 125 - src/components/resume/templates/leafish.tsx | 116 - src/components/resume/templates/meowth.tsx | 110 - src/components/resume/templates/onyx.tsx | 97 - src/components/resume/templates/pikachu.tsx | 125 - src/components/resume/templates/rhyhorn.tsx | 100 - src/components/resume/templates/types.ts | 8 - src/components/theme/combobox.tsx | 30 - src/components/theme/provider.tsx | 50 - src/components/theme/toggle-button.tsx | 67 - src/components/typography/combobox.tsx | 82 - src/components/typography/font-display.tsx | 45 - src/components/typography/types.ts | 19 - src/components/typography/webfontlist.json | 1 - src/components/ui/accordion.tsx | 51 - src/components/ui/alert-dialog.tsx | 154 - src/components/ui/alert.tsx | 51 - src/components/ui/avatar.tsx | 92 - src/components/ui/badge.tsx | 41 - src/components/ui/brand-icon.test.tsx | 36 - src/components/ui/brand-icon.tsx | 24 - src/components/ui/button-group.tsx | 76 - src/components/ui/button.tsx | 45 - src/components/ui/combobox.tsx | 448 - src/components/ui/command.tsx | 152 - src/components/ui/context-menu.tsx | 234 - src/components/ui/copyright.tsx | 51 - src/components/ui/dialog.tsx | 118 - src/components/ui/dropdown-menu.tsx | 254 - src/components/ui/form.tsx | 158 - src/components/ui/hover-card.tsx | 44 - src/components/ui/input-group.tsx | 140 - src/components/ui/input.tsx | 21 - src/components/ui/kbd.tsx | 20 - src/components/ui/label.tsx | 21 - src/components/ui/popover.tsx | 64 - src/components/ui/progress.tsx | 57 - src/components/ui/resizable.tsx | 45 - src/components/ui/scroll-area.tsx | 37 - src/components/ui/separator.tsx | 19 - src/components/ui/sheet.tsx | 102 - src/components/ui/sidebar.tsx | 673 - src/components/ui/skeleton.tsx | 7 - src/components/ui/slider.tsx | 45 - src/components/ui/sonner.tsx | 40 - src/components/ui/spinner.tsx | 20 - src/components/ui/switch.tsx | 30 - src/components/ui/tabs.tsx | 69 - src/components/ui/textarea.tsx | 18 - src/components/ui/toggle.tsx | 36 - src/components/ui/tooltip.tsx | 52 - src/components/user/dropdown-menu.tsx | 126 - src/constants/iso-countries.ts | 255 - src/dialogs/api-key/create.tsx | 224 - src/dialogs/auth/change-password.tsx | 182 - src/dialogs/auth/disable-two-factor.tsx | 135 - src/dialogs/auth/enable-two-factor.tsx | 371 - src/dialogs/manager.tsx | 79 - src/dialogs/resume/import.tsx | 356 - src/dialogs/resume/index.tsx | 370 - src/dialogs/resume/sections/award.tsx | 234 - src/dialogs/resume/sections/certification.tsx | 234 - src/dialogs/resume/sections/cover-letter.tsx | 170 - src/dialogs/resume/sections/custom.tsx | 207 - src/dialogs/resume/sections/education.tsx | 282 - src/dialogs/resume/sections/experience.tsx | 394 - src/dialogs/resume/sections/interest.tsx | 224 - src/dialogs/resume/sections/language.tsx | 192 - src/dialogs/resume/sections/profile.tsx | 268 - src/dialogs/resume/sections/project.tsx | 218 - src/dialogs/resume/sections/publication.tsx | 234 - src/dialogs/resume/sections/reference.tsx | 234 - src/dialogs/resume/sections/skill.tsx | 269 - src/dialogs/resume/sections/summary-item.tsx | 152 - src/dialogs/resume/sections/volunteer.tsx | 234 - src/dialogs/resume/template/data.ts | 114 - src/dialogs/resume/template/gallery.tsx | 123 - src/dialogs/store.test.ts | 206 - src/dialogs/store.ts | 205 - src/hooks/use-confirm.test.tsx | 122 - src/hooks/use-confirm.tsx | 99 - src/hooks/use-controlled-state.test.tsx | 109 - src/hooks/use-controlled-state.tsx | 32 - src/hooks/use-form-blocker.test.tsx | 181 - src/hooks/use-form-blocker.tsx | 63 - src/hooks/use-mobile.test.tsx | 104 - src/hooks/use-mobile.tsx | 20 - src/hooks/use-prompt.test.tsx | 159 - src/hooks/use-prompt.tsx | 143 - src/integrations/ai/prompts/tailor-system.md | 108 - src/integrations/ai/store.test.ts | 203 - src/integrations/ai/store.ts | 90 - src/integrations/auth/client.ts | 41 - src/integrations/auth/config.ts | 403 - src/integrations/auth/functions.ts | 18 - src/integrations/drizzle/client.ts | 36 - src/integrations/drizzle/helpers.ts | 8 - src/integrations/drizzle/index.ts | 1 - src/integrations/drizzle/schema.ts | 579 - src/integrations/email/service.ts | 74 - src/integrations/email/templates/auth.tsx | 187 - .../email/templates/verify-email-change.tsx | 19 - src/integrations/import/json-resume.test.ts | 327 - src/integrations/import/json-resume.tsx | 449 - .../import/reactive-resume-json.test.ts | 62 - .../import/reactive-resume-json.tsx | 77 - .../import/reactive-resume-v4-json.test.ts | 808 - .../import/reactive-resume-v4-json.tsx | 772 - src/integrations/jobs/factory.ts | 37 - src/integrations/jobs/provider.ts | 36 - .../jobs/providers/jsearch.test.ts | 271 - src/integrations/jobs/providers/jsearch.ts | 144 - src/integrations/jobs/store.test.ts | 93 - src/integrations/jobs/store.ts | 57 - src/integrations/orpc/client.ts | 55 - src/integrations/orpc/context.ts | 108 - src/integrations/orpc/dto/resume.ts | 106 - .../orpc/helpers/resume-access.test.ts | 106 - .../orpc/helpers/resume-access.ts | 37 - src/integrations/orpc/rate-limit.ts | 116 - src/integrations/orpc/router/ai.ts | 313 - src/integrations/orpc/router/auth.ts | 36 - src/integrations/orpc/router/flags.ts | 25 - src/integrations/orpc/router/index.ts | 19 - src/integrations/orpc/router/jobs.ts | 84 - src/integrations/orpc/router/printer.ts | 60 - src/integrations/orpc/router/resume.ts | 398 - src/integrations/orpc/router/statistics.ts | 64 - src/integrations/orpc/router/storage.ts | 111 - src/integrations/orpc/services/ai.ts | 473 - src/integrations/orpc/services/auth.ts | 51 - src/integrations/orpc/services/flags.ts | 13 - src/integrations/orpc/services/jobs.test.ts | 367 - src/integrations/orpc/services/jobs.ts | 90 - src/integrations/orpc/services/printer.ts | 603 - src/integrations/orpc/services/resume.test.ts | 41 - src/integrations/orpc/services/resume.ts | 508 - src/integrations/orpc/services/statistics.ts | 119 - src/integrations/orpc/services/storage.ts | 378 - src/integrations/query/client.ts | 38 - src/integrations/rate-limit/config.ts | 42 - src/routeTree.gen.ts | 1004 - src/router.tsx | 46 - src/routes/$username/$slug.tsx | 71 - .../oauth-protected-resource.$.ts | 25 - .../[.]well-known/oauth-protected-resource.ts | 25 - src/routes/__root.tsx | 145 - src/routes/_home/-sections/donate.tsx | 251 - src/routes/_home/-sections/faq.tsx | 130 - src/routes/_home/-sections/features.tsx | 205 - src/routes/_home/-sections/footer.tsx | 150 - src/routes/_home/-sections/header.tsx | 90 - src/routes/_home/-sections/hero.tsx | 154 - src/routes/_home/-sections/prefooter.tsx | 41 - .../_home/-sections/product-hunt-banner.tsx | 39 - src/routes/_home/-sections/statistics.tsx | 107 - src/routes/_home/-sections/templates.tsx | 118 - src/routes/_home/-sections/testimonials.tsx | 131 - src/routes/_home/route.tsx | 24 - src/routes/api/auth.$.ts | 131 - src/routes/api/health.ts | 131 - src/routes/api/openapi.$.ts | 86 - src/routes/api/rpc.$.ts | 35 - src/routes/auth/-components/social-auth.tsx | 177 - src/routes/auth/forgot-password.tsx | 149 - src/routes/auth/index.tsx | 8 - src/routes/auth/login.tsx | 249 - src/routes/auth/oauth.ts | 103 - src/routes/auth/register.tsx | 290 - src/routes/auth/reset-password.tsx | 140 - src/routes/auth/resume-password.tsx | 162 - src/routes/auth/route.tsx | 17 - src/routes/auth/verify-2fa-backup.tsx | 110 - src/routes/auth/verify-2fa.tsx | 132 - .../builder/$resumeId/-components/dock.tsx | 185 - .../builder/$resumeId/-components/edge.tsx | 19 - .../builder/$resumeId/-components/header.tsx | 181 - .../builder/$resumeId/-sidebar/left/index.tsx | 123 - .../-sidebar/left/sections/awards.tsx | 39 - .../-sidebar/left/sections/basics.tsx | 135 - .../-sidebar/left/sections/certifications.tsx | 48 - .../-sidebar/left/sections/custom-fields.tsx | 185 - .../-sidebar/left/sections/custom.tsx | 313 - .../-sidebar/left/sections/education.tsx | 39 - .../-sidebar/left/sections/experience.tsx | 48 - .../-sidebar/left/sections/interests.tsx | 39 - .../-sidebar/left/sections/languages.tsx | 39 - .../-sidebar/left/sections/picture.tsx | 475 - .../-sidebar/left/sections/profiles.tsx | 39 - .../-sidebar/left/sections/projects.tsx | 44 - .../-sidebar/left/sections/publications.tsx | 39 - .../-sidebar/left/sections/references.tsx | 39 - .../-sidebar/left/sections/skills.tsx | 39 - .../-sidebar/left/sections/summary.tsx | 21 - .../-sidebar/left/sections/volunteer.tsx | 45 - .../-sidebar/left/shared/section-base.tsx | 71 - .../-sidebar/left/shared/section-item.tsx | 348 - .../-sidebar/left/shared/section-menu.tsx | 178 - .../$resumeId/-sidebar/right/index.tsx | 110 - .../-sidebar/right/sections/css-editor.tsx | 212 - .../$resumeId/-sidebar/right/sections/css.tsx | 104 - .../-sidebar/right/sections/design.tsx | 293 - .../-sidebar/right/sections/export.tsx | 118 - .../-sidebar/right/sections/information.tsx | 108 - .../-sidebar/right/sections/layout/index.tsx | 103 - .../-sidebar/right/sections/layout/pages.tsx | 431 - .../-sidebar/right/sections/notes.tsx | 45 - .../-sidebar/right/sections/page.tsx | 262 - .../right/sections/resume-analysis.tsx | 278 - .../-sidebar/right/sections/sharing.tsx | 169 - .../-sidebar/right/sections/statistics.tsx | 77 - .../-sidebar/right/sections/template.tsx | 63 - .../-sidebar/right/sections/typography.tsx | 309 - .../-sidebar/right/shared/section-base.tsx | 52 - .../builder/$resumeId/-store/section.ts | 55 - .../builder/$resumeId/-store/sidebar.ts | 141 - src/routes/builder/$resumeId/index.tsx | 50 - src/routes/builder/$resumeId/route.tsx | 164 - src/routes/dashboard/-components/header.tsx | 20 - src/routes/dashboard/-components/sidebar.tsx | 207 - src/routes/dashboard/index.tsx | 7 - .../job-search/-components/filter-helpers.ts | 93 - .../job-search/-components/job-card.tsx | 83 - .../job-search/-components/job-detail.tsx | 248 - .../job-search/-components/job-utils.ts | 85 - .../-components/search-filters.test.ts | 318 - .../job-search/-components/search-filters.tsx | 277 - .../job-search/-components/tailor-dialog.tsx | 375 - .../job-search/-components/use-job-search.ts | 172 - src/routes/dashboard/job-search/index.tsx | 249 - .../resumes/-components/cards/base-card.tsx | 40 - .../resumes/-components/cards/create-card.tsx | 22 - .../resumes/-components/cards/import-card.tsx | 22 - .../resumes/-components/cards/resume-card.tsx | 83 - .../resumes/-components/grid-view.tsx | 59 - .../resumes/-components/list-view.tsx | 135 - .../-components/menus/context-menu.tsx | 132 - .../-components/menus/dropdown-menu.tsx | 130 - src/routes/dashboard/resumes/index.tsx | 142 - src/routes/dashboard/route.tsx | 38 - src/routes/dashboard/settings/api-keys.tsx | 166 - .../authentication/-components/hooks.tsx | 169 - .../authentication/-components/passkeys.tsx | 173 - .../authentication/-components/password.tsx | 75 - .../-components/social-provider.tsx | 87 - .../authentication/-components/two-factor.tsx | 78 - .../settings/authentication/index.tsx | 52 - src/routes/dashboard/settings/danger-zone.tsx | 109 - .../integrations/-components/ai-section.tsx | 283 - .../-components/job-search-section.tsx | 182 - .../dashboard/settings/integrations/route.tsx | 42 - src/routes/dashboard/settings/preferences.tsx | 60 - src/routes/dashboard/settings/profile.tsx | 283 - src/routes/mcp/-helpers/mcp-server-card.ts | 384 - src/routes/mcp/-helpers/mcp-tool-names.ts | 20 - src/routes/mcp/-helpers/prompts.ts | 247 - src/routes/mcp/-helpers/resources.ts | 73 - src/routes/mcp/-helpers/tool-annotations.ts | 99 - src/routes/mcp/-helpers/tools.ts | 570 - src/routes/mcp/index.ts | 130 - src/routes/printer/$resumeId.tsx | 68 - src/routes/schema[.]json.ts | 29 - src/routes/uploads/$userId.$.tsx | 178 - src/schema/icons.ts | 1535 -- src/schema/jobs.test.ts | 310 - src/schema/jobs.ts | 154 - src/schema/page.ts | 29 - src/schema/resume/analysis.ts | 54 - src/schema/resume/data.ts | 688 - src/schema/resume/sample.ts | 571 - src/schema/schema.json | 2473 -- src/schema/tailor.ts | 97 - src/schema/templates.ts | 41 - src/server.test.ts | 14 - src/server.ts | 56 - src/styles/globals.css | 177 - src/utils/ai-template.test.ts | 83 - src/utils/ai-template.ts | 131 - src/utils/color.test.ts | 92 - src/utils/color.ts | 43 - src/utils/date.test.ts | 71 - src/utils/date.ts | 57 - src/utils/env.test.ts | 37 - src/utils/env.ts | 81 - src/utils/error-message.test.ts | 64 - src/utils/error-message.ts | 36 - src/utils/field.test.ts | 21 - src/utils/file.test.ts | 79 - src/utils/file.ts | 28 - src/utils/fonts.test.ts | 82 - src/utils/fonts.ts | 205 - src/utils/html.test.ts | 40 - src/utils/html.ts | 31 - src/utils/level.test.ts | 74 - src/utils/level.ts | 31 - src/utils/locale.test.ts | 38 - src/utils/locale.ts | 173 - src/utils/network-icons.test.ts | 53 - src/utils/network-icons.ts | 35 - src/utils/password.test.ts | 53 - src/utils/password.ts | 8 - src/utils/printer-token.test.ts | 262 - src/utils/printer-token.ts | 55 - src/utils/resume/docx/builder.test.ts | 453 - src/utils/resume/docx/builder.ts | 503 - src/utils/resume/docx/html-to-docx.test.ts | 298 - src/utils/resume/docx/html-to-docx.ts | 298 - src/utils/resume/docx/index.test.ts | 69 - src/utils/resume/docx/index.ts | 14 - src/utils/resume/docx/link-utils.test.ts | 69 - src/utils/resume/docx/link-utils.ts | 20 - .../resume/docx/section-renderers.test.ts | 714 - src/utils/resume/docx/section-renderers.ts | 560 - src/utils/resume/move-item.test.ts | 614 - src/utils/resume/move-item.ts | 277 - src/utils/resume/patch.test.ts | 469 - src/utils/resume/patch.ts | 124 - src/utils/resume/section-actions.test.ts | 369 - src/utils/resume/section-actions.ts | 43 - src/utils/resume/section.tsx | 179 - src/utils/resume/tailor.test.ts | 673 - src/utils/resume/tailor.ts | 197 - src/utils/sanitize.test.ts | 164 - src/utils/sanitize.ts | 99 - src/utils/string.test.ts | 85 - src/utils/style.test.ts | 45 - src/utils/style.ts | 6 - src/utils/theme.test.ts | 42 - src/utils/url-security.test.ts | 130 - src/utils/url-security.ts | 122 - src/utils/url.test.ts | 24 - src/vite-env.d.ts | 57 - tsconfig.json | 31 +- turbo.json | 93 + vite.config.ts | 296 - vitest.setup.ts | 1 + vitest.shared.ts | 44 + 1015 files changed, 106087 insertions(+), 141872 deletions(-) delete mode 100644 .gitattributes create mode 100755 .vite-hooks/_/commit-msg create mode 100755 .vite-hooks/_/pre-commit delete mode 100755 .vite-hooks/pre-commit delete mode 100644 AGENTS.md delete mode 120000 CLAUDE.md delete mode 100644 Dockerfile.dev create mode 100644 apps/web/components.json create mode 100644 apps/web/lingui.config.ts rename {locales => apps/web/locales}/af-ZA.po (84%) rename {locales => apps/web/locales}/am-ET.po (84%) rename {locales => apps/web/locales}/ar-SA.po (85%) rename {locales => apps/web/locales}/az-AZ.po (84%) rename {locales => apps/web/locales}/bg-BG.po (85%) rename {locales => apps/web/locales}/bn-BD.po (85%) rename {locales => apps/web/locales}/ca-ES.po (84%) rename {locales => apps/web/locales}/cs-CZ.po (84%) rename {locales => apps/web/locales}/da-DK.po (84%) rename {locales => apps/web/locales}/de-DE.po (84%) rename {locales => apps/web/locales}/el-GR.po (85%) rename {locales => apps/web/locales}/en-GB.po (84%) rename {locales => apps/web/locales}/en-US.po (85%) rename {locales => apps/web/locales}/es-ES.po (84%) rename {locales => apps/web/locales}/fa-IR.po (85%) rename {locales => apps/web/locales}/fi-FI.po (84%) rename {locales => apps/web/locales}/fr-FR.po (85%) rename {locales => apps/web/locales}/he-IL.po (84%) rename {locales => apps/web/locales}/hi-IN.po (85%) rename {locales => apps/web/locales}/hu-HU.po (84%) rename {locales => apps/web/locales}/id-ID.po (84%) rename {locales => apps/web/locales}/it-IT.po (84%) rename {locales => apps/web/locales}/ja-JP.po (85%) rename {locales => apps/web/locales}/km-KH.po (85%) rename {locales => apps/web/locales}/kn-IN.po (85%) rename {locales => apps/web/locales}/ko-KR.po (85%) rename {locales => apps/web/locales}/lt-LT.po (85%) rename {locales => apps/web/locales}/lv-LV.po (84%) rename {locales => apps/web/locales}/ml-IN.po (85%) rename {locales => apps/web/locales}/mr-IN.po (85%) rename {locales => apps/web/locales}/ms-MY.po (84%) rename {locales => apps/web/locales}/ne-NP.po (85%) rename {locales => apps/web/locales}/nl-NL.po (84%) rename {locales => apps/web/locales}/no-NO.po (84%) rename {locales => apps/web/locales}/or-IN.po (85%) rename {locales => apps/web/locales}/pl-PL.po (84%) rename {locales => apps/web/locales}/pt-BR.po (84%) rename {locales => apps/web/locales}/pt-PT.po (84%) rename {locales => apps/web/locales}/ro-RO.po (84%) rename {locales => apps/web/locales}/ru-RU.po (85%) rename {locales => apps/web/locales}/sk-SK.po (84%) rename {locales => apps/web/locales}/sl-SI.po (84%) rename {locales => apps/web/locales}/sq-AL.po (84%) rename {locales => apps/web/locales}/sr-SP.po (85%) rename {locales => apps/web/locales}/sv-SE.po (84%) rename {locales => apps/web/locales}/ta-IN.po (85%) rename {locales => apps/web/locales}/te-IN.po (85%) rename {locales => apps/web/locales}/th-TH.po (85%) rename {locales => apps/web/locales}/tr-TR.po (84%) rename {locales => apps/web/locales}/uk-UA.po (85%) rename {locales => apps/web/locales}/uz-UZ.po (84%) rename {locales => apps/web/locales}/vi-VN.po (84%) rename {locales => apps/web/locales}/zh-CN.po (84%) rename {locales => apps/web/locales}/zh-TW.po (84%) rename {locales => apps/web/locales}/zu-ZA.po (84%) create mode 100644 apps/web/package.json create mode 100644 apps/web/plugins/1.migrate.ts rename {public => apps/web/public}/apple-touch-icon-180x180.png (100%) rename {public => apps/web/public}/favicon.ico (100%) rename {public => apps/web/public}/favicon.svg (100%) create mode 100644 apps/web/public/fonts/fira-sans-condensed-500.ttf create mode 100644 apps/web/public/fonts/ibm-plex-serif-400.ttf create mode 100644 apps/web/public/fonts/ibm-plex-serif-600.ttf create mode 100644 apps/web/public/funding.json rename {public => apps/web/public}/icon/dark.svg (100%) rename {public => apps/web/public}/icon/light.svg (100%) rename {public => apps/web/public}/logo/dark.svg (100%) rename {public => apps/web/public}/logo/light.svg (100%) create mode 100644 apps/web/public/manifest.webmanifest rename {public => apps/web/public}/maskable-icon-512x512.png (100%) rename {public => apps/web/public}/opengraph/banner.jpg (100%) rename {public => apps/web/public}/opengraph/features.jpg (100%) rename {public => apps/web/public}/opengraph/logo.svg (100%) rename {public => apps/web/public}/photos/sample-picture.jpg (100%) rename {public => apps/web/public}/pwa-192x192.png (100%) rename {public => apps/web/public}/pwa-512x512.png (100%) rename {public => apps/web/public}/pwa-64x64.png (100%) rename {public => apps/web/public}/robots.txt (100%) rename {public => apps/web/public}/screenshots/mobile/1-landing-page.webp (100%) rename {public => apps/web/public}/screenshots/mobile/2-resume-dashboard.webp (100%) rename {public => apps/web/public}/screenshots/mobile/3-builder-screen.webp (100%) rename {public => apps/web/public}/screenshots/mobile/4-template-gallery.webp (100%) rename {public => apps/web/public}/screenshots/web/1-landing-page.webp (100%) rename {public => apps/web/public}/screenshots/web/2-resume-dashboard.webp (100%) rename {public => apps/web/public}/screenshots/web/3-builder-screen.webp (100%) rename {public => apps/web/public}/screenshots/web/4-template-gallery.webp (100%) rename {public => apps/web/public}/sitemap.xml (100%) rename {public => apps/web/public}/sounds/switch-off.mp3 (100%) rename {public => apps/web/public}/sounds/switch-on.mp3 (100%) rename {public => apps/web/public}/templates/jpg/azurill.jpg (100%) rename {public => apps/web/public}/templates/jpg/bronzor.jpg (100%) rename {public => apps/web/public}/templates/jpg/chikorita.jpg (100%) rename {public => apps/web/public}/templates/jpg/ditgar.jpg (100%) rename {public => apps/web/public}/templates/jpg/ditto.jpg (100%) rename {public => apps/web/public}/templates/jpg/gengar.jpg (100%) rename {public => apps/web/public}/templates/jpg/glalie.jpg (100%) rename {public => apps/web/public}/templates/jpg/kakuna.jpg (100%) rename {public => apps/web/public}/templates/jpg/lapras.jpg (100%) rename {public => apps/web/public}/templates/jpg/leafish.jpg (100%) rename {public => apps/web/public}/templates/jpg/meowth.jpg (100%) rename {public => apps/web/public}/templates/jpg/onyx.jpg (100%) rename {public => apps/web/public}/templates/jpg/pikachu.jpg (100%) rename {public => apps/web/public}/templates/jpg/rhyhorn.jpg (100%) rename {public => apps/web/public}/templates/pdf/azurill.pdf (100%) rename {public => apps/web/public}/templates/pdf/bronzor.pdf (100%) rename {public => apps/web/public}/templates/pdf/chikorita.pdf (100%) rename {public => apps/web/public}/templates/pdf/ditgar.pdf (100%) rename {public => apps/web/public}/templates/pdf/ditto.pdf (100%) rename {public => apps/web/public}/templates/pdf/gengar.pdf (100%) rename {public => apps/web/public}/templates/pdf/glalie.pdf (100%) rename {public => apps/web/public}/templates/pdf/kakuna.pdf (100%) rename {public => apps/web/public}/templates/pdf/lapras.pdf (100%) rename {public => apps/web/public}/templates/pdf/leafish.pdf (100%) rename {public => apps/web/public}/templates/pdf/meowth.pdf (100%) rename {public => apps/web/public}/templates/pdf/onyx.pdf (100%) rename {public => apps/web/public}/templates/pdf/pikachu.pdf (100%) rename {public => apps/web/public}/templates/pdf/rhyhorn.pdf (100%) rename {public => apps/web/public}/videos/timelapse.mp4 (100%) create mode 100644 apps/web/src/components/animation/comet-card.tsx create mode 100644 apps/web/src/components/animation/count-up.tsx create mode 100644 apps/web/src/components/animation/spotlight.tsx create mode 100644 apps/web/src/components/animation/text-mask.tsx create mode 100644 apps/web/src/components/command-palette/index.tsx create mode 100644 apps/web/src/components/command-palette/pages/base.tsx create mode 100644 apps/web/src/components/command-palette/pages/navigation.tsx create mode 100644 apps/web/src/components/command-palette/pages/preferences/index.tsx create mode 100644 apps/web/src/components/command-palette/pages/preferences/language.tsx create mode 100644 apps/web/src/components/command-palette/pages/preferences/theme.tsx create mode 100644 apps/web/src/components/command-palette/pages/resumes.tsx create mode 100644 apps/web/src/components/command-palette/store.ts create mode 100644 apps/web/src/components/input/chip-input.tsx create mode 100644 apps/web/src/components/input/color-picker.tsx create mode 100644 apps/web/src/components/input/github-stars-button.tsx create mode 100644 apps/web/src/components/input/icon-picker.tsx create mode 100644 apps/web/src/components/input/rich-input.tsx create mode 100644 apps/web/src/components/input/url-input.tsx create mode 100644 apps/web/src/components/layout/breakpoint-indicator.tsx create mode 100644 apps/web/src/components/layout/error-screen.tsx create mode 100644 apps/web/src/components/layout/loading-screen.tsx create mode 100644 apps/web/src/components/layout/not-found-screen.tsx create mode 100644 apps/web/src/components/level/combobox.tsx create mode 100644 apps/web/src/components/level/display.tsx create mode 100644 apps/web/src/components/locale/combobox.tsx create mode 100644 apps/web/src/components/resume/pdf-canvas.tsx create mode 100644 apps/web/src/components/resume/preview.browser.tsx create mode 100644 apps/web/src/components/resume/preview.shared.tsx create mode 100644 apps/web/src/components/resume/preview.tsx create mode 100644 apps/web/src/components/resume/use-resume.ts create mode 100644 apps/web/src/components/theme/combobox.tsx create mode 100644 apps/web/src/components/theme/provider.tsx create mode 100644 apps/web/src/components/theme/toggle-button.tsx create mode 100644 apps/web/src/components/typography/combobox.tsx create mode 100644 apps/web/src/components/typography/font-display.tsx create mode 100644 apps/web/src/components/ui/combobox.tsx create mode 100644 apps/web/src/components/ui/copyright.tsx create mode 100644 apps/web/src/components/user/dropdown-menu.tsx create mode 100644 apps/web/src/dialogs/api-key/create.tsx create mode 100644 apps/web/src/dialogs/auth/change-password.tsx create mode 100644 apps/web/src/dialogs/auth/disable-two-factor.tsx create mode 100644 apps/web/src/dialogs/auth/enable-two-factor.tsx create mode 100644 apps/web/src/dialogs/manager.tsx create mode 100644 apps/web/src/dialogs/resume/import.tsx create mode 100644 apps/web/src/dialogs/resume/index.tsx create mode 100644 apps/web/src/dialogs/resume/sections/award.tsx create mode 100644 apps/web/src/dialogs/resume/sections/certification.tsx create mode 100644 apps/web/src/dialogs/resume/sections/cover-letter.tsx create mode 100644 apps/web/src/dialogs/resume/sections/custom.tsx create mode 100644 apps/web/src/dialogs/resume/sections/education.tsx create mode 100644 apps/web/src/dialogs/resume/sections/experience.tsx create mode 100644 apps/web/src/dialogs/resume/sections/interest.tsx create mode 100644 apps/web/src/dialogs/resume/sections/language.tsx create mode 100644 apps/web/src/dialogs/resume/sections/profile.tsx create mode 100644 apps/web/src/dialogs/resume/sections/project.tsx create mode 100644 apps/web/src/dialogs/resume/sections/publication.tsx create mode 100644 apps/web/src/dialogs/resume/sections/reference.tsx create mode 100644 apps/web/src/dialogs/resume/sections/skill.tsx create mode 100644 apps/web/src/dialogs/resume/sections/summary-item.tsx create mode 100644 apps/web/src/dialogs/resume/sections/volunteer.tsx create mode 100644 apps/web/src/dialogs/resume/template/data.ts create mode 100644 apps/web/src/dialogs/resume/template/gallery.tsx create mode 100644 apps/web/src/dialogs/store.ts create mode 100644 apps/web/src/hooks/use-confirm.tsx create mode 100644 apps/web/src/hooks/use-controlled-state.tsx create mode 100644 apps/web/src/hooks/use-form-blocker.tsx create mode 100644 apps/web/src/hooks/use-mobile.tsx create mode 100644 apps/web/src/hooks/use-prompt.tsx create mode 100644 apps/web/src/index.css create mode 100644 apps/web/src/libs/auth/client.ts create mode 100644 apps/web/src/libs/auth/session.ts create mode 100644 apps/web/src/libs/error-message.ts create mode 100644 apps/web/src/libs/locale.ts create mode 100644 apps/web/src/libs/orpc/client.ts create mode 100644 apps/web/src/libs/pwa.ts create mode 100644 apps/web/src/libs/query/client.ts create mode 100644 apps/web/src/libs/resume/make-section-item.ts create mode 100644 apps/web/src/libs/resume/move-item.ts create mode 100644 apps/web/src/libs/resume/pdf-document.tsx create mode 100644 apps/web/src/libs/resume/section-actions.ts create mode 100644 apps/web/src/libs/resume/section-title-locale.ts create mode 100644 apps/web/src/libs/resume/section-title.ts create mode 100644 apps/web/src/libs/resume/section.tsx create mode 100644 apps/web/src/libs/tanstack-form.tsx rename {src/utils => apps/web/src/libs}/theme.ts (59%) create mode 100644 apps/web/src/routeTree.gen.ts create mode 100644 apps/web/src/router.tsx create mode 100644 apps/web/src/routes/$username/$slug.tsx create mode 100644 apps/web/src/routes/$username/-components/public-resume.tsx rename {src => apps/web/src}/routes/[.]well-known/$.ts (63%) rename {src => apps/web/src}/routes/[.]well-known/mcp/server-card[.]json.ts (52%) rename {src => apps/web/src}/routes/[.]well-known/oauth-authorization-server.$.ts (67%) rename {src => apps/web/src}/routes/[.]well-known/oauth-authorization-server.ts (66%) create mode 100644 apps/web/src/routes/[.]well-known/oauth-protected-resource.$.ts create mode 100644 apps/web/src/routes/[.]well-known/oauth-protected-resource.ts rename {src => apps/web/src}/routes/[.]well-known/openid-configuration.ts (66%) create mode 100644 apps/web/src/routes/__root.tsx create mode 100644 apps/web/src/routes/_home/-sections/donate.tsx create mode 100644 apps/web/src/routes/_home/-sections/faq.tsx create mode 100644 apps/web/src/routes/_home/-sections/features.tsx create mode 100644 apps/web/src/routes/_home/-sections/footer.tsx create mode 100644 apps/web/src/routes/_home/-sections/header.tsx create mode 100644 apps/web/src/routes/_home/-sections/hero.tsx create mode 100644 apps/web/src/routes/_home/-sections/prefooter.tsx create mode 100644 apps/web/src/routes/_home/-sections/statistics.tsx create mode 100644 apps/web/src/routes/_home/-sections/templates.tsx create mode 100644 apps/web/src/routes/_home/-sections/testimonials.tsx rename {src => apps/web/src}/routes/_home/index.tsx (52%) create mode 100644 apps/web/src/routes/_home/route.tsx create mode 100644 apps/web/src/routes/api/auth.$.ts create mode 100644 apps/web/src/routes/api/health.ts create mode 100644 apps/web/src/routes/api/openapi.$.ts create mode 100644 apps/web/src/routes/api/rpc.$.ts create mode 100644 apps/web/src/routes/api/uploads/$userId.$.ts create mode 100644 apps/web/src/routes/auth/-components/social-auth.tsx create mode 100644 apps/web/src/routes/auth/forgot-password.tsx create mode 100644 apps/web/src/routes/auth/index.tsx create mode 100644 apps/web/src/routes/auth/login.tsx create mode 100644 apps/web/src/routes/auth/oauth.ts create mode 100644 apps/web/src/routes/auth/register.tsx create mode 100644 apps/web/src/routes/auth/reset-password.tsx create mode 100644 apps/web/src/routes/auth/resume-password.tsx create mode 100644 apps/web/src/routes/auth/route.tsx create mode 100644 apps/web/src/routes/auth/verify-2fa-backup.tsx create mode 100644 apps/web/src/routes/auth/verify-2fa.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-components/dock.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-components/edge.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-components/header.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-components/preview-page.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/index.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/awards.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/basics.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/certifications.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/custom-fields.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/custom.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/education.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/experience.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/interests.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/languages.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/profiles.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/projects.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/publications.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/references.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/skills.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/summary.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/sections/volunteer.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/shared/section-base.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/shared/section-item.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/left/shared/section-menu.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/index.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/export.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/information.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/layout/index.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/layout/pages.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/notes.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/page.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/resume-analysis.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/sharing.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/statistics.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/template.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/typography.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-sidebar/right/shared/section-base.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/-store/section.ts create mode 100644 apps/web/src/routes/builder/$resumeId/-store/sidebar.ts create mode 100644 apps/web/src/routes/builder/$resumeId/index.tsx create mode 100644 apps/web/src/routes/builder/$resumeId/route.tsx rename {src => apps/web/src}/routes/dashboard/-components/functions.ts (63%) create mode 100644 apps/web/src/routes/dashboard/-components/header.tsx create mode 100644 apps/web/src/routes/dashboard/-components/sidebar.tsx create mode 100644 apps/web/src/routes/dashboard/index.tsx create mode 100644 apps/web/src/routes/dashboard/resumes/-components/cards/base-card.tsx create mode 100644 apps/web/src/routes/dashboard/resumes/-components/cards/create-card.tsx create mode 100644 apps/web/src/routes/dashboard/resumes/-components/cards/import-card.tsx create mode 100644 apps/web/src/routes/dashboard/resumes/-components/cards/resume-card.tsx create mode 100644 apps/web/src/routes/dashboard/resumes/-components/cards/resume-thumbnail.shared.ts create mode 100644 apps/web/src/routes/dashboard/resumes/-components/cards/resume-thumbnail.tsx create mode 100644 apps/web/src/routes/dashboard/resumes/-components/grid-view.tsx create mode 100644 apps/web/src/routes/dashboard/resumes/-components/list-view.tsx create mode 100644 apps/web/src/routes/dashboard/resumes/-components/menus/context-menu.tsx create mode 100644 apps/web/src/routes/dashboard/resumes/-components/menus/dropdown-menu.tsx create mode 100644 apps/web/src/routes/dashboard/resumes/index.tsx create mode 100644 apps/web/src/routes/dashboard/route.tsx rename {src => apps/web/src}/routes/dashboard/settings/ai.tsx (56%) create mode 100644 apps/web/src/routes/dashboard/settings/api-keys.tsx create mode 100644 apps/web/src/routes/dashboard/settings/authentication/-components/hooks.tsx create mode 100644 apps/web/src/routes/dashboard/settings/authentication/-components/passkeys.tsx create mode 100644 apps/web/src/routes/dashboard/settings/authentication/-components/password.tsx create mode 100644 apps/web/src/routes/dashboard/settings/authentication/-components/social-provider.tsx create mode 100644 apps/web/src/routes/dashboard/settings/authentication/-components/two-factor.tsx create mode 100644 apps/web/src/routes/dashboard/settings/authentication/index.tsx create mode 100644 apps/web/src/routes/dashboard/settings/danger-zone.tsx create mode 100644 apps/web/src/routes/dashboard/settings/integrations/-components/ai-section.tsx create mode 100644 apps/web/src/routes/dashboard/settings/integrations/route.tsx rename {src => apps/web/src}/routes/dashboard/settings/job-search.tsx (57%) create mode 100644 apps/web/src/routes/dashboard/settings/preferences.tsx create mode 100644 apps/web/src/routes/dashboard/settings/profile.tsx create mode 100644 apps/web/src/routes/mcp/-helpers/mcp-server-card.ts create mode 100644 apps/web/src/routes/mcp/-helpers/mcp-tool-names.ts create mode 100644 apps/web/src/routes/mcp/-helpers/prompts.ts create mode 100644 apps/web/src/routes/mcp/-helpers/resources.ts create mode 100644 apps/web/src/routes/mcp/-helpers/tool-annotations.ts create mode 100644 apps/web/src/routes/mcp/-helpers/tools.ts create mode 100644 apps/web/src/routes/mcp/index.ts create mode 100644 apps/web/src/routes/schema[.]json.ts create mode 100644 apps/web/src/routes/templates/$.tsx create mode 100644 apps/web/src/routes/uploads/$userId.$.tsx create mode 100644 apps/web/src/server.ts create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/vite-env.d.ts create mode 100644 apps/web/vite.config.ts create mode 100644 apps/web/vitest.config.ts create mode 100644 biome.json create mode 100644 commitlint.config.cjs delete mode 100644 components.json delete mode 100644 docs/guides/using-custom-css.mdx delete mode 100644 docs/images/guides/using-custom-css/screenshot-1.webp delete mode 100644 drizzle.config.ts create mode 100644 lefthook.yml delete mode 100644 lingui.config.ts create mode 100644 migrations/20260501153733_brave_amazoness/migration.sql create mode 100644 migrations/20260501153733_brave_amazoness/snapshot.json create mode 100644 migrations/20260507055641_yellow_morg/migration.sql create mode 100644 migrations/20260507055641_yellow_morg/snapshot.json delete mode 100644 netlify.toml create mode 100644 packages/ai/package.json create mode 100644 packages/ai/src/markdown-raw.d.ts create mode 100644 packages/ai/src/prompts.ts rename {src/integrations/ai => packages/ai/src}/prompts/analyze-resume-system.md (100%) rename {src/integrations/ai => packages/ai/src}/prompts/chat-system.md (100%) rename {src/integrations/ai => packages/ai/src}/prompts/docx-parser-system.md (100%) rename {src/integrations/ai => packages/ai/src}/prompts/docx-parser-user.md (100%) rename {src/integrations/ai => packages/ai/src}/prompts/pdf-parser-system.md (100%) rename {src/integrations/ai => packages/ai/src}/prompts/pdf-parser-user.md (100%) create mode 100644 packages/ai/src/resume/extraction-template.ts create mode 100644 packages/ai/src/resume/sanitize.ts create mode 100644 packages/ai/src/store.ts rename {src/integrations/ai => packages/ai/src}/tools/patch-resume.ts (55%) rename {src/integrations/ai => packages/ai/src}/types.ts (53%) create mode 100644 packages/ai/tsconfig.json create mode 100644 packages/ai/vitest.config.ts create mode 100644 packages/api/.gitignore create mode 100644 packages/api/package.json create mode 100644 packages/api/src/context.ts create mode 100644 packages/api/src/dto/resume.ts create mode 100644 packages/api/src/helpers/resume-access.ts create mode 100644 packages/api/src/middleware/rate-limit/index.ts create mode 100644 packages/api/src/routers/ai.ts create mode 100644 packages/api/src/routers/auth.ts create mode 100644 packages/api/src/routers/flags.ts create mode 100644 packages/api/src/routers/index.ts create mode 100644 packages/api/src/routers/resume.ts create mode 100644 packages/api/src/routers/statistics.ts create mode 100644 packages/api/src/routers/storage.ts create mode 100644 packages/api/src/services/ai.ts create mode 100644 packages/api/src/services/auth.ts create mode 100644 packages/api/src/services/flags.ts create mode 100644 packages/api/src/services/resume-access-policy.ts create mode 100644 packages/api/src/services/resume.ts create mode 100644 packages/api/src/services/statistics.ts create mode 100644 packages/api/src/services/storage.ts create mode 100644 packages/api/tsconfig.json create mode 100644 packages/api/vitest.config.ts create mode 100644 packages/auth/.gitignore create mode 100644 packages/auth/package.json create mode 100644 packages/auth/src/config.ts create mode 100644 packages/auth/src/functions.ts rename {src/integrations/auth => packages/auth/src}/types.ts (74%) create mode 100644 packages/auth/tsconfig.json create mode 100644 packages/auth/vitest.config.ts create mode 100644 packages/config/package.json create mode 100644 packages/config/tsconfig.base.json create mode 100644 packages/config/vitest.config.ts create mode 100644 packages/db/.gitignore create mode 100644 packages/db/drizzle.config.ts create mode 100644 packages/db/package.json create mode 100644 packages/db/src/client.ts create mode 100644 packages/db/src/relations.ts create mode 100644 packages/db/src/schema/auth.ts create mode 100644 packages/db/src/schema/index.ts create mode 100644 packages/db/src/schema/resume.ts create mode 100644 packages/db/tsconfig.json create mode 100644 packages/db/vitest.config.ts create mode 100644 packages/email/package.json create mode 100644 packages/email/src/templates/auth.tsx rename {src/integrations/email => packages/email/src}/templates/reset-password.tsx (53%) create mode 100644 packages/email/src/templates/verify-email-change.tsx rename {src/integrations/email => packages/email/src}/templates/verify-email.tsx (53%) create mode 100644 packages/email/src/transport.ts create mode 100644 packages/email/tsconfig.json create mode 100644 packages/email/vitest.config.ts create mode 100644 packages/env/package.json create mode 100644 packages/env/src/server.ts create mode 100644 packages/env/tsconfig.json create mode 100644 packages/env/vitest.config.ts create mode 100644 packages/fonts/package.json create mode 100644 packages/fonts/src/index.ts create mode 100644 packages/fonts/src/webfontlist.json create mode 100644 packages/fonts/tsconfig.json create mode 100644 packages/fonts/vitest.config.ts create mode 100644 packages/import/package.json create mode 100644 packages/import/src/json-resume.tsx create mode 100644 packages/import/src/reactive-resume-json.tsx create mode 100644 packages/import/src/reactive-resume-v4-json.tsx create mode 100644 packages/import/tsconfig.json create mode 100644 packages/import/vitest.config.ts create mode 100644 packages/pdf/package.json create mode 100644 packages/pdf/src/context.tsx create mode 100644 packages/pdf/src/document.tsx create mode 100644 packages/pdf/src/hooks/use-register-fonts.ts create mode 100644 packages/pdf/src/section-title.ts create mode 100644 packages/pdf/src/svg-raw.d.ts create mode 100644 packages/pdf/src/templates/azurill/AzurillPage.tsx create mode 100644 packages/pdf/src/templates/bronzor/BronzorPage.tsx create mode 100644 packages/pdf/src/templates/chikorita/ChikoritaPage.tsx create mode 100644 packages/pdf/src/templates/ditgar/DitgarPage.tsx create mode 100644 packages/pdf/src/templates/ditto/DittoPage.tsx create mode 100644 packages/pdf/src/templates/gengar/GengarPage.tsx create mode 100644 packages/pdf/src/templates/glalie/GlaliePage.tsx create mode 100644 packages/pdf/src/templates/index.ts create mode 100644 packages/pdf/src/templates/kakuna/KakunaPage.tsx create mode 100644 packages/pdf/src/templates/lapras/LaprasPage.tsx create mode 100644 packages/pdf/src/templates/leafish/LeafishPage.tsx create mode 100644 packages/pdf/src/templates/meowth/MeowthPage.tsx create mode 100644 packages/pdf/src/templates/onyx/OnyxPage.tsx create mode 100644 packages/pdf/src/templates/pikachu/PikachuPage.tsx create mode 100644 packages/pdf/src/templates/rhyhorn/RhyhornPage.tsx create mode 100644 packages/pdf/src/templates/shared/columns.ts create mode 100644 packages/pdf/src/templates/shared/context.tsx create mode 100644 packages/pdf/src/templates/shared/filtering.ts create mode 100644 packages/pdf/src/templates/shared/level-display.tsx create mode 100644 packages/pdf/src/templates/shared/meta-line.tsx create mode 100644 packages/pdf/src/templates/shared/metrics.ts create mode 100644 packages/pdf/src/templates/shared/page-size.ts create mode 100644 packages/pdf/src/templates/shared/picture.ts create mode 100644 packages/pdf/src/templates/shared/primitives.tsx create mode 100644 packages/pdf/src/templates/shared/rich-text.tsx create mode 100644 packages/pdf/src/templates/shared/sections.tsx create mode 100644 packages/pdf/src/templates/shared/styles.ts create mode 100644 packages/pdf/src/templates/shared/types.ts create mode 100644 packages/pdf/tsconfig.json create mode 100644 packages/pdf/vitest.config.ts create mode 100644 packages/runtime-externals/package.json create mode 100644 packages/schema/package.json create mode 100644 packages/schema/schema.json create mode 100644 packages/schema/src/icons.ts create mode 100644 packages/schema/src/page.ts create mode 100644 packages/schema/src/resume/analysis.ts create mode 100644 packages/schema/src/resume/data.ts create mode 100644 packages/schema/src/resume/default.ts create mode 100644 packages/schema/src/resume/sample.ts create mode 100644 packages/schema/src/templates.ts create mode 100644 packages/schema/tsconfig.json create mode 100644 packages/schema/vitest.config.ts create mode 100644 packages/scripts/database/reset.ts create mode 100644 packages/scripts/fonts/generate.ts create mode 100644 packages/scripts/fonts/types.ts create mode 100644 packages/scripts/package.json create mode 100644 packages/scripts/tsconfig.json create mode 100644 packages/ui/components.json create mode 100644 packages/ui/package.json create mode 100644 packages/ui/postcss.config.mjs create mode 100644 packages/ui/src/components/accordion.tsx create mode 100644 packages/ui/src/components/alert-dialog.tsx create mode 100644 packages/ui/src/components/alert.tsx create mode 100644 packages/ui/src/components/avatar.tsx create mode 100644 packages/ui/src/components/badge.tsx create mode 100644 packages/ui/src/components/brand-icon.tsx create mode 100644 packages/ui/src/components/button-group.tsx create mode 100644 packages/ui/src/components/button.tsx create mode 100644 packages/ui/src/components/card.tsx create mode 100644 packages/ui/src/components/checkbox.tsx create mode 100644 packages/ui/src/components/combobox.tsx create mode 100644 packages/ui/src/components/command.tsx create mode 100644 packages/ui/src/components/context-menu.tsx create mode 100644 packages/ui/src/components/dialog.tsx create mode 100644 packages/ui/src/components/direction.tsx create mode 100644 packages/ui/src/components/dropdown-menu.tsx create mode 100644 packages/ui/src/components/form.tsx create mode 100644 packages/ui/src/components/hover-card.tsx create mode 100644 packages/ui/src/components/input-group.tsx create mode 100644 packages/ui/src/components/input.tsx create mode 100644 packages/ui/src/components/kbd.tsx create mode 100644 packages/ui/src/components/label.tsx create mode 100644 packages/ui/src/components/popover.tsx create mode 100644 packages/ui/src/components/progress.tsx create mode 100644 packages/ui/src/components/resizable.tsx create mode 100644 packages/ui/src/components/scroll-area.tsx create mode 100644 packages/ui/src/components/separator.tsx create mode 100644 packages/ui/src/components/sheet.tsx create mode 100644 packages/ui/src/components/sidebar.tsx create mode 100644 packages/ui/src/components/skeleton.tsx create mode 100644 packages/ui/src/components/slider.tsx create mode 100644 packages/ui/src/components/sonner.tsx create mode 100644 packages/ui/src/components/spinner.tsx create mode 100644 packages/ui/src/components/switch.tsx create mode 100644 packages/ui/src/components/tabs.tsx create mode 100644 packages/ui/src/components/textarea.tsx create mode 100644 packages/ui/src/components/toggle.tsx create mode 100644 packages/ui/src/components/tooltip.tsx create mode 100644 packages/ui/src/globals.d.ts create mode 100644 packages/ui/src/hooks/use-confirm.tsx create mode 100644 packages/ui/src/hooks/use-controlled-state.tsx create mode 100644 packages/ui/src/hooks/use-mobile.tsx create mode 100644 packages/ui/src/hooks/use-prompt.tsx create mode 100644 packages/ui/src/styles/globals.css create mode 100644 packages/ui/tsconfig.json create mode 100644 packages/ui/vitest.config.ts create mode 100644 packages/utils/package.json create mode 100644 packages/utils/src/color.ts create mode 100644 packages/utils/src/date.ts rename {src/utils => packages/utils/src}/field.ts (77%) create mode 100644 packages/utils/src/file.ts create mode 100644 packages/utils/src/html.ts create mode 100644 packages/utils/src/level.ts create mode 100644 packages/utils/src/locale.ts create mode 100644 packages/utils/src/network-icons.ts create mode 100644 packages/utils/src/rate-limit.ts create mode 100644 packages/utils/src/resume/docx/builder.ts create mode 100644 packages/utils/src/resume/docx/html-to-docx.ts create mode 100644 packages/utils/src/resume/docx/index.ts create mode 100644 packages/utils/src/resume/docx/link-utils.ts create mode 100644 packages/utils/src/resume/docx/section-renderers.ts create mode 100644 packages/utils/src/resume/patch.ts create mode 100644 packages/utils/src/sanitize.ts rename {src/utils => packages/utils/src}/string.ts (72%) create mode 100644 packages/utils/src/style.ts create mode 100644 packages/utils/src/url-security.ts rename {src/utils => packages/utils/src}/url.ts (70%) create mode 100644 packages/utils/tsconfig.json create mode 100644 packages/utils/vitest.config.ts delete mode 100644 plugins/1.migrate.ts create mode 100644 pnpm-workspace.yaml delete mode 100644 public/funding.json delete mode 100644 public/manifest.webmanifest delete mode 100644 scripts/database/migrate.ts delete mode 100644 scripts/database/reset.ts delete mode 100644 scripts/fonts/generate.ts delete mode 100644 scripts/fonts/types.ts delete mode 100644 scripts/migration/resume.ts delete mode 100644 scripts/migration/test-database.ts delete mode 100644 scripts/migration/user.ts delete mode 100644 src/components/animation/comet-card.tsx delete mode 100644 src/components/animation/count-up.tsx delete mode 100644 src/components/animation/spotlight.tsx delete mode 100644 src/components/animation/text-mask.tsx delete mode 100644 src/components/command-palette/index.tsx delete mode 100644 src/components/command-palette/pages/base.tsx delete mode 100644 src/components/command-palette/pages/navigation.tsx delete mode 100644 src/components/command-palette/pages/preferences/index.tsx delete mode 100644 src/components/command-palette/pages/preferences/language.tsx delete mode 100644 src/components/command-palette/pages/preferences/theme.tsx delete mode 100644 src/components/command-palette/pages/resumes.tsx delete mode 100644 src/components/command-palette/store.ts delete mode 100644 src/components/input/chip-input.test.tsx delete mode 100644 src/components/input/chip-input.tsx delete mode 100644 src/components/input/color-picker.tsx delete mode 100644 src/components/input/github-stars-button.tsx delete mode 100644 src/components/input/icon-picker.tsx delete mode 100644 src/components/input/rich-input.module.css delete mode 100644 src/components/input/rich-input.tsx delete mode 100644 src/components/input/url-input.test.tsx delete mode 100644 src/components/input/url-input.tsx delete mode 100644 src/components/layout/breakpoint-indicator.tsx delete mode 100644 src/components/layout/error-screen.tsx delete mode 100644 src/components/layout/loading-screen.tsx delete mode 100644 src/components/layout/not-found-screen.tsx delete mode 100644 src/components/level/combobox.tsx delete mode 100644 src/components/level/display.test.tsx delete mode 100644 src/components/level/display.tsx delete mode 100644 src/components/locale/combobox.tsx delete mode 100644 src/components/resume/hooks/use-css-variables.tsx delete mode 100644 src/components/resume/hooks/use-webfonts.tsx delete mode 100644 src/components/resume/preview.module.css delete mode 100644 src/components/resume/preview.tsx delete mode 100644 src/components/resume/shared/get-section-component.tsx delete mode 100644 src/components/resume/shared/inline-header.tsx delete mode 100644 src/components/resume/shared/items/awards-item.tsx delete mode 100644 src/components/resume/shared/items/certifications-item.tsx delete mode 100644 src/components/resume/shared/items/cover-letter-item.tsx delete mode 100644 src/components/resume/shared/items/education-item.tsx delete mode 100644 src/components/resume/shared/items/experience-item.tsx delete mode 100644 src/components/resume/shared/items/interests-item.tsx delete mode 100644 src/components/resume/shared/items/languages-item.tsx delete mode 100644 src/components/resume/shared/items/profiles-item.tsx delete mode 100644 src/components/resume/shared/items/projects-item.tsx delete mode 100644 src/components/resume/shared/items/publications-item.tsx delete mode 100644 src/components/resume/shared/items/references-item.tsx delete mode 100644 src/components/resume/shared/items/skills-item.tsx delete mode 100644 src/components/resume/shared/items/summary-item.tsx delete mode 100644 src/components/resume/shared/items/volunteer-item.tsx delete mode 100644 src/components/resume/shared/linked-title.test.tsx delete mode 100644 src/components/resume/shared/linked-title.tsx delete mode 100644 src/components/resume/shared/page-icon.tsx delete mode 100644 src/components/resume/shared/page-level.tsx delete mode 100644 src/components/resume/shared/page-link.test.tsx delete mode 100644 src/components/resume/shared/page-link.tsx delete mode 100644 src/components/resume/shared/page-picture.tsx delete mode 100644 src/components/resume/shared/page-section.tsx delete mode 100644 src/components/resume/shared/page-summary.tsx delete mode 100644 src/components/resume/store/resume.test.ts delete mode 100644 src/components/resume/store/resume.ts delete mode 100644 src/components/resume/templates/azurill.tsx delete mode 100644 src/components/resume/templates/bronzor.tsx delete mode 100644 src/components/resume/templates/chikorita.tsx delete mode 100644 src/components/resume/templates/ditgar.tsx delete mode 100644 src/components/resume/templates/ditto.tsx delete mode 100644 src/components/resume/templates/gengar.tsx delete mode 100644 src/components/resume/templates/glalie.tsx delete mode 100644 src/components/resume/templates/kakuna.tsx delete mode 100644 src/components/resume/templates/lapras.tsx delete mode 100644 src/components/resume/templates/leafish.tsx delete mode 100644 src/components/resume/templates/meowth.tsx delete mode 100644 src/components/resume/templates/onyx.tsx delete mode 100644 src/components/resume/templates/pikachu.tsx delete mode 100644 src/components/resume/templates/rhyhorn.tsx delete mode 100644 src/components/resume/templates/types.ts delete mode 100644 src/components/theme/combobox.tsx delete mode 100644 src/components/theme/provider.tsx delete mode 100644 src/components/theme/toggle-button.tsx delete mode 100644 src/components/typography/combobox.tsx delete mode 100644 src/components/typography/font-display.tsx delete mode 100644 src/components/typography/types.ts delete mode 100644 src/components/typography/webfontlist.json delete mode 100644 src/components/ui/accordion.tsx delete mode 100644 src/components/ui/alert-dialog.tsx delete mode 100644 src/components/ui/alert.tsx delete mode 100644 src/components/ui/avatar.tsx delete mode 100644 src/components/ui/badge.tsx delete mode 100644 src/components/ui/brand-icon.test.tsx delete mode 100644 src/components/ui/brand-icon.tsx delete mode 100644 src/components/ui/button-group.tsx delete mode 100644 src/components/ui/button.tsx delete mode 100644 src/components/ui/combobox.tsx delete mode 100644 src/components/ui/command.tsx delete mode 100644 src/components/ui/context-menu.tsx delete mode 100644 src/components/ui/copyright.tsx delete mode 100644 src/components/ui/dialog.tsx delete mode 100644 src/components/ui/dropdown-menu.tsx delete mode 100644 src/components/ui/form.tsx delete mode 100644 src/components/ui/hover-card.tsx delete mode 100644 src/components/ui/input-group.tsx delete mode 100644 src/components/ui/input.tsx delete mode 100644 src/components/ui/kbd.tsx delete mode 100644 src/components/ui/label.tsx delete mode 100644 src/components/ui/popover.tsx delete mode 100644 src/components/ui/progress.tsx delete mode 100644 src/components/ui/resizable.tsx delete mode 100644 src/components/ui/scroll-area.tsx delete mode 100644 src/components/ui/separator.tsx delete mode 100644 src/components/ui/sheet.tsx delete mode 100644 src/components/ui/sidebar.tsx delete mode 100644 src/components/ui/skeleton.tsx delete mode 100644 src/components/ui/slider.tsx delete mode 100644 src/components/ui/sonner.tsx delete mode 100644 src/components/ui/spinner.tsx delete mode 100644 src/components/ui/switch.tsx delete mode 100644 src/components/ui/tabs.tsx delete mode 100644 src/components/ui/textarea.tsx delete mode 100644 src/components/ui/toggle.tsx delete mode 100644 src/components/ui/tooltip.tsx delete mode 100644 src/components/user/dropdown-menu.tsx delete mode 100644 src/constants/iso-countries.ts delete mode 100644 src/dialogs/api-key/create.tsx delete mode 100644 src/dialogs/auth/change-password.tsx delete mode 100644 src/dialogs/auth/disable-two-factor.tsx delete mode 100644 src/dialogs/auth/enable-two-factor.tsx delete mode 100644 src/dialogs/manager.tsx delete mode 100644 src/dialogs/resume/import.tsx delete mode 100644 src/dialogs/resume/index.tsx delete mode 100644 src/dialogs/resume/sections/award.tsx delete mode 100644 src/dialogs/resume/sections/certification.tsx delete mode 100644 src/dialogs/resume/sections/cover-letter.tsx delete mode 100644 src/dialogs/resume/sections/custom.tsx delete mode 100644 src/dialogs/resume/sections/education.tsx delete mode 100644 src/dialogs/resume/sections/experience.tsx delete mode 100644 src/dialogs/resume/sections/interest.tsx delete mode 100644 src/dialogs/resume/sections/language.tsx delete mode 100644 src/dialogs/resume/sections/profile.tsx delete mode 100644 src/dialogs/resume/sections/project.tsx delete mode 100644 src/dialogs/resume/sections/publication.tsx delete mode 100644 src/dialogs/resume/sections/reference.tsx delete mode 100644 src/dialogs/resume/sections/skill.tsx delete mode 100644 src/dialogs/resume/sections/summary-item.tsx delete mode 100644 src/dialogs/resume/sections/volunteer.tsx delete mode 100644 src/dialogs/resume/template/data.ts delete mode 100644 src/dialogs/resume/template/gallery.tsx delete mode 100644 src/dialogs/store.test.ts delete mode 100644 src/dialogs/store.ts delete mode 100644 src/hooks/use-confirm.test.tsx delete mode 100644 src/hooks/use-confirm.tsx delete mode 100644 src/hooks/use-controlled-state.test.tsx delete mode 100644 src/hooks/use-controlled-state.tsx delete mode 100644 src/hooks/use-form-blocker.test.tsx delete mode 100644 src/hooks/use-form-blocker.tsx delete mode 100644 src/hooks/use-mobile.test.tsx delete mode 100644 src/hooks/use-mobile.tsx delete mode 100644 src/hooks/use-prompt.test.tsx delete mode 100644 src/hooks/use-prompt.tsx delete mode 100644 src/integrations/ai/prompts/tailor-system.md delete mode 100644 src/integrations/ai/store.test.ts delete mode 100644 src/integrations/ai/store.ts delete mode 100644 src/integrations/auth/client.ts delete mode 100644 src/integrations/auth/config.ts delete mode 100644 src/integrations/auth/functions.ts delete mode 100644 src/integrations/drizzle/client.ts delete mode 100644 src/integrations/drizzle/helpers.ts delete mode 100644 src/integrations/drizzle/index.ts delete mode 100644 src/integrations/drizzle/schema.ts delete mode 100644 src/integrations/email/service.ts delete mode 100644 src/integrations/email/templates/auth.tsx delete mode 100644 src/integrations/email/templates/verify-email-change.tsx delete mode 100644 src/integrations/import/json-resume.test.ts delete mode 100644 src/integrations/import/json-resume.tsx delete mode 100644 src/integrations/import/reactive-resume-json.test.ts delete mode 100644 src/integrations/import/reactive-resume-json.tsx delete mode 100644 src/integrations/import/reactive-resume-v4-json.test.ts delete mode 100644 src/integrations/import/reactive-resume-v4-json.tsx delete mode 100644 src/integrations/jobs/factory.ts delete mode 100644 src/integrations/jobs/provider.ts delete mode 100644 src/integrations/jobs/providers/jsearch.test.ts delete mode 100644 src/integrations/jobs/providers/jsearch.ts delete mode 100644 src/integrations/jobs/store.test.ts delete mode 100644 src/integrations/jobs/store.ts delete mode 100644 src/integrations/orpc/client.ts delete mode 100644 src/integrations/orpc/context.ts delete mode 100644 src/integrations/orpc/dto/resume.ts delete mode 100644 src/integrations/orpc/helpers/resume-access.test.ts delete mode 100644 src/integrations/orpc/helpers/resume-access.ts delete mode 100644 src/integrations/orpc/rate-limit.ts delete mode 100644 src/integrations/orpc/router/ai.ts delete mode 100644 src/integrations/orpc/router/auth.ts delete mode 100644 src/integrations/orpc/router/flags.ts delete mode 100644 src/integrations/orpc/router/index.ts delete mode 100644 src/integrations/orpc/router/jobs.ts delete mode 100644 src/integrations/orpc/router/printer.ts delete mode 100644 src/integrations/orpc/router/resume.ts delete mode 100644 src/integrations/orpc/router/statistics.ts delete mode 100644 src/integrations/orpc/router/storage.ts delete mode 100644 src/integrations/orpc/services/ai.ts delete mode 100644 src/integrations/orpc/services/auth.ts delete mode 100644 src/integrations/orpc/services/flags.ts delete mode 100644 src/integrations/orpc/services/jobs.test.ts delete mode 100644 src/integrations/orpc/services/jobs.ts delete mode 100644 src/integrations/orpc/services/printer.ts delete mode 100644 src/integrations/orpc/services/resume.test.ts delete mode 100644 src/integrations/orpc/services/resume.ts delete mode 100644 src/integrations/orpc/services/statistics.ts delete mode 100644 src/integrations/orpc/services/storage.ts delete mode 100644 src/integrations/query/client.ts delete mode 100644 src/integrations/rate-limit/config.ts delete mode 100644 src/routeTree.gen.ts delete mode 100644 src/router.tsx delete mode 100644 src/routes/$username/$slug.tsx delete mode 100644 src/routes/[.]well-known/oauth-protected-resource.$.ts delete mode 100644 src/routes/[.]well-known/oauth-protected-resource.ts delete mode 100644 src/routes/__root.tsx delete mode 100644 src/routes/_home/-sections/donate.tsx delete mode 100644 src/routes/_home/-sections/faq.tsx delete mode 100644 src/routes/_home/-sections/features.tsx delete mode 100644 src/routes/_home/-sections/footer.tsx delete mode 100644 src/routes/_home/-sections/header.tsx delete mode 100644 src/routes/_home/-sections/hero.tsx delete mode 100644 src/routes/_home/-sections/prefooter.tsx delete mode 100644 src/routes/_home/-sections/product-hunt-banner.tsx delete mode 100644 src/routes/_home/-sections/statistics.tsx delete mode 100644 src/routes/_home/-sections/templates.tsx delete mode 100644 src/routes/_home/-sections/testimonials.tsx delete mode 100644 src/routes/_home/route.tsx delete mode 100644 src/routes/api/auth.$.ts delete mode 100644 src/routes/api/health.ts delete mode 100644 src/routes/api/openapi.$.ts delete mode 100644 src/routes/api/rpc.$.ts delete mode 100644 src/routes/auth/-components/social-auth.tsx delete mode 100644 src/routes/auth/forgot-password.tsx delete mode 100644 src/routes/auth/index.tsx delete mode 100644 src/routes/auth/login.tsx delete mode 100644 src/routes/auth/oauth.ts delete mode 100644 src/routes/auth/register.tsx delete mode 100644 src/routes/auth/reset-password.tsx delete mode 100644 src/routes/auth/resume-password.tsx delete mode 100644 src/routes/auth/route.tsx delete mode 100644 src/routes/auth/verify-2fa-backup.tsx delete mode 100644 src/routes/auth/verify-2fa.tsx delete mode 100644 src/routes/builder/$resumeId/-components/dock.tsx delete mode 100644 src/routes/builder/$resumeId/-components/edge.tsx delete mode 100644 src/routes/builder/$resumeId/-components/header.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/index.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/awards.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/basics.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/certifications.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/custom-fields.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/custom.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/education.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/experience.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/interests.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/languages.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/profiles.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/projects.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/publications.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/references.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/skills.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/summary.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/sections/volunteer.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/shared/section-base.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/shared/section-item.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/left/shared/section-menu.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/index.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/css-editor.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/css.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/export.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/information.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/layout/index.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/layout/pages.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/notes.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/page.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/resume-analysis.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/sharing.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/statistics.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/template.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/sections/typography.tsx delete mode 100644 src/routes/builder/$resumeId/-sidebar/right/shared/section-base.tsx delete mode 100644 src/routes/builder/$resumeId/-store/section.ts delete mode 100644 src/routes/builder/$resumeId/-store/sidebar.ts delete mode 100644 src/routes/builder/$resumeId/index.tsx delete mode 100644 src/routes/builder/$resumeId/route.tsx delete mode 100644 src/routes/dashboard/-components/header.tsx delete mode 100644 src/routes/dashboard/-components/sidebar.tsx delete mode 100644 src/routes/dashboard/index.tsx delete mode 100644 src/routes/dashboard/job-search/-components/filter-helpers.ts delete mode 100644 src/routes/dashboard/job-search/-components/job-card.tsx delete mode 100644 src/routes/dashboard/job-search/-components/job-detail.tsx delete mode 100644 src/routes/dashboard/job-search/-components/job-utils.ts delete mode 100644 src/routes/dashboard/job-search/-components/search-filters.test.ts delete mode 100644 src/routes/dashboard/job-search/-components/search-filters.tsx delete mode 100644 src/routes/dashboard/job-search/-components/tailor-dialog.tsx delete mode 100644 src/routes/dashboard/job-search/-components/use-job-search.ts delete mode 100644 src/routes/dashboard/job-search/index.tsx delete mode 100644 src/routes/dashboard/resumes/-components/cards/base-card.tsx delete mode 100644 src/routes/dashboard/resumes/-components/cards/create-card.tsx delete mode 100644 src/routes/dashboard/resumes/-components/cards/import-card.tsx delete mode 100644 src/routes/dashboard/resumes/-components/cards/resume-card.tsx delete mode 100644 src/routes/dashboard/resumes/-components/grid-view.tsx delete mode 100644 src/routes/dashboard/resumes/-components/list-view.tsx delete mode 100644 src/routes/dashboard/resumes/-components/menus/context-menu.tsx delete mode 100644 src/routes/dashboard/resumes/-components/menus/dropdown-menu.tsx delete mode 100644 src/routes/dashboard/resumes/index.tsx delete mode 100644 src/routes/dashboard/route.tsx delete mode 100644 src/routes/dashboard/settings/api-keys.tsx delete mode 100644 src/routes/dashboard/settings/authentication/-components/hooks.tsx delete mode 100644 src/routes/dashboard/settings/authentication/-components/passkeys.tsx delete mode 100644 src/routes/dashboard/settings/authentication/-components/password.tsx delete mode 100644 src/routes/dashboard/settings/authentication/-components/social-provider.tsx delete mode 100644 src/routes/dashboard/settings/authentication/-components/two-factor.tsx delete mode 100644 src/routes/dashboard/settings/authentication/index.tsx delete mode 100644 src/routes/dashboard/settings/danger-zone.tsx delete mode 100644 src/routes/dashboard/settings/integrations/-components/ai-section.tsx delete mode 100644 src/routes/dashboard/settings/integrations/-components/job-search-section.tsx delete mode 100644 src/routes/dashboard/settings/integrations/route.tsx delete mode 100644 src/routes/dashboard/settings/preferences.tsx delete mode 100644 src/routes/dashboard/settings/profile.tsx delete mode 100644 src/routes/mcp/-helpers/mcp-server-card.ts delete mode 100644 src/routes/mcp/-helpers/mcp-tool-names.ts delete mode 100644 src/routes/mcp/-helpers/prompts.ts delete mode 100644 src/routes/mcp/-helpers/resources.ts delete mode 100644 src/routes/mcp/-helpers/tool-annotations.ts delete mode 100644 src/routes/mcp/-helpers/tools.ts delete mode 100644 src/routes/mcp/index.ts delete mode 100644 src/routes/printer/$resumeId.tsx delete mode 100644 src/routes/schema[.]json.ts delete mode 100644 src/routes/uploads/$userId.$.tsx delete mode 100644 src/schema/icons.ts delete mode 100644 src/schema/jobs.test.ts delete mode 100644 src/schema/jobs.ts delete mode 100644 src/schema/page.ts delete mode 100644 src/schema/resume/analysis.ts delete mode 100644 src/schema/resume/data.ts delete mode 100644 src/schema/resume/sample.ts delete mode 100644 src/schema/schema.json delete mode 100644 src/schema/tailor.ts delete mode 100644 src/schema/templates.ts delete mode 100644 src/server.test.ts delete mode 100644 src/server.ts delete mode 100644 src/styles/globals.css delete mode 100644 src/utils/ai-template.test.ts delete mode 100644 src/utils/ai-template.ts delete mode 100644 src/utils/color.test.ts delete mode 100644 src/utils/color.ts delete mode 100644 src/utils/date.test.ts delete mode 100644 src/utils/date.ts delete mode 100644 src/utils/env.test.ts delete mode 100644 src/utils/env.ts delete mode 100644 src/utils/error-message.test.ts delete mode 100644 src/utils/error-message.ts delete mode 100644 src/utils/field.test.ts delete mode 100644 src/utils/file.test.ts delete mode 100644 src/utils/file.ts delete mode 100644 src/utils/fonts.test.ts delete mode 100644 src/utils/fonts.ts delete mode 100644 src/utils/html.test.ts delete mode 100644 src/utils/html.ts delete mode 100644 src/utils/level.test.ts delete mode 100644 src/utils/level.ts delete mode 100644 src/utils/locale.test.ts delete mode 100644 src/utils/locale.ts delete mode 100644 src/utils/network-icons.test.ts delete mode 100644 src/utils/network-icons.ts delete mode 100644 src/utils/password.test.ts delete mode 100644 src/utils/password.ts delete mode 100644 src/utils/printer-token.test.ts delete mode 100644 src/utils/printer-token.ts delete mode 100644 src/utils/resume/docx/builder.test.ts delete mode 100644 src/utils/resume/docx/builder.ts delete mode 100644 src/utils/resume/docx/html-to-docx.test.ts delete mode 100644 src/utils/resume/docx/html-to-docx.ts delete mode 100644 src/utils/resume/docx/index.test.ts delete mode 100644 src/utils/resume/docx/index.ts delete mode 100644 src/utils/resume/docx/link-utils.test.ts delete mode 100644 src/utils/resume/docx/link-utils.ts delete mode 100644 src/utils/resume/docx/section-renderers.test.ts delete mode 100644 src/utils/resume/docx/section-renderers.ts delete mode 100644 src/utils/resume/move-item.test.ts delete mode 100644 src/utils/resume/move-item.ts delete mode 100644 src/utils/resume/patch.test.ts delete mode 100644 src/utils/resume/patch.ts delete mode 100644 src/utils/resume/section-actions.test.ts delete mode 100644 src/utils/resume/section-actions.ts delete mode 100644 src/utils/resume/section.tsx delete mode 100644 src/utils/resume/tailor.test.ts delete mode 100644 src/utils/resume/tailor.ts delete mode 100644 src/utils/sanitize.test.ts delete mode 100644 src/utils/sanitize.ts delete mode 100644 src/utils/string.test.ts delete mode 100644 src/utils/style.test.ts delete mode 100644 src/utils/style.ts delete mode 100644 src/utils/theme.test.ts delete mode 100644 src/utils/url-security.test.ts delete mode 100644 src/utils/url-security.ts delete mode 100644 src/utils/url.test.ts delete mode 100644 src/vite-env.d.ts create mode 100644 turbo.json delete mode 100644 vite.config.ts create mode 100644 vitest.setup.ts create mode 100644 vitest.shared.ts diff --git a/.dockerignore b/.dockerignore index 538f2652b..936d30a9d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,15 +1,31 @@ -dist -docs +# VCS and editor state +.git +.gitignore +.cursor +.DS_Store + +# Local configuration and runtime state .env* /data + +# Dependency and package-manager caches +node_modules +**/node_modules +.pnpm-store + +# Build, test, and framework output +dist +**/dist .nitro -.cursor .output .vercel .netlify -coverage -.DS_Store .tanstack -node_modules +.turbo +**/.turbo +coverage +reports + +# Generated service-worker assets are rebuilt during the web build. public/sw.js -public/workbox-*.js \ No newline at end of file +public/workbox-*.js diff --git a/.env.example b/.env.example index f27227377..4b16cc8e0 100644 --- a/.env.example +++ b/.env.example @@ -1,18 +1,14 @@ -# --- Server --- -TZ="Etc/UTC" +# --- Application --- +# Port used by the web server in local development and self-hosted containers. +PORT="3000" + +# Public URL where the app is served. Used for auth callbacks, OAuth issuer URLs, +# OpenGraph metadata, and absolute upload URLs. APP_URL="http://localhost:3000" -# Optional, uses APP_URL by default -# PLEASE READ: This should be set to an internal URL (like http://host.docker.internal:3000 or http://{docker_service}:3000) -# to let the browser navigate to a non-public instance of Reactive Resume. -# This is required when the printer service is running inside Docker, and cannot reach the app via the APP URL, -# which is usually when the APP_URL is localhost or a local network IP/hostname. -PRINTER_APP_URL="http://host.docker.internal:3000" - -# --- Printer --- -PRINTER_ENDPOINT="ws://localhost:4000?token=change-me" - # --- Database (PostgreSQL) --- +# PostgreSQL connection URL. In Docker Compose, the hostname is usually `postgres`; +# when running directly on your machine, `localhost` is typical. DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" # --- Authentication --- @@ -20,34 +16,45 @@ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" AUTH_SECRET="change-me-to-a-secure-secret-key-in-production" # Better Auth Dashboard (optional) +# Enables the Better Auth Dashboard plugin when set, you probably don't need this. BETTER_AUTH_API_KEY="" # Social Auth (Google, optional) +# Set both values to enable Google sign-in. GOOGLE_CLIENT_ID="" GOOGLE_CLIENT_SECRET="" # Social Auth (GitHub, optional) +# Set both values to enable GitHub sign-in. GITHUB_CLIENT_ID="" GITHUB_CLIENT_SECRET="" # Social Auth (LinkedIn, optional) +# Set both values to enable LinkedIn sign-in. LINKEDIN_CLIENT_ID="" LINKEDIN_CLIENT_SECRET="" # Custom OAuth Provider (optional) +# Set OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET plus either OAUTH_DISCOVERY_URL or +# the three manual endpoint URLs below. OAUTH_PROVIDER_NAME="" OAUTH_CLIENT_ID="" OAUTH_CLIENT_SECRET="" OAUTH_DISCOVERY_URL="" OAUTH_AUTHORIZATION_URL="" +OAUTH_TOKEN_URL="" +OAUTH_USER_INFO_URL="" + +# Space-separated scopes requested from the custom OAuth provider. +OAUTH_SCOPES="openid profile email" + +# Comma-separated extra hosts/origins allowed for dynamic OAuth client redirect URIs. +# By default, only the APP_URL origin is allowed. OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS="" -# AI provider base URL allowlist (optional, comma-separated hosts/origins) -# Example: api.openai.com,https://gateway.ai.vercel.com -AI_ALLOWED_BASE_URLS="" - # --- Email (optional) --- -# If all keys are disabled, the app logs the email to be sent to the console instead. +# If SMTP_HOST, SMTP_USER, SMTP_PASS, or SMTP_FROM is missing, the app logs the +# email to the console instead. SMTP_HOST="localhost" SMTP_PORT="1025" SMTP_USER="" @@ -68,14 +75,11 @@ S3_BUCKET="reactive-resume" S3_FORCE_PATH_STYLE="true" # --- Feature Flags --- -# This flag bypasses the check that the endpoint to fetch data for the printing of PDFs `getByIdForPrinter`, is only accessible from the server. -# Useful for when you want to debug the /printer/{resumeId} route to quickly take a peek at the page that is sent to the printer. -FLAG_DEBUG_PRINTER="false" - # This flag disables new signups, both on the web app and the server. FLAG_DISABLE_SIGNUPS="false" -# This flag disables email/password login. Disables email verification, forgot password, and reset password flows. Users can still sign up via social auth (Google/GitHub/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true. +# This flag disables email/password login. Disables email verification, forgot password, and reset password flows. +# Users can still sign up via social auth (Google/GitHub/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true. FLAG_DISABLE_EMAIL_AUTH="false" # This flag disables the image processing. @@ -84,10 +88,11 @@ FLAG_DISABLE_IMAGE_PROCESSING="false" # --- Others --- # Google Cloud API Key (optional) -# This is not used within Reactive Resume, but in src/scripts/fonts/generate.ts to generate a list of fonts served by Google Fonts. -# Note: Make sure "Google Fonts Developer API" is unrestricted. +# For font-list generation tooling. +# Requires "Google Fonts Developer API" to be enabled. GOOGLE_CLOUD_API_KEY="" -# --- Crowdin --- +# Crowdin (optional) +# For translation tooling. CROWDIN_PROJECT_ID="" -CROWDIN_API_TOKEN="" \ No newline at end of file +CROWDIN_API_TOKEN="" diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index c65231d10..000000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -locales/*.po linguist-generated=true \ No newline at end of file diff --git a/.github/.well-known/funding.json b/.github/.well-known/funding.json index bc2b25393..37b1f500f 100644 --- a/.github/.well-known/funding.json +++ b/.github/.well-known/funding.json @@ -1,68 +1,68 @@ { - "$schema": "https://fundingjson.org/schema/v1.1.0.json", - "version": "v1.1.0", - "entity": { - "type": "individual", - "role": "maintainer", - "name": "Amruth Pillai", - "email": "im.amruth@gmail.com", - "description": "Software Engineer", - "webpageUrl": { - "url": "https://rxresu.me/funding.json" - } - }, - "projects": [ - { - "guid": "reactive-resume", - "name": "Reactive Resume", - "description": "A free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume.", - "webpageUrl": { - "url": "https://rxresu.me" - }, - "repositoryUrl": { - "url": "https://github.com/amruthpillai/reactive-resume", - "wellKnown": "https://github.com/amruthpillai/reactive-resume/blob/main/.github/.well-known/funding-manifest-urls" - }, - "licenses": ["spdx:MIT"], - "tags": ["data", "design", "productivity", "resume-builder"] - } - ], - "funding": { - "plans": [ - { - "guid": "sponsor", - "status": "active", - "name": "Sponsor", - "description": "Support the project on a recurring basis by becoming a sponsor.", - "amount": 10, - "currency": "EUR", - "frequency": "monthly", - "channels": ["github", "open-collective"] - }, - { - "guid": "donation", - "status": "active", - "name": "Donation", - "description": "Show your support for the project by making a one-time donation.", - "amount": 0, - "currency": "EUR", - "frequency": "one-time", - "channels": ["github", "open-collective"] - } - ], - "channels": [ - { - "guid": "github", - "type": "payment-provider", - "description": "GitHub Sponsors", - "address": "https://github.com/sponsors/AmruthPillai" - }, - { - "guid": "open-collective", - "type": "payment-provider", - "description": "Open Collective", - "address": "https://opencollective.com/reactive-resume" - } - ] - } + "$schema": "https://fundingjson.org/schema/v1.1.0.json", + "version": "v1.1.0", + "entity": { + "type": "individual", + "role": "maintainer", + "name": "Amruth Pillai", + "email": "hello@amruthpillai.com", + "description": "Software Engineer", + "webpageUrl": { + "url": "https://rxresu.me/funding.json" + } + }, + "projects": [ + { + "guid": "reactive-resume", + "name": "Reactive Resume", + "description": "A free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume.", + "webpageUrl": { + "url": "https://rxresu.me" + }, + "repositoryUrl": { + "url": "https://github.com/amruthpillai/reactive-resume", + "wellKnown": "https://github.com/amruthpillai/reactive-resume/blob/main/.github/.well-known/funding-manifest-urls" + }, + "licenses": ["spdx:MIT"], + "tags": ["data", "design", "productivity", "resume-builder"] + } + ], + "funding": { + "plans": [ + { + "guid": "sponsor", + "status": "active", + "name": "Sponsor", + "description": "Support the project on a recurring basis by becoming a sponsor.", + "amount": 10, + "currency": "EUR", + "frequency": "monthly", + "channels": ["github", "open-collective"] + }, + { + "guid": "donation", + "status": "active", + "name": "Donation", + "description": "Show your support for the project by making a one-time donation.", + "amount": 0, + "currency": "EUR", + "frequency": "one-time", + "channels": ["github", "open-collective"] + } + ], + "channels": [ + { + "guid": "github", + "type": "payment-provider", + "description": "GitHub Sponsors", + "address": "https://github.com/sponsors/AmruthPillai" + }, + { + "guid": "open-collective", + "type": "payment-provider", + "description": "Open Collective", + "address": "https://opencollective.com/reactive-resume" + } + ] + } } diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 437e06a9f..5a491c6ad 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -19,20 +19,26 @@ jobs: - name: Checkout Repository uses: actions/checkout@v6 - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 + - name: Install pnpm + uses: pnpm/action-setup@v6 + + - name: Setup Node + uses: actions/setup-node@v6 with: - node-version: "24" - cache: true + node-version: "lts/*" + cache: "pnpm" - name: Install Dependencies - run: vp install --frozen-lockfile + run: pnpm install --frozen-lockfile - - name: Run Tests - run: vp test + - name: Remove Unused Dependencies + run: pnpm knip --fix - - name: Run Lint and Format - run: vp check --fix + - name: Lint and Format + run: pnpm check + + - name: Extract Translations + run: pnpm lingui:extract - name: Autofix uses: autofix-ci/action@7a166d7532b277f34e16238930461bf77f9d7ed8 diff --git a/.gitignore b/.gitignore index 9427847a8..a2fb422e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,51 @@ -dist -.env* -/data -.nitro -.agents +# Dependencies +node_modules +.pnpm-store + +# Build Outputs .output .vercel -.cursor -.claude -TODO.md -coverage -.netlify -.DS_Store -.tanstack -node_modules +.wrangler + +# Environment Variables +.env* !.env.example -public/sw.js -public/sw.js.map -scripts/**/*.json -public/workbox-*.js -public/workbox-*.js.map \ No newline at end of file + +# IDEs and Editors +*~ +*.swp +*.swo +.DS_Store +.idea +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# Log Files +logs +*.log* + +# Turborepo +.turbo + +# TanStack Start +.tanstack + +# Testing +coverage +reports + +# Cache +tmp +temp +.cache + +# AI +.codex +.claude +.cursor + +# Data +apps/web/data diff --git a/.ncurc.cjs b/.ncurc.cjs index 2998a31a4..80a12c400 100644 --- a/.ncurc.cjs +++ b/.ncurc.cjs @@ -1,17 +1,15 @@ // @ts-check -const nextPackages = ["@monaco-editor/react"]; - const betaPackages = ["drizzle-orm", "drizzle-kit", "drizzle-zod"]; /** @type {import('npm-check-updates').RunOptions} */ module.exports = { - upgrade: true, - install: "always", - packageManager: "pnpm", - target: (packageName) => { - if (nextPackages.includes(packageName)) return "@next"; - if (betaPackages.includes(packageName)) return "@beta"; - return "latest"; - }, + upgrade: true, + workspaces: true, + install: "always", + packageManager: "pnpm", + target: (packageName) => { + if (betaPackages.includes(packageName)) return "@beta"; + return "latest"; + }, }; diff --git a/.vite-hooks/_/commit-msg b/.vite-hooks/_/commit-msg new file mode 100755 index 000000000..6f7c3d996 --- /dev/null +++ b/.vite-hooks/_/commit-msg @@ -0,0 +1,71 @@ +#!/bin/sh + +if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then + set -x +fi + +if [ "$LEFTHOOK" = "0" ]; then + exit 0 +fi + +call_lefthook() +{ + if test -n "$LEFTHOOK_BIN" + then + "$LEFTHOOK_BIN" "$@" + elif lefthook -h >/dev/null 2>&1 + then + lefthook "$@" + elif /Users/amruth/Projects/reactive-resume/node_modules/.pnpm/lefthook-darwin-arm64@2.1.6/node_modules/lefthook-darwin-arm64/bin/lefthook -h >/dev/null 2>&1 + then + /Users/amruth/Projects/reactive-resume/node_modules/.pnpm/lefthook-darwin-arm64@2.1.6/node_modules/lefthook-darwin-arm64/bin/lefthook "$@" + else + dir="$(git rev-parse --show-toplevel)" + osArch=$(uname | tr '[:upper:]' '[:lower:]') + cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/') + if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" + then + "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@" + elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" + then + "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@" + elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" + then + "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@" + elif test -f "$dir/node_modules/lefthook/bin/index.js" + then + "$dir/node_modules/lefthook/bin/index.js" "$@" + elif go tool lefthook -h >/dev/null 2>&1 + then + go tool lefthook "$@" + elif bundle exec lefthook -h >/dev/null 2>&1 + then + bundle exec lefthook "$@" + elif yarn lefthook -h >/dev/null 2>&1 + then + yarn lefthook "$@" + elif pnpm lefthook -h >/dev/null 2>&1 + then + pnpm lefthook "$@" + elif swift package lefthook >/dev/null 2>&1 + then + swift package --build-path .build/lefthook --disable-sandbox lefthook "$@" + elif command -v mint >/dev/null 2>&1 + then + mint run csjones/lefthook-plugin "$@" + elif uv run lefthook -h >/dev/null 2>&1 + then + uv run lefthook "$@" + elif mise exec -- lefthook -h >/dev/null 2>&1 + then + mise exec -- lefthook "$@" + elif devbox run lefthook -h >/dev/null 2>&1 + then + devbox run lefthook "$@" + else + echo "Can't find lefthook in PATH" + fi + fi +} + +call_lefthook run "commit-msg" "$@" diff --git a/.vite-hooks/_/pre-commit b/.vite-hooks/_/pre-commit new file mode 100755 index 000000000..38d25d166 --- /dev/null +++ b/.vite-hooks/_/pre-commit @@ -0,0 +1,71 @@ +#!/bin/sh + +if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then + set -x +fi + +if [ "$LEFTHOOK" = "0" ]; then + exit 0 +fi + +call_lefthook() +{ + if test -n "$LEFTHOOK_BIN" + then + "$LEFTHOOK_BIN" "$@" + elif lefthook -h >/dev/null 2>&1 + then + lefthook "$@" + elif /Users/amruth/Projects/reactive-resume/node_modules/.pnpm/lefthook-darwin-arm64@2.1.6/node_modules/lefthook-darwin-arm64/bin/lefthook -h >/dev/null 2>&1 + then + /Users/amruth/Projects/reactive-resume/node_modules/.pnpm/lefthook-darwin-arm64@2.1.6/node_modules/lefthook-darwin-arm64/bin/lefthook "$@" + else + dir="$(git rev-parse --show-toplevel)" + osArch=$(uname | tr '[:upper:]' '[:lower:]') + cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/') + if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" + then + "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@" + elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" + then + "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@" + elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" + then + "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@" + elif test -f "$dir/node_modules/lefthook/bin/index.js" + then + "$dir/node_modules/lefthook/bin/index.js" "$@" + elif go tool lefthook -h >/dev/null 2>&1 + then + go tool lefthook "$@" + elif bundle exec lefthook -h >/dev/null 2>&1 + then + bundle exec lefthook "$@" + elif yarn lefthook -h >/dev/null 2>&1 + then + yarn lefthook "$@" + elif pnpm lefthook -h >/dev/null 2>&1 + then + pnpm lefthook "$@" + elif swift package lefthook >/dev/null 2>&1 + then + swift package --build-path .build/lefthook --disable-sandbox lefthook "$@" + elif command -v mint >/dev/null 2>&1 + then + mint run csjones/lefthook-plugin "$@" + elif uv run lefthook -h >/dev/null 2>&1 + then + uv run lefthook "$@" + elif mise exec -- lefthook -h >/dev/null 2>&1 + then + mise exec -- lefthook "$@" + elif devbox run lefthook -h >/dev/null 2>&1 + then + devbox run lefthook "$@" + else + echo "Can't find lefthook in PATH" + fi + fi +} + +call_lefthook run "pre-commit" "$@" diff --git a/.vite-hooks/pre-commit b/.vite-hooks/pre-commit deleted file mode 100755 index 85fb65b4f..000000000 --- a/.vite-hooks/pre-commit +++ /dev/null @@ -1 +0,0 @@ -vp staged diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 74ed08e01..0e6f72ac0 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,3 @@ { - "recommendations": [ - "oxc.oxc-vscode", - "bradlc.vscode-tailwindcss", - "lokalise.i18n-ally", - "typescriptteam.native-preview", - "VoidZero.vite-plus-extension-pack" - ] + "recommendations": ["biomejs.biome", "bradlc.vscode-tailwindcss", "typescriptteam.native-preview"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index f7cad65e1..a028ad7be 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,42 +1,31 @@ { - "biome.enabled": false, - "editor.defaultFormatter": "oxc.oxc-vscode", - "editor.codeActionsOnSave": { - "source.fixAll.oxc": "explicit" - }, - "editor.formatOnSave": true, - "editor.formatOnSaveMode": "file", - "files.associations": { - "*.css": "tailwindcss" - }, - "files.readonlyInclude": { - "**/routeTree.gen.ts": true, - "pnpm-lock.yaml": true - }, - "files.watcherExclude": { - "**/routeTree.gen.ts": true, - "locales/**.po": true, - "pnpm-lock.yaml": true - }, - "i18n-ally.enabledParsers": ["po"], - "i18n-ally.localesPaths": ["locales"], - "i18n-ally.sourceLanguage": "en-US", - "oxc.enable.oxfmt": true, - "oxc.enable.oxlint": true, - "oxc.configPath": "./vite.config.ts", - "oxc.fmt.configPath": "./vite.config.ts", - "search.exclude": { - "**/routeTree.gen.ts": true, - "locales/**.po": true, - "pnpm-lock.yaml": true - }, - "tailwindCSS.classFunctions": ["cn", "cva"], - "tailwindCSS.experimental.classRegex": [ - ["class:\\s*?[\"'`]([^\"'`]*).*?,"], - ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], - ["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] - ], - "tailwindCSS.experimental.configFile": "src/styles/globals.css", - "typescript.experimental.useTsgo": true, - "i18n-ally.keystyle": "nested" + "biome.enabled": true, + "editor.codeActionsOnSave": { + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" + }, + "editor.defaultFormatter": "biomejs.biome", + "files.readonlyInclude": { + "**/locales/**.po": true, + "**/routeTree.gen.ts": true, + "pnpm-lock.yaml": true + }, + "files.watcherExclude": { + "**/locales/**.po": true, + "**/routeTree.gen.ts": true, + "pnpm-lock.yaml": true + }, + "search.exclude": { + "**/locales/**.po": true, + "**/routeTree.gen.ts": true, + "pnpm-lock.yaml": true + }, + "tailwindCSS.classFunctions": ["cn", "cva"], + "tailwindCSS.experimental.classRegex": [ + ["class:\\s*?[\"'`]([^\"'`]*).*?,"], + ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], + ["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] + ], + "tailwindCSS.experimental.configFile": "src/styles/globals.css", + "typescript.experimental.useTsgo": true } diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 02de9804e..000000000 --- a/AGENTS.md +++ /dev/null @@ -1,131 +0,0 @@ -## Overview - -Reactive Resume is a single-package full-stack TypeScript app (not a monorepo) built with [TanStack Start](https://tanstack.com/start/latest/docs/framework/react/overview) (React, Vite, Nitro). It serves both frontend and API on port 3000. - -This project uses [Vite+](https://vite.dev/blog/announcing-viteplus), a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. All modules should be imported from the `vite-plus` dependency (e.g., `import { defineConfig } from 'vite-plus'` or `import { expect, test, vi } from 'vite-plus/test'`). - -## Key Libraries - -| Area | Library | Docs | -| -------------------- | ------------------------------------------------------------------------ | ---------------------------------- | -| Frontend framework | React | https://react.dev | -| Full-stack framework | TanStack Start | https://tanstack.com/start/latest | -| Router | TanStack React Router | https://tanstack.com/router/latest | -| Server state | TanStack React Query | https://tanstack.com/query/latest | -| Client state | Zustand (+ Zundo for undo/redo, Immer for immutable updates) | https://zustand.docs.pmnd.rs | -| Type-safe API | oRPC | https://orpc.unnoq.com | -| Database ORM | Drizzle ORM (PostgreSQL) | https://orm.drizzle.team | -| Authentication | Better Auth (+ Drizzle adapter, OAuth provider, API keys, 2FA, Passkeys) | https://www.better-auth.com | -| Styling | Tailwind CSS | https://tailwindcss.com | -| UI Components | shadcn/ui (built on Base UI) | https://ui.shadcn.com | -| Icons | Phosphor Icons | https://phosphoricons.com | -| Forms | React Hook Form (+ Zod resolvers) | https://react-hook-form.com | -| Rich text editor | Tiptap | https://tiptap.dev | -| Validation | Zod | https://zod.dev | -| AI | Vercel AI SDK (OpenAI, Anthropic, Google, Ollama providers) | https://ai-sdk.dev | -| MCP | Model Context Protocol SDK | https://modelcontextprotocol.io | -| i18n | Lingui | https://lingui.dev | -| Animations | Motion (Framer Motion) | https://motion.dev | -| PDF export | Puppeteer Core (via Browserless) | https://pptr.dev | -| Drag and drop | dnd-kit | https://dndkit.com | -| Server engine | Nitro | https://nitro.build | -| PWA | Vite PWA Plugin | https://vite-pwa-org.netlify.app | -| Unused deps | Knip | https://knip.dev | - -## Project Structure - -``` -src/ - components/ UI, resume, layout, animation, theme, locale components - routes/ File-based routing (TanStack React Router) - integrations/ Feature modules (auth, drizzle, orpc, ai, email, jobs, mcp, storage) - schema/ Zod schemas for resume data validation - utils/ Utility functions (locale, theme, env, resume processing) - dialogs/ Modal/dialog components - hooks/ Custom React hooks - styles/ CSS and Tailwind configuration - stores/ Zustand stores (resume, AI, dialog, command palette) -migrations/ Drizzle database migrations -locales/ Lingui i18n message catalogs (47+ locales) -``` - -### Key Config Files - -- `vite.config.ts` — Vite + Nitro + TanStack Start + PWA + Tailwind + Lingui -- `drizzle.config.ts` — PostgreSQL dialect, schema at `./src/integrations/drizzle/schema.ts` -- `tsconfig.json` — ES2022, strict mode, path alias `@/*` → `./src/*` -- `lingui.config.ts` — i18n extraction and locale configuration -- `components.json` — shadcn CLI configuration - -### API Architecture - -- **oRPC API** (`/api/rpc/*`) — Type-safe RPC with routers for: `ai`, `auth`, `resume`, `storage`, `printer`, `jobs`, `statistics`, `flags`. Three procedure types: `publicProcedure`, `protectedProcedure`, `serverOnlyProcedure`. -- **Better Auth API** (`/api/auth/*`) — OAuth, session management, social provider callbacks. -- **MCP Server** (`/mcp/`) — Model Context Protocol with OAuth Bearer tokens and API key auth. Exposes resumes as resources and tools for resume CRUD. - -## Infrastructure Services - -Before running the dev server, Docker must be running with at least PostgreSQL. Start services via `compose.dev.yml`: - -```bash -sudo dockerd &>/var/log/dockerd.log & -sudo docker compose -f compose.dev.yml up -d postgres browserless -``` - -- **PostgreSQL** (port 5432) — required. The app auto-runs Drizzle migrations on startup via a Nitro plugin. -- **Browserless** (port 4000) — required for PDF export. Maps container port 3000 to host port 4000. - -## Environment Variables - -Copy `.env.example` to `.env` if not present. Key notes for local dev: - -- `APP_URL` — local dev server origin on port 3000. -- `PRINTER_APP_URL` — must use the Docker bridge gateway IP (not localhost) so the Browserless container can reach the app on the host. Get the IP with: `sudo docker network inspect reactive_resume_default --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}'` -- `PRINTER_ENDPOINT` — websocket URL to Browserless on host port 4000 with token `1234567890`. -- `DATABASE_URL` — PostgreSQL connection using `postgres:postgres` credentials on localhost:5432. -- S3/Storage and SMTP vars can be left empty — the app falls back to local filesystem and console-logged emails. - -## Common Commands - -`vp` is the global CLI for Vite+. Do not use pnpm/npm/yarn directly — Vite+ wraps the underlying package manager. - -| Task | Command | -| ----------------------------- | --------------------------------------------------------------- | -| Install dependencies | `vp install` | -| Dev server (port 3000) | `vp dev` | -| Lint (Oxlint, type-aware) | `vp lint --type-aware` | -| Format (Oxfmt) | `vp fmt` | -| Check (lint + format + types) | `vp check` | -| Typecheck | `pnpm typecheck` (uses tsgo) | -| Run tests | `vp test` | -| DB migrations | `pnpm db:generate` / `pnpm db:migrate` (auto-runs on dev start) | -| DB studio | `pnpm db:studio` | -| i18n extraction | `pnpm lingui:extract` | -| Add a dependency | `vp add ` | -| Remove a dependency | `vp remove ` | -| One-off binary | `vp dlx ` | -| Build for production | `vp build` | -| Preview production build | `vp preview` | -| Start production server | `pnpm start` | - -## Vite+ Pitfalls - -- **Do not use pnpm/npm/yarn directly** for package operations — use `vp add`, `vp remove`, `vp install`, etc. -- **Do not run `vp vitest` or `vp oxlint`** — they don't exist. Use `vp test` and `vp lint`. -- **Do not install Vitest, Oxlint, Oxfmt, or tsdown directly** — Vite+ bundles them. -- **Import from `vite-plus`**, not from `vite` or `vitest` directly (e.g., `import { defineConfig } from 'vite-plus'`). -- **Vite+ commands take precedence** over `package.json` scripts. If there's a naming conflict, use `vp run ')).toBe(""); - }); - - it("should strip event handlers", () => { - expect(sanitizeHtml('')).toBe(""); - }); - - it("should allow links with href", () => { - const html = 'link'; - expect(sanitizeHtml(html)).toContain('href="https://example.com"'); - }); - - it("should strip javascript: hrefs", () => { - const result = sanitizeHtml('click'); - expect(result).not.toContain("javascript:"); - }); - - it("should allow list elements", () => { - const html = "
  • one
  • two
"; - expect(sanitizeHtml(html)).toBe(html); - }); - - it("should allow table elements", () => { - const html = "
cell
"; - expect(sanitizeHtml(html)).toContain(""); - }); - - it("should preserve safe inline text colors", () => { - const html = '

colored

'; - const result = sanitizeHtml(html); - - expect(result).toContain(" { - it("should return empty string for empty input", () => { - expect(sanitizeCss("")).toBe(""); - }); - - it("should pass through normal CSS", () => { - expect(sanitizeCss("color: red;")).toBe("color: red;"); - }); - - it("should strip javascript: expressions", () => { - expect(sanitizeCss("background: javascript:alert(1)")).not.toContain("javascript:"); - }); - - it("should strip expression() calls", () => { - expect(sanitizeCss("width: expression(alert(1))")).not.toContain("expression("); - }); - - it("should strip behavior: property", () => { - expect(sanitizeCss("behavior: url(evil.htc)")).not.toContain("behavior:"); - }); - - it("should strip -moz-binding", () => { - expect(sanitizeCss("-moz-binding: url(evil.xml)")).not.toContain("-moz-binding:"); - }); - - it("should strip @import rules", () => { - const css = "@import url('https://evil.example/css'); .safe { color: red; }"; - const result = sanitizeCss(css); - expect(result).not.toContain("@import"); - expect(result).toContain(".safe"); - }); - - it("should strip network url() calls and keep regular declarations", () => { - const css = ".card { background: url('https://evil.example/bg.png'); color: #111; }"; - const result = sanitizeCss(css); - expect(result).not.toContain("url("); - expect(result).toMatch(/color\s*:\s*#111/); - }); - - it("should strip @font-face blocks", () => { - const css = - "@font-face { font-family: test; src: url('https://evil.example/font.woff2'); } .x { font-family: test; }"; - const result = sanitizeCss(css); - expect(result).not.toContain("@font-face"); - expect(result).toContain(".x"); - }); - - it("should strip css comments before scanning dangerous values", () => { - const css = ".x { background: java/**/script:alert(1); color: blue; }"; - const result = sanitizeCss(css); - expect(result).not.toContain("javascript:"); - expect(result).toContain("color"); - }); - - it("should decode escaped javascript protocol and strip it", () => { - const css = ".x { background: \\6a\\61\\76\\61\\73\\63\\72\\69\\70\\74:alert(1); color: green; }"; - const result = sanitizeCss(css); - expect(result).not.toContain("javascript:"); - expect(result).toContain("color"); - }); - - it("should decode escaped expression() and strip it", () => { - const css = ".x { width: \\65\\78\\70\\72\\65\\73\\73\\69\\6f\\6e(alert(1)); height: 10px; }"; - const result = sanitizeCss(css); - expect(result).not.toContain("expression("); - expect(result).toContain("height"); - }); - - it("should decode non-hex escapes without dropping characters", () => { - const css = ".x { content: \\(ok\\); color: teal; }"; - const result = sanitizeCss(css); - expect(result).toContain("content: (ok)"); - expect(result).toContain("color: teal"); - }); - - it("should strip data url payloads in url() functions", () => { - const css = ".x { background-image: url(data:text/html;base64,PHNjcmlwdD4=); color: black; }"; - const result = sanitizeCss(css); - expect(result).not.toContain("url("); - expect(result).toContain("color"); - }); - - it("should strip image-set and cross-fade function network payloads", () => { - const css = - ".x { background-image: image-set(url('https://evil.example/a.png') 1x); mask-image: cross-fade(url('https://evil.example/a.png'), url('https://evil.example/b.png')); color: purple; }"; - const result = sanitizeCss(css); - expect(result).not.toContain("image-set("); - expect(result).not.toContain("cross-fade("); - expect(result).toContain("color"); - }); -}); - -describe("isObject", () => { - it("should return true for plain objects", () => { - expect(isObject({})).toBe(true); - expect(isObject({ a: 1 })).toBe(true); - }); - - it("should return false for arrays", () => { - expect(isObject([])).toBe(false); - }); - - it("should return false for null", () => { - expect(isObject(null)).toBe(false); - }); - - it("should return false for primitives", () => { - expect(isObject("string")).toBe(false); - expect(isObject(42)).toBe(false); - expect(isObject(undefined)).toBe(false); - }); -}); diff --git a/src/utils/sanitize.ts b/src/utils/sanitize.ts deleted file mode 100644 index 320de0cda..000000000 --- a/src/utils/sanitize.ts +++ /dev/null @@ -1,99 +0,0 @@ -import DOMPurify, { type Config } from "dompurify"; - -const RICH_TEXT_CONFIG: Config = { - ALLOWED_TAGS: [ - "p", - "br", - "hr", - "span", - "div", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "strong", - "b", - "em", - "i", - "u", - "s", - "strike", - "mark", - "code", - "pre", - "ul", - "ol", - "li", - "table", - "thead", - "tbody", - "tfoot", - "tr", - "th", - "td", - "colgroup", - "col", - "a", - "blockquote", - ], - ALLOWED_ATTR: ["class", "style", "href", "target", "rel", "colspan", "rowspan", "data-type", "data-label"], - ALLOWED_URI_REGEXP: /^(?:(?:https?):\/\/|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i, - ADD_ATTR: ["target", "rel"], - ALLOW_DATA_ATTR: false, -}; - -const PLAIN_TEXT_SANITIZE_CONFIG: Config = { - ALLOWED_TAGS: [], - ALLOWED_ATTR: [], - KEEP_CONTENT: true, - RETURN_TRUSTED_TYPE: false, -}; - -function sanitizePlainTextContent(value: string): string { - return DOMPurify.sanitize(value, PLAIN_TEXT_SANITIZE_CONFIG) as string; -} - -function stripCssComments(value: string): string { - if (!value) return ""; - return value.replace(/\/\*[\s\S]*?\*\//g, ""); -} - -function decodeCssEscapes(value: string): string { - if (!value) return ""; - - return value.replace(/\\([0-9a-fA-F]{1,6})(?:\r\n|[ \t\r\n\f])?|\\(.)/g, (_match, hex, escapedChar) => { - if (hex) return String.fromCodePoint(parseInt(hex, 16)); - return escapedChar ?? ""; - }); -} - -export function sanitizeHtml(html: string): string { - if (!html) return ""; - return DOMPurify.sanitize(html, { ...RICH_TEXT_CONFIG, RETURN_TRUSTED_TYPE: false }) as string; -} - -export function sanitizeCss(css: string): string { - if (!css) return ""; - - const normalized = decodeCssEscapes(stripCssComments(css)); - - const preSanitized = normalized - .replace(/javascript\s*:/gi, "") - .replace(/expression\s*\(/gi, "") - .replace(/behavior\s*:[^;}]*/gi, "") - .replace(/-moz-binding\s*:[^;}]*/gi, ""); - - const sanitized = preSanitized - .replace(/@import[^;]*;/gi, "") - .replace(/@font-face\s*\{[^}]*\}/gi, "") - .replace(/\b(?:url|image-set|cross-fade)\s*\([^)]*\)/gi, "") - .replace(/^\s*src\s*:[^;]*;?/gim, ""); - - return sanitizePlainTextContent(sanitized); -} - -export function isObject(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} diff --git a/src/utils/string.test.ts b/src/utils/string.test.ts deleted file mode 100644 index 9e67a6d81..000000000 --- a/src/utils/string.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { describe, expect, it } from "vite-plus/test"; - -import { generateRandomName, getInitials, slugify, stripHtml, toUsername } from "./string"; - -describe("slugify", () => { - it("should lowercase and hyphenate spaces", () => { - expect(slugify("Hello World")).toBe("hello-world"); - }); - - it("should strip special characters", () => { - expect(slugify("Résumé & CV!")).toBe("resume-and-cv"); - }); - - it("should not decamelize camelCase strings", () => { - expect(slugify("camelCase")).toBe("camelcase"); - }); - - it("should return empty string for empty input", () => { - expect(slugify("")).toBe(""); - }); -}); - -describe("getInitials", () => { - it("should return up to two uppercase initials", () => { - expect(getInitials("John Doe")).toBe("JD"); - }); - - it("should return one initial for a single name", () => { - expect(getInitials("Alice")).toBe("A"); - }); - - it("should take only the first two words", () => { - expect(getInitials("John Michael Doe")).toBe("JM"); - }); -}); - -describe("toUsername", () => { - it("should lowercase and strip disallowed characters", () => { - expect(toUsername("John Doe!")).toBe("johndoe"); - }); - - it("should keep dots, hyphens, and underscores", () => { - expect(toUsername("john.doe-name_ok")).toBe("john.doe-name_ok"); - }); - - it("should trim whitespace", () => { - expect(toUsername(" alice ")).toBe("alice"); - }); - - it("should truncate to 64 characters", () => { - const long = "a".repeat(100); - expect(toUsername(long)).toHaveLength(64); - }); -}); - -describe("stripHtml", () => { - it("should remove HTML tags and trim", () => { - expect(stripHtml("

Hello World

")).toBe("Hello World"); - }); - - it("should return empty string for undefined", () => { - expect(stripHtml(undefined)).toBe(""); - }); - - it("should return empty string for empty string", () => { - expect(stripHtml("")).toBe(""); - }); - - it("should handle nested tags", () => { - expect(stripHtml("
  • item
")).toBe("item"); - }); -}); - -describe("generateRandomName", () => { - it("should generate a 3-word capitalized name", () => { - const result = generateRandomName(); - const parts = result.split(" "); - - expect(parts).toHaveLength(3); - for (const part of parts) { - expect(part.length).toBeGreaterThan(0); - expect(part[0]).toBe(part[0].toUpperCase()); - } - }); -}); diff --git a/src/utils/style.test.ts b/src/utils/style.test.ts deleted file mode 100644 index 53fe467e9..000000000 --- a/src/utils/style.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, expect, it } from "vite-plus/test"; - -import { cn } from "./style"; - -describe("cn", () => { - it("merges simple class names", () => { - expect(cn("foo", "bar")).toBe("foo bar"); - }); - - it("handles conditional classes (falsy values ignored)", () => { - const falsyValue = false; - expect(cn("base", falsyValue && "hidden", "active")).toBe("base active"); - }); - - it("handles undefined and null", () => { - expect(cn("base", undefined, null, "end")).toBe("base end"); - }); - - it("merges Tailwind classes (last wins)", () => { - expect(cn("p-4", "p-8")).toBe("p-8"); - }); - - it("merges conflicting Tailwind utilities", () => { - expect(cn("text-red-500", "text-blue-500")).toBe("text-blue-500"); - }); - - it("keeps non-conflicting Tailwind classes", () => { - const result = cn("p-4", "m-2", "text-red-500"); - expect(result).toContain("p-4"); - expect(result).toContain("m-2"); - expect(result).toContain("text-red-500"); - }); - - it("handles empty input", () => { - expect(cn()).toBe(""); - }); - - it("handles array input", () => { - expect(cn(["foo", "bar"])).toBe("foo bar"); - }); - - it("handles object input", () => { - expect(cn({ foo: true, bar: false, baz: true })).toBe("foo baz"); - }); -}); diff --git a/src/utils/style.ts b/src/utils/style.ts deleted file mode 100644 index 365058ceb..000000000 --- a/src/utils/style.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type ClassValue, clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} diff --git a/src/utils/theme.test.ts b/src/utils/theme.test.ts deleted file mode 100644 index 5da36441c..000000000 --- a/src/utils/theme.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { describe, expect, it, vi } from "vite-plus/test"; - -vi.mock("@lingui/core/macro", () => ({ - msg: (strings: TemplateStringsArray) => ({ id: strings[0] }), -})); - -vi.mock("@tanstack/react-start", () => { - const chainable = () => new Proxy({}, { get: () => chainable }); - return { - createIsomorphicFn: chainable, - createServerFn: chainable, - }; -}); - -vi.mock("@tanstack/react-start/server", () => ({ - getCookie: () => undefined, - setCookie: () => {}, -})); - -vi.mock("js-cookie", () => ({ - default: { get: () => undefined }, -})); - -import { isTheme } from "./theme"; - -describe("isTheme", () => { - it("returns true for 'light'", () => { - expect(isTheme("light")).toBe(true); - }); - - it("returns true for 'dark'", () => { - expect(isTheme("dark")).toBe(true); - }); - - it("returns false for invalid theme strings", () => { - expect(isTheme("auto")).toBe(false); - expect(isTheme("system")).toBe(false); - expect(isTheme("")).toBe(false); - expect(isTheme("DARK")).toBe(false); - expect(isTheme("Light")).toBe(false); - }); -}); diff --git a/src/utils/url-security.test.ts b/src/utils/url-security.test.ts deleted file mode 100644 index 4afc7d51e..000000000 --- a/src/utils/url-security.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { describe, expect, it } from "vite-plus/test"; - -import { - isAllowedOAuthRedirectUri, - isAllowedExternalUrl, - isPrivateOrLoopbackHost, - parseAllowedHostList, - parseUrl, - sanitizeResumePictureUrl, -} from "./url-security"; - -describe("isPrivateOrLoopbackHost", () => { - it("returns true for localhost and private hosts", () => { - expect(isPrivateOrLoopbackHost("localhost")).toBe(true); - expect(isPrivateOrLoopbackHost("127.0.0.1")).toBe(true); - expect(isPrivateOrLoopbackHost("10.0.0.7")).toBe(true); - expect(isPrivateOrLoopbackHost("192.168.0.2")).toBe(true); - }); - - it("returns false for public hosts", () => { - expect(isPrivateOrLoopbackHost("example.com")).toBe(false); - expect(isPrivateOrLoopbackHost("8.8.8.8")).toBe(false); - }); - - it("returns true for private and link-local ipv6 addresses", () => { - expect(isPrivateOrLoopbackHost("::1")).toBe(true); - expect(isPrivateOrLoopbackHost("[::1]")).toBe(true); - expect(isPrivateOrLoopbackHost("fd00::1234")).toBe(true); - expect(isPrivateOrLoopbackHost("[fe80::1]")).toBe(true); - }); -}); - -describe("isAllowedExternalUrl", () => { - const allowedHosts = new Set(["api.openai.com", "https://gateway.ai.vercel.com"]); - - it("allows https URLs on allowed hosts", () => { - expect(isAllowedExternalUrl("https://api.openai.com/v1", allowedHosts)).toBe(true); - expect(isAllowedExternalUrl("https://gateway.ai.vercel.com/v1", allowedHosts)).toBe(true); - }); - - it("blocks local and non-https URLs", () => { - expect(isAllowedExternalUrl("http://api.openai.com/v1", allowedHosts)).toBe(false); - expect(isAllowedExternalUrl("https://localhost:11434/v1", allowedHosts)).toBe(false); - expect(isAllowedExternalUrl("https://10.0.0.1/v1", allowedHosts)).toBe(false); - }); - - it("blocks malformed URLs and credentialed URLs", () => { - expect(isAllowedExternalUrl("not-a-url", allowedHosts)).toBe(false); - expect(isAllowedExternalUrl("https://user:pass@api.openai.com/v1", allowedHosts)).toBe(false); - }); - - it("supports origin-only entries in the allow list", () => { - const originOnlyAllowedHosts = new Set(["https://example.org"]); - expect(isAllowedExternalUrl("https://example.org/v1/hello", originOnlyAllowedHosts)).toBe(true); - expect(isAllowedExternalUrl("https://sub.example.org/v1/hello", originOnlyAllowedHosts)).toBe(false); - }); -}); - -describe("isAllowedOAuthRedirectUri", () => { - const trustedOrigins = ["https://rxresu.me"]; - const allowedHosts = new Set(["https://client.example.com", "trusted.example.com"]); - - it("allows local loopback HTTP callbacks for native OAuth clients", () => { - expect(isAllowedOAuthRedirectUri("http://localhost:6188/callback", trustedOrigins, allowedHosts)).toBe(true); - expect(isAllowedOAuthRedirectUri("http://127.0.0.1:6188/callback", trustedOrigins, allowedHosts)).toBe(true); - expect(isAllowedOAuthRedirectUri("http://[::1]:6188/callback", trustedOrigins, allowedHosts)).toBe(true); - }); - - it("keeps rejecting unsafe HTTP and malformed redirects", () => { - expect(isAllowedOAuthRedirectUri("http://192.168.1.20:6188/callback", trustedOrigins, allowedHosts)).toBe(false); - expect(isAllowedOAuthRedirectUri("http://example.com/callback", trustedOrigins, allowedHosts)).toBe(false); - expect(isAllowedOAuthRedirectUri("not-a-url", trustedOrigins, allowedHosts)).toBe(false); - }); - - it("allows only trusted HTTPS redirects without credentials or fragments", () => { - expect(isAllowedOAuthRedirectUri("https://rxresu.me/callback", trustedOrigins, allowedHosts)).toBe(true); - expect(isAllowedOAuthRedirectUri("https://client.example.com/callback", trustedOrigins, allowedHosts)).toBe(true); - expect(isAllowedOAuthRedirectUri("https://trusted.example.com/callback", trustedOrigins, allowedHosts)).toBe(true); - expect(isAllowedOAuthRedirectUri("https://evil.example.com/callback", trustedOrigins, allowedHosts)).toBe(false); - expect(isAllowedOAuthRedirectUri("https://user:pass@rxresu.me/callback", trustedOrigins, allowedHosts)).toBe(false); - expect(isAllowedOAuthRedirectUri("https://rxresu.me/callback#token", trustedOrigins, allowedHosts)).toBe(false); - }); -}); - -describe("sanitizeResumePictureUrl", () => { - const appUrl = "https://rxresu.me"; - - it("keeps local uploads paths", () => { - expect(sanitizeResumePictureUrl("/uploads/user/pictures/a.webp", appUrl)).toBe("/uploads/user/pictures/a.webp"); - }); - - it("converts same-origin upload URLs to path-only values", () => { - expect(sanitizeResumePictureUrl("https://rxresu.me/uploads/u/p.jpg", appUrl)).toBe("/uploads/u/p.jpg"); - }); - - it("keeps query and hash for same-origin upload URLs", () => { - expect(sanitizeResumePictureUrl("https://rxresu.me/uploads/u/p.jpg?size=2#thumb", appUrl)).toBe( - "/uploads/u/p.jpg?size=2#thumb", - ); - }); - - it("rejects non-upload and cross-origin URLs", () => { - expect(sanitizeResumePictureUrl("https://example.com/pic.jpg", appUrl)).toBe(""); - expect(sanitizeResumePictureUrl("https://rxresu.me/other/pic.jpg", appUrl)).toBe(""); - }); - - it("rejects invalid URLs, invalid app URLs, and credentialed URLs", () => { - expect(sanitizeResumePictureUrl("not-a-url", appUrl)).toBe(""); - expect(sanitizeResumePictureUrl("ftp://rxresu.me/uploads/p.jpg", appUrl)).toBe(""); - expect(sanitizeResumePictureUrl("https://user:pass@rxresu.me/uploads/p.jpg", appUrl)).toBe(""); - expect(sanitizeResumePictureUrl("https://rxresu.me/uploads/p.jpg", "not-a-url")).toBe(""); - }); -}); - -describe("parse helpers", () => { - it("parseUrl returns URL object for valid URLs and null for invalid input", () => { - expect(parseUrl("https://example.com/path")?.hostname).toBe("example.com"); - expect(parseUrl("invalid url")).toBeNull(); - }); - - it("parseAllowedHostList normalizes host entries and handles empty input", () => { - const set = parseAllowedHostList(" API.OpenAI.com , https://gateway.ai.vercel.com ,,api.openai.com "); - expect(set.has("api.openai.com")).toBe(true); - expect(set.has("https://gateway.ai.vercel.com")).toBe(true); - expect(set.size).toBe(2); - - expect(parseAllowedHostList(undefined).size).toBe(0); - expect(parseAllowedHostList("").size).toBe(0); - }); -}); diff --git a/src/utils/url-security.ts b/src/utils/url-security.ts deleted file mode 100644 index a2b193835..000000000 --- a/src/utils/url-security.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { isIP } from "node:net"; - -function normalizeHostname(hostname: string) { - return hostname.trim().toLowerCase(); -} - -function stripIpv6Brackets(hostname: string): string { - return hostname.replace(/^\[/, "").replace(/\]$/, ""); -} - -function isLoopbackOrLocalHostname(hostname: string) { - const normalized = normalizeHostname(hostname); - return ( - normalized === "localhost" || normalized === "::1" || normalized === "[::1]" || normalized.endsWith(".localhost") - ); -} - -function isPrivateIPv4(hostname: string) { - const [first = 0, second = 0] = hostname.split(".").map((part) => Number.parseInt(part, 10)); - if (Number.isNaN(first) || Number.isNaN(second)) return false; - - if (first === 10) return true; - if (first === 127) return true; - if (first === 169 && second === 254) return true; - if (first === 172 && second >= 16 && second <= 31) return true; - if (first === 192 && second === 168) return true; - if (first === 0) return true; - - return false; -} - -function isPrivateIPv6(hostname: string) { - const normalized = stripIpv6Brackets(normalizeHostname(hostname)); - return ( - normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:") - ); -} - -export function isPrivateOrLoopbackHost(hostname: string) { - const normalized = stripIpv6Brackets(normalizeHostname(hostname)); - if (isLoopbackOrLocalHostname(normalized)) return true; - - const ipVersion = isIP(normalized); - if (ipVersion === 4) return isPrivateIPv4(normalized); - if (ipVersion === 6) return isPrivateIPv6(normalized); - - return false; -} - -function isOAuthLoopbackRedirectHost(hostname: string) { - const normalized = stripIpv6Brackets(normalizeHostname(hostname)); - return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1"; -} - -export function parseUrl(input: string) { - try { - return new URL(input); - } catch { - return null; - } -} - -export function parseAllowedHostList(value?: string) { - if (!value) return new Set(); - - const hosts = value - .split(",") - .map((entry) => entry.trim().toLowerCase()) - .filter(Boolean); - - return new Set(hosts); -} - -export function isAllowedExternalUrl(input: string, allowedHosts: Set) { - const parsed = parseUrl(input); - if (!parsed) return false; - if (parsed.protocol !== "https:") return false; - if (parsed.username || parsed.password) return false; - if (isPrivateOrLoopbackHost(parsed.hostname)) return false; - - const hostname = normalizeHostname(parsed.hostname); - if (allowedHosts.has(hostname)) return true; - - const origin = parsed.origin.toLowerCase(); - return allowedHosts.has(origin); -} - -export function isAllowedOAuthRedirectUri(input: string, trustedOrigins: string[], allowedHosts: Set) { - const parsed = parseUrl(input); - if (!parsed) return false; - if (parsed.username || parsed.password) return false; - if (parsed.hash) return false; - - const origin = parsed.origin.toLowerCase(); - const hostname = normalizeHostname(parsed.hostname); - - if (parsed.protocol === "http:") return isOAuthLoopbackRedirectHost(hostname); - if (parsed.protocol !== "https:") return false; - if (isPrivateOrLoopbackHost(hostname)) return false; - - if (trustedOrigins.includes(origin)) return true; - if (allowedHosts.has(origin)) return true; - return allowedHosts.has(hostname); -} - -export function sanitizeResumePictureUrl(url: string, appUrl: string) { - if (!url) return ""; - if (url.startsWith("/uploads/")) return url; - - const parsed = parseUrl(url); - if (!parsed) return ""; - if (parsed.username || parsed.password) return ""; - if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return ""; - - const app = parseUrl(appUrl); - if (!app) return ""; - - if (parsed.origin !== app.origin) return ""; - if (!parsed.pathname.startsWith("/uploads/")) return ""; - - return `${parsed.pathname}${parsed.search}${parsed.hash}`; -} diff --git a/src/utils/url.test.ts b/src/utils/url.test.ts deleted file mode 100644 index eaad11c5d..000000000 --- a/src/utils/url.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { describe, expect, it } from "vite-plus/test"; - -import { createUrl } from "./url"; - -describe("createUrl", () => { - it("returns empty strings for undefined url", () => { - expect(createUrl()).toEqual({ url: "", label: "" }); - expect(createUrl(undefined)).toEqual({ url: "", label: "" }); - }); - - it("uses url as label when label is not provided", () => { - expect(createUrl("https://example.com")).toEqual({ - url: "https://example.com", - label: "https://example.com", - }); - }); - - it("uses provided label", () => { - expect(createUrl("https://example.com", "Example")).toEqual({ - url: "https://example.com", - label: "Example", - }); - }); -}); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts deleted file mode 100644 index dbd49023e..000000000 --- a/src/vite-env.d.ts +++ /dev/null @@ -1,57 +0,0 @@ -/// - -declare const __APP_VERSION__: string; - -declare module "*.css"; -declare module "@fontsource/*" {} -declare module "@fontsource-variable/*" {} - -declare namespace NodeJS { - interface ProcessEnv { - // Basics - PORT: string; - APP_URL: string; - PRINTER_APP_URL?: string; - - // Authentication - AUTH_SECRET: string; - - // Printer - PRINTER_ENDPOINT?: string; - - // Database - DATABASE_URL: string; - - // Social Auth (Google) - GOOGLE_CLIENT_ID?: string; - GOOGLE_CLIENT_SECRET?: string; - - // Social Auth (GitHub) - GITHUB_CLIENT_ID?: string; - GITHUB_CLIENT_SECRET?: string; - - // Storage (Optional) - S3_ACCESS_KEY_ID?: string; - S3_SECRET_ACCESS_KEY?: string; - S3_REGION?: string; - S3_ENDPOINT?: string; - S3_BUCKET?: string; - S3_FORCE_PATH_STYLE?: string | boolean; - - // Custom OAuth Provider - OAUTH_PROVIDER_NAME?: string; - OAUTH_CLIENT_ID?: string; - OAUTH_CLIENT_SECRET?: string; - OAUTH_DISCOVERY_URL?: string; - OAUTH_AUTHORIZATION_URL?: string; - OAUTH_TOKEN_URL?: string; - OAUTH_USER_INFO_URL?: string; - OAUTH_SCOPES?: string; - - // Feature Flags - FLAG_DEBUG_PRINTER: string | boolean; - FLAG_DISABLE_SIGNUPS: string | boolean; - FLAG_DISABLE_EMAIL_AUTH: string | boolean; - FLAG_DISABLE_IMAGE_PROCESSING: string | boolean; - } -} diff --git a/tsconfig.json b/tsconfig.json index 654eae826..66de57db4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,32 +1,3 @@ { - "include": ["**/*.ts", "**/*.tsx"], - "compilerOptions": { - "target": "ES2022", - "jsx": "react-jsx", - "module": "ESNext", - "types": ["vite/client"], - "lib": ["ES2022", "DOM", "DOM.Iterable"], - - /* Bundler mode */ - "noEmit": true, - "moduleResolution": "bundler", - "esModuleInterop": true, - "verbatimModuleSyntax": false, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowImportingTsExtensions": true, - - /* Linting */ - "strict": true, - "skipLibCheck": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": false, - - /* Paths */ - "paths": { - "@/*": ["./src/*"] - } - } + "extends": "@reactive-resume/config/tsconfig.base.json" } diff --git a/turbo.json b/turbo.json new file mode 100644 index 000000000..39dff1f25 --- /dev/null +++ b/turbo.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://turbo.build/schema.json", + "ui": "tui", + "globalEnv": [ + "PORT", + "APP_URL", + "DATABASE_URL", + "AUTH_SECRET", + "BETTER_AUTH_API_KEY", + "GOOGLE_CLIENT_ID", + "GOOGLE_CLIENT_SECRET", + "GITHUB_CLIENT_ID", + "GITHUB_CLIENT_SECRET", + "LINKEDIN_CLIENT_ID", + "LINKEDIN_CLIENT_SECRET", + "OAUTH_PROVIDER_NAME", + "OAUTH_CLIENT_ID", + "OAUTH_CLIENT_SECRET", + "OAUTH_DISCOVERY_URL", + "OAUTH_AUTHORIZATION_URL", + "OAUTH_TOKEN_URL", + "OAUTH_USER_INFO_URL", + "OAUTH_SCOPES", + "OAUTH_DYNAMIC_CLIENT_REDIRECT_HOSTS", + "SMTP_HOST", + "SMTP_PORT", + "SMTP_USER", + "SMTP_PASS", + "SMTP_FROM", + "SMTP_SECURE", + "S3_ACCESS_KEY_ID", + "S3_SECRET_ACCESS_KEY", + "S3_REGION", + "S3_ENDPOINT", + "S3_BUCKET", + "S3_FORCE_PATH_STYLE", + "FLAG_DISABLE_SIGNUPS", + "FLAG_DISABLE_EMAIL_AUTH", + "FLAG_DISABLE_IMAGE_PROCESSING", + "GOOGLE_CLOUD_API_KEY", + "CROWDIN_PROJECT_ID", + "CROWDIN_API_TOKEN" + ], + "tasks": { + "source": { + "dependsOn": ["^source"] + }, + "build": { + "dependsOn": ["source"], + "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.env*"], + "outputs": ["dist/**", ".output/**", ".vercel/**"] + }, + "lint": { + "dependsOn": ["^lint"] + }, + "typecheck": { + "dependsOn": ["source"] + }, + "test": { + "dependsOn": ["source"] + }, + "test:coverage": { + "dependsOn": ["source"] + }, + "test:ci": { + "dependsOn": ["source"] + }, + "test:agent": { + "dependsOn": ["source"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "start": { + "cache": false, + "persistent": true + }, + "db:generate": { + "cache": false + }, + "db:migrate": { + "cache": false + }, + "db:studio": { + "cache": false, + "persistent": true + }, + "lingui:extract": { + "cache": false + } + } +} diff --git a/vite.config.ts b/vite.config.ts deleted file mode 100644 index 8fc019f75..000000000 --- a/vite.config.ts +++ /dev/null @@ -1,296 +0,0 @@ -import type { PluginOption } from "vite-plus"; -import type { BuiltinReporters } from "vite-plus/test/reporters"; - -import { lingui } from "@lingui/vite-plugin"; -import babel from "@rolldown/plugin-babel"; -import tailwindcss from "@tailwindcss/vite"; -import { tanstackStart } from "@tanstack/react-start/plugin/vite"; -import viteReact from "@vitejs/plugin-react"; -import { nitro } from "nitro/vite"; -import { VitePWA } from "vite-plugin-pwa"; -import { defineConfig } from "vite-plus"; - -const isVitest = Boolean(process.env.VITEST); - -const testReporters: BuiltinReporters[] = []; - -if (process.env.GITHUB_ACTIONS) { - testReporters.push("github-actions"); -} else if (process.env.AGENT) { - testReporters.push("agent"); -} else { - testReporters.push("dot"); -} - -const plugins: PluginOption[] = [tanstackStart({ router: { semicolons: true, quoteStyle: "double" } }), viteReact()]; - -if (!isVitest) { - plugins.push( - lingui(), - tailwindcss(), - nitro({ - plugins: ["plugins/1.migrate.ts"], - rolldownConfig: { - treeshake: { - moduleSideEffects: (id) => { - if (id.includes("unenv/polyfill/")) return true; - if (id.includes("reflect-metadata/Reflect")) return true; - if (id.includes("node-fetch-native/polyfill")) return true; - return false; - }, - }, - }, - }), - babel({ plugins: ["@lingui/babel-plugin-lingui-macro"] }), - VitePWA({ - outDir: "public", - useCredentials: true, - injectRegister: false, - includeAssets: ["**/*"], - registerType: "autoUpdate", - workbox: { - skipWaiting: true, - clientsClaim: true, - globPatterns: ["**/*"], - globIgnores: ["**/manifest.webmanifest"], - maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, // 10mb - navigateFallback: null, // Disable navigation fallback for SSR - }, - manifest: { - name: "Reactive Resume", - short_name: "Reactive Resume", - description: "A free and open-source resume builder.", - id: "/?source=pwa", - start_url: "/?source=pwa", - display: "standalone", - orientation: "portrait", - theme_color: "#09090B", - background_color: "#09090B", - icons: [ - { - src: "favicon.ico", - sizes: "128x128", - type: "image/x-icon", - }, - { - src: "pwa-64x64.png", - sizes: "64x64", - type: "image/png", - }, - { - src: "pwa-192x192.png", - sizes: "192x192", - type: "image/png", - }, - { - src: "pwa-512x512.png", - sizes: "512x512", - type: "image/png", - purpose: "any", - }, - { - src: "maskable-icon-512x512.png", - sizes: "512x512", - type: "image/png", - purpose: "maskable", - }, - ], - screenshots: [ - { - src: "screenshots/web/1-landing-page.webp", - sizes: "1920x1080 any", - type: "image/webp", - form_factor: "wide", - label: "Landing Page", - }, - { - src: "screenshots/web/2-resume-dashboard.webp", - sizes: "1920x1080 any", - type: "image/webp", - form_factor: "wide", - label: "Resume Dashboard", - }, - { - src: "screenshots/web/3-builder-screen.webp", - sizes: "1920x1080 any", - type: "image/webp", - form_factor: "wide", - label: "Builder Screen", - }, - { - src: "screenshots/web/4-template-gallery.webp", - sizes: "1920x1080 any", - type: "image/webp", - form_factor: "wide", - label: "Template Gallery", - }, - { - src: "screenshots/mobile/1-landing-page.webp", - sizes: "1284x2778 any", - type: "image/webp", - form_factor: "narrow", - label: "Landing Page", - }, - { - src: "screenshots/mobile/2-resume-dashboard.webp", - sizes: "1284x2778 any", - type: "image/webp", - form_factor: "narrow", - label: "Resume Dashboard", - }, - { - src: "screenshots/mobile/3-builder-screen.webp", - sizes: "1284x2778 any", - type: "image/webp", - form_factor: "narrow", - label: "Builder Screen", - }, - { - src: "screenshots/mobile/4-template-gallery.webp", - sizes: "1284x2778 any", - type: "image/webp", - form_factor: "narrow", - label: "Template Gallery", - }, - ], - categories: [ - "ai", - "builder", - "business", - "career", - "cv", - "editor", - "free", - "generator", - "job-search", - "multilingual", - "open-source", - "privacy", - "productivity", - "resume", - "self-hosted", - "templates", - "utilities", - "writing", - ], - }, - }), - ); -} - -const config = defineConfig({ - plugins, - - run: { cache: true }, - - staged: { - "*": "vp check --fix", - }, - - fmt: { - printWidth: 120, - ignorePatterns: ["webfontlist.json", "routeTree.gen.ts", "docs/changelog/index.mdx"], - sortPackageJson: { - sortScripts: true, - }, - sortTailwindcss: { - stylesheet: "./src/styles/globals.css", - functions: ["clsx", "cva", "cn"], - }, - sortImports: { - groups: [ - "type-import", - ["value-builtin", "value-external"], - "type-internal", - "value-internal", - ["type-parent", "type-sibling", "type-index"], - ["value-parent", "value-sibling", "value-index"], - "unknown", - ], - }, - }, - - lint: { - env: { builtin: true }, - ignorePatterns: ["webfontlist.json", "routeTree.gen.ts"], - options: { typeAware: true, typeCheck: true }, - settings: { - react: { - version: "19", - linkComponents: ["Link"], - }, - }, - plugins: [ - "eslint", - "import", - "jest", - "jsdoc", - "jsx-a11y", - "node", - "oxc", - "promise", - "react-perf", - "react", - "typescript", - "unicorn", - "vitest", - ], - rules: { - "react/exhaustive-deps": "off", - "jest/no-conditional-expect": "off", - "jest/require-to-throw-message": "off", - "typescript/consistent-type-imports": "error", - }, - }, - - define: { - __APP_VERSION__: JSON.stringify(process.env.npm_package_version), - }, - - resolve: { - tsconfigPaths: true, - }, - - test: { - environment: "happy-dom", - reporters: testReporters, - coverage: { - provider: "v8", - reporter: ["text", "html", "lcov", "json-summary"], - reportsDirectory: "./coverage", - include: [ - "src/utils/**/*.{ts,tsx}", - "src/hooks/**/*.{ts,tsx}", - "src/integrations/**/*.{ts,tsx}", - "src/components/**/*.{ts,tsx}", - "src/schema/**/*.{ts,tsx}", - "src/dialogs/**/*.{ts,tsx}", - ], - exclude: ["**/*.test.{ts,tsx}", "**/*.d.ts", "**/node_modules/**", "src/components/ui/**"], - thresholds: { - statements: 32, - branches: 37, - functions: 20, - lines: 31, - }, - }, - }, - - build: { - sourcemap: true, - chunkSizeWarningLimit: 10 * 1024, // 10mb - }, - - server: { - host: true, - port: 3000, - strictPort: true, - allowedHosts: true, - hmr: { - host: "localhost", - port: 3000, - }, - }, -}); - -export default config; diff --git a/vitest.setup.ts b/vitest.setup.ts new file mode 100644 index 000000000..f149f27ae --- /dev/null +++ b/vitest.setup.ts @@ -0,0 +1 @@ +import "@testing-library/jest-dom/vitest"; diff --git a/vitest.shared.ts b/vitest.shared.ts new file mode 100644 index 000000000..ac962b002 --- /dev/null +++ b/vitest.shared.ts @@ -0,0 +1,44 @@ +import type { ViteUserConfig } from "vitest/config"; +import type { VitestEnvironment } from "vitest/node"; +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vitest/config"; + +const workspaceRoot = fileURLToPath(new URL(".", import.meta.url)); +const setupFile = fileURLToPath(new URL("./vitest.setup.ts", import.meta.url)); + +type VitestProjectOptions = { + name: string; + dirname: string; + environment?: VitestEnvironment; + plugins?: ViteUserConfig["plugins"]; +}; + +export const createVitestProjectConfig = ({ + name, + dirname, + environment = "node", + plugins = [], +}: VitestProjectOptions) => + defineConfig({ + root: dirname, + envDir: workspaceRoot, + resolve: { tsconfigPaths: true }, + plugins, + test: { + name, + environment, + setupFiles: [setupFile], + include: ["src/**/*.{test,spec}.?(c|m)[jt]s?(x)"], + exclude: ["node_modules", "dist", ".output", "coverage", "reports"], + pool: "forks", + passWithNoTests: true, + coverage: { + provider: "v8", + reportsDirectory: "coverage", + reporter: ["text", "text-summary", "json-summary", "json", "lcov", "html"], + include: ["src/**/*.{ts,tsx}"], + exclude: ["src/**/*.{test,spec}.*", "src/**/*.d.ts", "src/routeTree.gen.ts"], + reportOnFailure: true, + }, + }, + });