From 21d5096bc0d10b875e803a35be4c91367bc11faf Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 12 Jul 2026 23:50:02 -0300 Subject: [PATCH 1/4] feat: setup project --- frontend/.env | 1 + frontend/.gitignore | 24 + frontend/.oxlintrc.json | 8 + frontend/README.md | 71 + frontend/coverage/base.css | 224 ++ frontend/coverage/block-navigation.js | 87 + frontend/coverage/favicon.png | Bin 0 -> 445 bytes frontend/coverage/index.html | 251 ++ frontend/coverage/lcov-report/base.css | 224 ++ .../coverage/lcov-report/block-navigation.js | 87 + frontend/coverage/lcov-report/favicon.png | Bin 0 -> 445 bytes frontend/coverage/lcov-report/index.html | 251 ++ frontend/coverage/lcov-report/prettify.css | 1 + frontend/coverage/lcov-report/prettify.js | 2 + .../lcov-report/sort-arrow-sprite.png | Bin 0 -> 138 bytes frontend/coverage/lcov-report/sorter.js | 210 ++ .../coverage/lcov-report/src/App.tsx.html | 169 + .../components/form/CurrencyField.tsx.html | 262 ++ .../components/form/MaskedTextField.tsx.html | 265 ++ .../src/components/form/SelectField.tsx.html | 229 ++ .../src/components/form/TextField.tsx.html | 232 ++ .../src/components/form/index.html | 161 + .../src/components/layout/Layout.tsx.html | 145 + .../src/components/layout/Logo.tsx.html | 106 + .../src/components/layout/index.html | 131 + .../components/wizard/StepIndicator.tsx.html | 166 + .../components/wizard/SummarySection.tsx.html | 178 ++ .../components/wizard/WizardActions.tsx.html | 148 + .../wizard/WizardStepShell.tsx.html | 136 + .../src/components/wizard/index.html | 161 + .../lcov-report/src/constants/index.html | 131 + .../lcov-report/src/constants/masks.ts.html | 112 + .../lcov-report/src/constants/options.ts.html | 223 ++ .../src/context/WizardFormProvider.tsx.html | 163 + .../lcov-report/src/context/index.html | 116 + .../coverage/lcov-report/src/hooks/index.html | 131 + .../src/hooks/useRequireStepData.ts.html | 145 + .../src/hooks/useStepNavigation.ts.html | 148 + frontend/coverage/lcov-report/src/index.html | 116 + .../coverage/lcov-report/src/lib/api.ts.html | 277 ++ .../coverage/lcov-report/src/lib/cpf.ts.html | 163 + .../coverage/lcov-report/src/lib/index.html | 176 ++ .../lcov-report/src/lib/masks.ts.html | 178 ++ .../coverage/lcov-report/src/lib/pdf.ts.html | 370 +++ .../lcov-report/src/lib/storage.ts.html | 154 + .../src/pages/Step1PersonalData.tsx.html | 193 ++ .../src/pages/Step2Address.tsx.html | 328 ++ .../src/pages/Step3Professional.tsx.html | 253 ++ .../src/pages/SummaryPage.tsx.html | 298 ++ .../coverage/lcov-report/src/pages/index.html | 161 + .../src/schemas/formSchema.ts.html | 328 ++ .../lcov-report/src/schemas/index.html | 116 + frontend/coverage/lcov.info | 707 +++++ frontend/coverage/prettify.css | 1 + frontend/coverage/prettify.js | 2 + frontend/coverage/sort-arrow-sprite.png | Bin 0 -> 138 bytes frontend/coverage/sorter.js | 210 ++ frontend/coverage/src/App.tsx.html | 169 + .../components/form/CurrencyField.tsx.html | 262 ++ .../components/form/MaskedTextField.tsx.html | 265 ++ .../src/components/form/SelectField.tsx.html | 229 ++ .../src/components/form/TextField.tsx.html | 232 ++ .../coverage/src/components/form/index.html | 161 + .../src/components/layout/Layout.tsx.html | 145 + .../src/components/layout/Logo.tsx.html | 106 + .../coverage/src/components/layout/index.html | 131 + .../components/wizard/StepIndicator.tsx.html | 166 + .../components/wizard/SummarySection.tsx.html | 178 ++ .../components/wizard/WizardActions.tsx.html | 148 + .../wizard/WizardStepShell.tsx.html | 136 + .../coverage/src/components/wizard/index.html | 161 + frontend/coverage/src/constants/index.html | 131 + frontend/coverage/src/constants/masks.ts.html | 112 + .../coverage/src/constants/options.ts.html | 223 ++ .../src/context/WizardFormProvider.tsx.html | 163 + frontend/coverage/src/context/index.html | 116 + frontend/coverage/src/hooks/index.html | 131 + .../src/hooks/useRequireStepData.ts.html | 145 + .../src/hooks/useStepNavigation.ts.html | 148 + frontend/coverage/src/index.html | 116 + frontend/coverage/src/lib/api.ts.html | 277 ++ frontend/coverage/src/lib/cpf.ts.html | 163 + frontend/coverage/src/lib/index.html | 176 ++ frontend/coverage/src/lib/masks.ts.html | 178 ++ frontend/coverage/src/lib/pdf.ts.html | 370 +++ frontend/coverage/src/lib/storage.ts.html | 154 + .../src/pages/Step1PersonalData.tsx.html | 193 ++ .../coverage/src/pages/Step2Address.tsx.html | 328 ++ .../src/pages/Step3Professional.tsx.html | 253 ++ .../coverage/src/pages/SummaryPage.tsx.html | 298 ++ frontend/coverage/src/pages/index.html | 161 + .../coverage/src/schemas/formSchema.ts.html | 328 ++ frontend/coverage/src/schemas/index.html | 116 + frontend/index.html | 13 + frontend/mock/db.json | 86 + frontend/package.json | 45 + frontend/pnpm-lock.yaml | 2748 +++++++++++++++++ frontend/public/favicon.svg | 1 + frontend/public/icons.svg | 24 + frontend/public/logo.avif | Bin 0 -> 3464 bytes frontend/src/App.test.tsx | 69 + frontend/src/App.tsx | 28 + .../components/form/CurrencyField.test.tsx | 43 + .../src/components/form/CurrencyField.tsx | 59 + .../components/form/MaskedTextField.test.tsx | 28 + .../src/components/form/MaskedTextField.tsx | 60 + .../src/components/form/SelectField.test.tsx | 34 + frontend/src/components/form/SelectField.tsx | 48 + .../src/components/form/TextField.test.tsx | 22 + frontend/src/components/form/TextField.tsx | 49 + .../src/components/layout/Layout.test.tsx | 21 + frontend/src/components/layout/Layout.tsx | 20 + frontend/src/components/layout/Logo.test.tsx | 12 + frontend/src/components/layout/Logo.tsx | 7 + .../components/wizard/StepIndicator.test.tsx | 28 + .../src/components/wizard/StepIndicator.tsx | 27 + .../components/wizard/SummarySection.test.tsx | 34 + .../src/components/wizard/SummarySection.tsx | 31 + .../components/wizard/WizardActions.test.tsx | 25 + .../src/components/wizard/WizardActions.tsx | 21 + .../wizard/WizardStepShell.test.tsx | 27 + .../src/components/wizard/WizardStepShell.tsx | 17 + frontend/src/constants/masks.ts | 9 + frontend/src/constants/options.test.ts | 13 + frontend/src/constants/options.ts | 46 + .../src/context/WizardFormProvider.test.tsx | 54 + frontend/src/context/WizardFormProvider.tsx | 26 + .../src/hooks/useRequireStepData.test.tsx | 37 + frontend/src/hooks/useRequireStepData.ts | 20 + frontend/src/hooks/useStepNavigation.test.tsx | 71 + frontend/src/hooks/useStepNavigation.ts | 21 + frontend/src/index.css | 359 +++ frontend/src/lib/api.test.ts | 74 + frontend/src/lib/api.ts | 64 + frontend/src/lib/cpf.test.ts | 37 + frontend/src/lib/cpf.ts | 26 + frontend/src/lib/masks.test.ts | 50 + frontend/src/lib/masks.ts | 31 + frontend/src/lib/pdf.test.ts | 62 + frontend/src/lib/pdf.ts | 95 + frontend/src/lib/storage.test.ts | 42 + frontend/src/lib/storage.ts | 23 + frontend/src/main.tsx | 10 + frontend/src/pages/Step1PersonalData.test.tsx | 51 + frontend/src/pages/Step1PersonalData.tsx | 36 + frontend/src/pages/Step2Address.test.tsx | 82 + frontend/src/pages/Step2Address.tsx | 81 + frontend/src/pages/Step3Professional.test.tsx | 66 + frontend/src/pages/Step3Professional.tsx | 56 + frontend/src/pages/SummaryPage.test.tsx | 86 + frontend/src/pages/SummaryPage.tsx | 71 + frontend/src/schemas/formSchema.test.ts | 95 + frontend/src/schemas/formSchema.ts | 81 + frontend/src/test/renderWithForm.tsx | 36 + frontend/src/test/setup.ts | 8 + frontend/tsconfig.app.json | 26 + frontend/tsconfig.json | 7 + frontend/tsconfig.node.json | 23 + frontend/vite.config.ts | 31 + 159 files changed, 21532 insertions(+) create mode 100644 frontend/.env create mode 100644 frontend/.gitignore create mode 100644 frontend/.oxlintrc.json create mode 100644 frontend/README.md create mode 100644 frontend/coverage/base.css create mode 100644 frontend/coverage/block-navigation.js create mode 100644 frontend/coverage/favicon.png create mode 100644 frontend/coverage/index.html create mode 100644 frontend/coverage/lcov-report/base.css create mode 100644 frontend/coverage/lcov-report/block-navigation.js create mode 100644 frontend/coverage/lcov-report/favicon.png create mode 100644 frontend/coverage/lcov-report/index.html create mode 100644 frontend/coverage/lcov-report/prettify.css create mode 100644 frontend/coverage/lcov-report/prettify.js create mode 100644 frontend/coverage/lcov-report/sort-arrow-sprite.png create mode 100644 frontend/coverage/lcov-report/sorter.js create mode 100644 frontend/coverage/lcov-report/src/App.tsx.html create mode 100644 frontend/coverage/lcov-report/src/components/form/CurrencyField.tsx.html create mode 100644 frontend/coverage/lcov-report/src/components/form/MaskedTextField.tsx.html create mode 100644 frontend/coverage/lcov-report/src/components/form/SelectField.tsx.html create mode 100644 frontend/coverage/lcov-report/src/components/form/TextField.tsx.html create mode 100644 frontend/coverage/lcov-report/src/components/form/index.html create mode 100644 frontend/coverage/lcov-report/src/components/layout/Layout.tsx.html create mode 100644 frontend/coverage/lcov-report/src/components/layout/Logo.tsx.html create mode 100644 frontend/coverage/lcov-report/src/components/layout/index.html create mode 100644 frontend/coverage/lcov-report/src/components/wizard/StepIndicator.tsx.html create mode 100644 frontend/coverage/lcov-report/src/components/wizard/SummarySection.tsx.html create mode 100644 frontend/coverage/lcov-report/src/components/wizard/WizardActions.tsx.html create mode 100644 frontend/coverage/lcov-report/src/components/wizard/WizardStepShell.tsx.html create mode 100644 frontend/coverage/lcov-report/src/components/wizard/index.html create mode 100644 frontend/coverage/lcov-report/src/constants/index.html create mode 100644 frontend/coverage/lcov-report/src/constants/masks.ts.html create mode 100644 frontend/coverage/lcov-report/src/constants/options.ts.html create mode 100644 frontend/coverage/lcov-report/src/context/WizardFormProvider.tsx.html create mode 100644 frontend/coverage/lcov-report/src/context/index.html create mode 100644 frontend/coverage/lcov-report/src/hooks/index.html create mode 100644 frontend/coverage/lcov-report/src/hooks/useRequireStepData.ts.html create mode 100644 frontend/coverage/lcov-report/src/hooks/useStepNavigation.ts.html create mode 100644 frontend/coverage/lcov-report/src/index.html create mode 100644 frontend/coverage/lcov-report/src/lib/api.ts.html create mode 100644 frontend/coverage/lcov-report/src/lib/cpf.ts.html create mode 100644 frontend/coverage/lcov-report/src/lib/index.html create mode 100644 frontend/coverage/lcov-report/src/lib/masks.ts.html create mode 100644 frontend/coverage/lcov-report/src/lib/pdf.ts.html create mode 100644 frontend/coverage/lcov-report/src/lib/storage.ts.html create mode 100644 frontend/coverage/lcov-report/src/pages/Step1PersonalData.tsx.html create mode 100644 frontend/coverage/lcov-report/src/pages/Step2Address.tsx.html create mode 100644 frontend/coverage/lcov-report/src/pages/Step3Professional.tsx.html create mode 100644 frontend/coverage/lcov-report/src/pages/SummaryPage.tsx.html create mode 100644 frontend/coverage/lcov-report/src/pages/index.html create mode 100644 frontend/coverage/lcov-report/src/schemas/formSchema.ts.html create mode 100644 frontend/coverage/lcov-report/src/schemas/index.html create mode 100644 frontend/coverage/lcov.info create mode 100644 frontend/coverage/prettify.css create mode 100644 frontend/coverage/prettify.js create mode 100644 frontend/coverage/sort-arrow-sprite.png create mode 100644 frontend/coverage/sorter.js create mode 100644 frontend/coverage/src/App.tsx.html create mode 100644 frontend/coverage/src/components/form/CurrencyField.tsx.html create mode 100644 frontend/coverage/src/components/form/MaskedTextField.tsx.html create mode 100644 frontend/coverage/src/components/form/SelectField.tsx.html create mode 100644 frontend/coverage/src/components/form/TextField.tsx.html create mode 100644 frontend/coverage/src/components/form/index.html create mode 100644 frontend/coverage/src/components/layout/Layout.tsx.html create mode 100644 frontend/coverage/src/components/layout/Logo.tsx.html create mode 100644 frontend/coverage/src/components/layout/index.html create mode 100644 frontend/coverage/src/components/wizard/StepIndicator.tsx.html create mode 100644 frontend/coverage/src/components/wizard/SummarySection.tsx.html create mode 100644 frontend/coverage/src/components/wizard/WizardActions.tsx.html create mode 100644 frontend/coverage/src/components/wizard/WizardStepShell.tsx.html create mode 100644 frontend/coverage/src/components/wizard/index.html create mode 100644 frontend/coverage/src/constants/index.html create mode 100644 frontend/coverage/src/constants/masks.ts.html create mode 100644 frontend/coverage/src/constants/options.ts.html create mode 100644 frontend/coverage/src/context/WizardFormProvider.tsx.html create mode 100644 frontend/coverage/src/context/index.html create mode 100644 frontend/coverage/src/hooks/index.html create mode 100644 frontend/coverage/src/hooks/useRequireStepData.ts.html create mode 100644 frontend/coverage/src/hooks/useStepNavigation.ts.html create mode 100644 frontend/coverage/src/index.html create mode 100644 frontend/coverage/src/lib/api.ts.html create mode 100644 frontend/coverage/src/lib/cpf.ts.html create mode 100644 frontend/coverage/src/lib/index.html create mode 100644 frontend/coverage/src/lib/masks.ts.html create mode 100644 frontend/coverage/src/lib/pdf.ts.html create mode 100644 frontend/coverage/src/lib/storage.ts.html create mode 100644 frontend/coverage/src/pages/Step1PersonalData.tsx.html create mode 100644 frontend/coverage/src/pages/Step2Address.tsx.html create mode 100644 frontend/coverage/src/pages/Step3Professional.tsx.html create mode 100644 frontend/coverage/src/pages/SummaryPage.tsx.html create mode 100644 frontend/coverage/src/pages/index.html create mode 100644 frontend/coverage/src/schemas/formSchema.ts.html create mode 100644 frontend/coverage/src/schemas/index.html create mode 100644 frontend/index.html create mode 100644 frontend/mock/db.json create mode 100644 frontend/package.json create mode 100644 frontend/pnpm-lock.yaml create mode 100644 frontend/public/favicon.svg create mode 100644 frontend/public/icons.svg create mode 100644 frontend/public/logo.avif create mode 100644 frontend/src/App.test.tsx create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/components/form/CurrencyField.test.tsx create mode 100644 frontend/src/components/form/CurrencyField.tsx create mode 100644 frontend/src/components/form/MaskedTextField.test.tsx create mode 100644 frontend/src/components/form/MaskedTextField.tsx create mode 100644 frontend/src/components/form/SelectField.test.tsx create mode 100644 frontend/src/components/form/SelectField.tsx create mode 100644 frontend/src/components/form/TextField.test.tsx create mode 100644 frontend/src/components/form/TextField.tsx create mode 100644 frontend/src/components/layout/Layout.test.tsx create mode 100644 frontend/src/components/layout/Layout.tsx create mode 100644 frontend/src/components/layout/Logo.test.tsx create mode 100644 frontend/src/components/layout/Logo.tsx create mode 100644 frontend/src/components/wizard/StepIndicator.test.tsx create mode 100644 frontend/src/components/wizard/StepIndicator.tsx create mode 100644 frontend/src/components/wizard/SummarySection.test.tsx create mode 100644 frontend/src/components/wizard/SummarySection.tsx create mode 100644 frontend/src/components/wizard/WizardActions.test.tsx create mode 100644 frontend/src/components/wizard/WizardActions.tsx create mode 100644 frontend/src/components/wizard/WizardStepShell.test.tsx create mode 100644 frontend/src/components/wizard/WizardStepShell.tsx create mode 100644 frontend/src/constants/masks.ts create mode 100644 frontend/src/constants/options.test.ts create mode 100644 frontend/src/constants/options.ts create mode 100644 frontend/src/context/WizardFormProvider.test.tsx create mode 100644 frontend/src/context/WizardFormProvider.tsx create mode 100644 frontend/src/hooks/useRequireStepData.test.tsx create mode 100644 frontend/src/hooks/useRequireStepData.ts create mode 100644 frontend/src/hooks/useStepNavigation.test.tsx create mode 100644 frontend/src/hooks/useStepNavigation.ts create mode 100644 frontend/src/index.css create mode 100644 frontend/src/lib/api.test.ts create mode 100644 frontend/src/lib/api.ts create mode 100644 frontend/src/lib/cpf.test.ts create mode 100644 frontend/src/lib/cpf.ts create mode 100644 frontend/src/lib/masks.test.ts create mode 100644 frontend/src/lib/masks.ts create mode 100644 frontend/src/lib/pdf.test.ts create mode 100644 frontend/src/lib/pdf.ts create mode 100644 frontend/src/lib/storage.test.ts create mode 100644 frontend/src/lib/storage.ts create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/pages/Step1PersonalData.test.tsx create mode 100644 frontend/src/pages/Step1PersonalData.tsx create mode 100644 frontend/src/pages/Step2Address.test.tsx create mode 100644 frontend/src/pages/Step2Address.tsx create mode 100644 frontend/src/pages/Step3Professional.test.tsx create mode 100644 frontend/src/pages/Step3Professional.tsx create mode 100644 frontend/src/pages/SummaryPage.test.tsx create mode 100644 frontend/src/pages/SummaryPage.tsx create mode 100644 frontend/src/schemas/formSchema.test.ts create mode 100644 frontend/src/schemas/formSchema.ts create mode 100644 frontend/src/test/renderWithForm.tsx create mode 100644 frontend/src/test/setup.ts create mode 100644 frontend/tsconfig.app.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 0000000..8af0033 --- /dev/null +++ b/frontend/.env @@ -0,0 +1 @@ +VITE_MOCK_API_URL=http://localhost:3001 diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/.oxlintrc.json b/frontend/.oxlintrc.json new file mode 100644 index 0000000..6fa991d --- /dev/null +++ b/frontend/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..a10a7d5 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,71 @@ +# Verity — Cadastro (Teste Técnico Front-End) + +Formulário de cadastro em 3 etapas (Dados Pessoais, Informações Residenciais e +Informações Profissionais) com resumo final e exportação em PDF, construído +com React + TypeScript + Vite. + +## Stack + +- **React 19** + **TypeScript** + **Vite** +- **React Hook Form** + **Zod** para validação dos formulários +- **react-router-dom** para navegação entre etapas +- **react-imask** para máscaras de Data de Nascimento, CPF, Telefone e CEP +- **jsPDF** para exportação do resumo em PDF +- **json-server** como serviço mockado para busca de CEP e lista de profissões +- **Vitest** + **Testing Library** para os testes unitários (cobertura ≥ 80%) + +## Como rodar + +Instale as dependências (pnpm): + +```bash +pnpm install +``` + +Suba a aplicação e a API mockada juntas: + +```bash +pnpm dev:all +``` + +Isso inicia o Vite em `http://localhost:5173` e o json-server em +`http://localhost:3001` (dados em `mock/db.json`, com os endpoints +`GET /ceps/:cep` e `GET /professions`). + +Se preferir rodar cada um separadamente: + +```bash +pnpm dev # front-end +pnpm mock:api # API mockada +``` + +## Busca de CEP + +Ao digitar um CEP completo, a aplicação primeiro consulta o json-server +(`/ceps/:cep`). Caso o CEP não exista no mock, ela cai automaticamente para o +ViaCEP (endpoint público dos Correios/ViaCEP) como fallback — cobrindo tanto o +requisito principal quanto o "plus" do desafio. CEPs de exemplo disponíveis no +mock: `01310-100`, `01001-000`, `20040-020`, `30130-010`, `80010-000`, +`90010-000`, `40010-000`, `70040-010`, `60060-170`, `50030-230`. + +## Testes + +```bash +pnpm test # roda a suíte uma vez +pnpm test:watch # modo watch +pnpm test:coverage # com relatório de cobertura (mínimo configurado: 80%) +``` + +## Build + +```bash +pnpm build +pnpm preview +``` + +## Identidade visual + +A paleta (azul/branco) e o wordmark "verity" usados são um placeholder até a +logo oficial da Verity ser fornecida — o componente `Logo` +(`src/components/layout/Logo.tsx`) foi isolado exatamente para facilitar essa +troca depois. diff --git a/frontend/coverage/base.css b/frontend/coverage/base.css new file mode 100644 index 0000000..f418035 --- /dev/null +++ b/frontend/coverage/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/frontend/coverage/block-navigation.js b/frontend/coverage/block-navigation.js new file mode 100644 index 0000000..530d1ed --- /dev/null +++ b/frontend/coverage/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/frontend/coverage/favicon.png b/frontend/coverage/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1525b811a167671e9de1fa78aab9f5c0b61cef7 GIT binary patch literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 97.07% + Statements + 232/239 +
+ + +
+ 84.76% + Branches + 89/105 +
+ + +
+ 94.52% + Functions + 69/73 +
+ + +
+ 98.64% + Lines + 219/222 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
src +
+
100%1/1100%0/0100%1/1100%1/1
src/components/form +
+
100%21/2178.94%30/38100%10/10100%21/21
src/components/layout +
+
100%2/2100%0/0100%2/2100%2/2
src/components/wizard +
+
100%11/11100%14/14100%7/7100%10/10
src/constants +
+
100%8/8100%2/2100%2/2100%7/7
src/context +
+
100%7/7100%0/0100%4/4100%6/6
src/hooks +
+
100%12/12100%4/4100%5/5100%12/12
src/lib +
+
100%99/9993.54%29/31100%15/15100%93/93
src/pages +
+
88.88%56/6362.5%10/1682.6%19/2394.64%53/56
src/schemas +
+
100%15/15100%0/0100%4/4100%14/14
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/base.css b/frontend/coverage/lcov-report/base.css new file mode 100644 index 0000000..f418035 --- /dev/null +++ b/frontend/coverage/lcov-report/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/frontend/coverage/lcov-report/block-navigation.js b/frontend/coverage/lcov-report/block-navigation.js new file mode 100644 index 0000000..530d1ed --- /dev/null +++ b/frontend/coverage/lcov-report/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/frontend/coverage/lcov-report/favicon.png b/frontend/coverage/lcov-report/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1525b811a167671e9de1fa78aab9f5c0b61cef7 GIT binary patch literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 97.07% + Statements + 232/239 +
+ + +
+ 84.76% + Branches + 89/105 +
+ + +
+ 94.52% + Functions + 69/73 +
+ + +
+ 98.64% + Lines + 219/222 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
src +
+
100%1/1100%0/0100%1/1100%1/1
src/components/form +
+
100%21/2178.94%30/38100%10/10100%21/21
src/components/layout +
+
100%2/2100%0/0100%2/2100%2/2
src/components/wizard +
+
100%11/11100%14/14100%7/7100%10/10
src/constants +
+
100%8/8100%2/2100%2/2100%7/7
src/context +
+
100%7/7100%0/0100%4/4100%6/6
src/hooks +
+
100%12/12100%4/4100%5/5100%12/12
src/lib +
+
100%99/9993.54%29/31100%15/15100%93/93
src/pages +
+
88.88%56/6362.5%10/1682.6%19/2394.64%53/56
src/schemas +
+
100%15/15100%0/0100%4/4100%14/14
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/prettify.css b/frontend/coverage/lcov-report/prettify.css new file mode 100644 index 0000000..b317a7c --- /dev/null +++ b/frontend/coverage/lcov-report/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/frontend/coverage/lcov-report/prettify.js b/frontend/coverage/lcov-report/prettify.js new file mode 100644 index 0000000..b322523 --- /dev/null +++ b/frontend/coverage/lcov-report/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/frontend/coverage/lcov-report/sort-arrow-sprite.png b/frontend/coverage/lcov-report/sort-arrow-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed68316eb3f65dec9063332d2f69bf3093bbfab GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc literal 0 HcmV?d00001 diff --git a/frontend/coverage/lcov-report/sorter.js b/frontend/coverage/lcov-report/sorter.js new file mode 100644 index 0000000..4ed70ae --- /dev/null +++ b/frontend/coverage/lcov-report/sorter.js @@ -0,0 +1,210 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + + // Try to create a RegExp from the searchValue. If it fails (invalid regex), + // it will be treated as a plain text search + let searchRegex; + try { + searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive + } catch (error) { + searchRegex = null; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let isMatch = false; + + if (searchRegex) { + // If a valid regex was created, use it for matching + isMatch = searchRegex.test(row.textContent); + } else { + // Otherwise, fall back to the original plain text search + isMatch = row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()); + } + + row.style.display = isMatch ? '' : 'none'; + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/frontend/coverage/lcov-report/src/App.tsx.html b/frontend/coverage/lcov-report/src/App.tsx.html new file mode 100644 index 0000000..669544b --- /dev/null +++ b/frontend/coverage/lcov-report/src/App.tsx.html @@ -0,0 +1,169 @@ + + + + + + Code coverage report for src/App.tsx + + + + + + + + + +
+
+

All files / src App.tsx

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29  +  +  +  +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'
+import { Layout } from './components/layout/Layout'
+import { WizardFormProvider } from './context/WizardFormProvider'
+import { Step1PersonalData } from './pages/Step1PersonalData'
+import { Step2Address } from './pages/Step2Address'
+import { Step3Professional } from './pages/Step3Professional'
+import { SummaryPage } from './pages/SummaryPage'
+ 
+function App() {
+  return (
+    <BrowserRouter>
+      <WizardFormProvider>
+        <Layout>
+          <Routes>
+            <Route path="/" element={<Navigate to="/etapa-1" replace />} />
+            <Route path="/etapa-1" element={<Step1PersonalData />} />
+            <Route path="/etapa-2" element={<Step2Address />} />
+            <Route path="/etapa-3" element={<Step3Professional />} />
+            <Route path="/resumo" element={<SummaryPage />} />
+            <Route path="*" element={<Navigate to="/etapa-1" replace />} />
+          </Routes>
+        </Layout>
+      </WizardFormProvider>
+    </BrowserRouter>
+  )
+}
+ 
+export default App
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/form/CurrencyField.tsx.html b/frontend/coverage/lcov-report/src/components/form/CurrencyField.tsx.html new file mode 100644 index 0000000..c87fe89 --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/form/CurrencyField.tsx.html @@ -0,0 +1,262 @@ + + + + + + Code coverage report for src/components/form/CurrencyField.tsx + + + + + + + + + +
+
+

All files / src/components/form CurrencyField.tsx

+
+ +
+ 100% + Statements + 9/9 +
+ + +
+ 58.33% + Branches + 7/12 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 9/9 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60  +  +  +  +3x +  +  +30x +  +30x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +19x +19x +  +19x +  +  +  +  +  +  +50x +  +  +  +  +  +  +  +  +  +31x +31x +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { Controller, useFormContext } from 'react-hook-form'
+import type { FormValues } from '../../schemas/formSchema'
+import { onlyDigits } from '../../lib/masks'
+ 
+const NON_BREAKING_SPACE = ' '
+ 
+function formatBRLFromDigits(digits: string): string {
+  const numeric = Number(digits || '0') / 100
+  // toLocaleString inserts a non-breaking space after "R$"; normalize to a regular space.
+  return numeric
+    .toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })
+    .replaceAll(NON_BREAKING_SPACE, ' ')
+}
+ 
+interface CurrencyFieldProps {
+  name: keyof FormValues
+  label: string
+  fullWidth?: boolean
+}
+ 
+export function CurrencyField({ name, label, fullWidth }: Readonly<CurrencyFieldProps>) {
+  const {
+    control,
+    formState: { errors },
+  } = useFormContext<FormValues>()
+  const error = errors[name]?.message as string | undefined
+ 
+  return (
+    <div className={`field${fullWidth ? ' field-full' : ''}`}>
+      <label htmlFor={name}>{label}</label>
+      <Controller
+        name={name}
+        control={control}
+        render={({ field }) => (
+          <input
+            id={name}
+            inputMode="decimal"
+            placeholder="R$ 0,00"
+            aria-invalid={!!error}
+            aria-describedby={error ? `${name}-error` : undefined}
+            value={field.value ?? ''}
+            onChange={(event) => {
+              // Strip leading zeros so backspacing down to nothing actually clears the
+              // field instead of getting stuck re-formatting to "R$ 0,00" forever.
+              const digits = onlyDigits(event.target.value).replace(/^0+/, '').slice(0, 12)
+              field.onChange(digits ? formatBRLFromDigits(digits) : '')
+            }}
+            onBlur={field.onBlur}
+          />
+        )}
+      />
+      {error && (
+        <span className="field-error" id={`${name}-error`} role="alert">
+          {error}
+        </span>
+      )}
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/form/MaskedTextField.tsx.html b/frontend/coverage/lcov-report/src/components/form/MaskedTextField.tsx.html new file mode 100644 index 0000000..8d491fe --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/form/MaskedTextField.tsx.html @@ -0,0 +1,265 @@ + + + + + + Code coverage report for src/components/form/MaskedTextField.tsx + + + + + + + + + +
+
+

All files / src/components/form MaskedTextField.tsx

+
+ +
+ 100% + Statements + 5/5 +
+ + +
+ 90.9% + Branches + 10/11 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +88x +88x +  +88x +  +  +  +  +  +  +170x +  +  +  +  +  +  +  +  +  +114x +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { Controller, useFormContext } from 'react-hook-form'
+import { IMaskInput } from 'react-imask'
+import type { FormValues } from '../../schemas/formSchema'
+ 
+interface MaskedTextFieldProps {
+  name: keyof FormValues
+  label: string
+  maskOptions: Record<string, unknown>
+  placeholder?: string
+  inputMode?: 'text' | 'numeric' | 'decimal'
+  hint?: string
+  fullWidth?: boolean
+}
+ 
+export function MaskedTextField({
+  name,
+  label,
+  maskOptions,
+  placeholder,
+  inputMode,
+  hint,
+  fullWidth,
+}: Readonly<MaskedTextFieldProps>) {
+  const {
+    control,
+    formState: { errors },
+  } = useFormContext<FormValues>()
+  const error = errors[name]?.message as string | undefined
+ 
+  return (
+    <div className={`field${fullWidth ? ' field-full' : ''}`}>
+      <label htmlFor={name}>{label}</label>
+      <Controller
+        name={name}
+        control={control}
+        render={({ field }) => (
+          <IMaskInput
+            {...maskOptions}
+            id={name}
+            name={field.name}
+            value={field.value ?? ''}
+            unmask={false}
+            inputMode={inputMode}
+            placeholder={placeholder}
+            aria-invalid={!!error}
+            aria-describedby={error ? `${name}-error` : undefined}
+            onAccept={(value: string) => field.onChange(value)}
+            onBlur={field.onBlur}
+          />
+        )}
+      />
+      {error && (
+        <span className="field-error" id={`${name}-error`} role="alert">
+          {error}
+        </span>
+      )}
+      {!error && hint && <span className="field-hint">{hint}</span>}
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/form/SelectField.tsx.html b/frontend/coverage/lcov-report/src/components/form/SelectField.tsx.html new file mode 100644 index 0000000..1ea8025 --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/form/SelectField.tsx.html @@ -0,0 +1,229 @@ + + + + + + Code coverage report for src/components/form/SelectField.tsx + + + + + + + + + +
+
+

All files / src/components/form SelectField.tsx

+
+ +
+ 100% + Statements + 4/4 +
+ + +
+ 75% + Branches + 6/8 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 4/4 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +83x +83x +  +83x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1456x +  +  +  +  +  +  +  +  +  +  +  +  + 
import { useFormContext } from 'react-hook-form'
+import type { FormValues } from '../../schemas/formSchema'
+import type { SelectOption } from '../../constants/options'
+ 
+interface SelectFieldProps {
+  name: keyof FormValues
+  label: string
+  options: SelectOption[]
+  placeholder?: string
+  disabled?: boolean
+  fullWidth?: boolean
+}
+ 
+export function SelectField({ name, label, options, placeholder, disabled, fullWidth }: Readonly<SelectFieldProps>) {
+  const {
+    register,
+    formState: { errors },
+  } = useFormContext<FormValues>()
+  const error = errors[name]?.message as string | undefined
+ 
+  return (
+    <div className={`field${fullWidth ? ' field-full' : ''}`}>
+      <label htmlFor={name}>{label}</label>
+      <select
+        id={name}
+        disabled={disabled}
+        aria-invalid={!!error}
+        aria-describedby={error ? `${name}-error` : undefined}
+        defaultValue=""
+        {...register(name)}
+      >
+        <option value="" disabled>
+          {placeholder ?? 'Selecione...'}
+        </option>
+        {options.map((option) => (
+          <option key={option.value} value={option.value}>
+            {option.label}
+          </option>
+        ))}
+      </select>
+      {error && (
+        <span className="field-error" id={`${name}-error`} role="alert">
+          {error}
+        </span>
+      )}
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/form/TextField.tsx.html b/frontend/coverage/lcov-report/src/components/form/TextField.tsx.html new file mode 100644 index 0000000..9f9667e --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/form/TextField.tsx.html @@ -0,0 +1,232 @@ + + + + + + Code coverage report for src/components/form/TextField.tsx + + + + + + + + + +
+
+

All files / src/components/form TextField.tsx

+
+ +
+ 100% + Statements + 3/3 +
+ + +
+ 100% + Branches + 7/7 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +200x +200x +  +200x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { useFormContext } from 'react-hook-form'
+import type { FormValues } from '../../schemas/formSchema'
+ 
+interface TextFieldProps {
+  name: keyof FormValues
+  label: string
+  placeholder?: string
+  type?: string
+  autoComplete?: string
+  fullWidth?: boolean
+  readOnly?: boolean
+}
+ 
+export function TextField({
+  name,
+  label,
+  placeholder,
+  type = 'text',
+  autoComplete,
+  fullWidth,
+  readOnly,
+}: Readonly<TextFieldProps>) {
+  const {
+    register,
+    formState: { errors },
+  } = useFormContext<FormValues>()
+  const error = errors[name]?.message as string | undefined
+ 
+  return (
+    <div className={`field${fullWidth ? ' field-full' : ''}`}>
+      <label htmlFor={name}>{label}</label>
+      <input
+        id={name}
+        type={type}
+        placeholder={placeholder}
+        autoComplete={autoComplete}
+        readOnly={readOnly}
+        aria-invalid={!!error}
+        aria-describedby={error ? `${name}-error` : undefined}
+        {...register(name)}
+      />
+      {error && (
+        <span className="field-error" id={`${name}-error`} role="alert">
+          {error}
+        </span>
+      )}
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/form/index.html b/frontend/coverage/lcov-report/src/components/form/index.html new file mode 100644 index 0000000..b54a3c1 --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/form/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for src/components/form + + + + + + + + + +
+
+

All files src/components/form

+
+ +
+ 100% + Statements + 21/21 +
+ + +
+ 78.94% + Branches + 30/38 +
+ + +
+ 100% + Functions + 10/10 +
+ + +
+ 100% + Lines + 21/21 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
CurrencyField.tsx +
+
100%9/958.33%7/12100%4/4100%9/9
MaskedTextField.tsx +
+
100%5/590.9%10/11100%3/3100%5/5
SelectField.tsx +
+
100%4/475%6/8100%2/2100%4/4
TextField.tsx +
+
100%3/3100%7/7100%1/1100%3/3
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/layout/Layout.tsx.html b/frontend/coverage/lcov-report/src/components/layout/Layout.tsx.html new file mode 100644 index 0000000..ce3bcb6 --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/layout/Layout.tsx.html @@ -0,0 +1,145 @@ + + + + + + Code coverage report for src/components/layout/Layout.tsx + + + + + + + + + +
+
+

All files / src/components/layout Layout.tsx

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21  +  +  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import type { ReactNode } from 'react'
+import { Logo } from './Logo'
+import { StepIndicator } from '../wizard/StepIndicator'
+ 
+export function Layout({ children }: { children: ReactNode }) {
+  return (
+    <div className="app-shell">
+      <header className="app-header">
+        <Logo />
+      </header>
+      <StepIndicator />
+      <main className="app-main">
+        <div className="card">{children}</div>
+      </main>
+      <footer className="app-footer">
+        <p>&copy; {new Date().getFullYear()} Verity. Todos os direitos reservados.</p>
+      </footer>
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/layout/Logo.tsx.html b/frontend/coverage/lcov-report/src/components/layout/Logo.tsx.html new file mode 100644 index 0000000..c7bf3a2 --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/layout/Logo.tsx.html @@ -0,0 +1,106 @@ + + + + + + Code coverage report for src/components/layout/Logo.tsx + + + + + + + + + +
+
+

All files / src/components/layout Logo.tsx

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8  +4x +  +  +  +  +  + 
export function Logo() {
+  return (
+    <div className="logo-mark">
+      <img src="/logo.avif" alt="Verity" className="logo-image" />
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/layout/index.html b/frontend/coverage/lcov-report/src/components/layout/index.html new file mode 100644 index 0000000..e0e40fa --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/layout/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for src/components/layout + + + + + + + + + +
+
+

All files src/components/layout

+
+ +
+ 100% + Statements + 2/2 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 2/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
Layout.tsx +
+
100%1/1100%0/0100%1/1100%1/1
Logo.tsx +
+
100%1/1100%0/0100%1/1100%1/1
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/wizard/StepIndicator.tsx.html b/frontend/coverage/lcov-report/src/components/wizard/StepIndicator.tsx.html new file mode 100644 index 0000000..724168d --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/wizard/StepIndicator.tsx.html @@ -0,0 +1,166 @@ + + + + + + Code coverage report for src/components/wizard/StepIndicator.tsx + + + + + + + + + +
+
+

All files / src/components/wizard StepIndicator.tsx

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 100% + Branches + 6/6 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 6/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28  +  +3x +  +  +  +  +  +  +  +10x +26x +  +10x +  +  +40x +40x +  +  +  +  +  +  +  +  +  + 
import { useLocation } from 'react-router-dom'
+ 
+const STEPS = [
+  { path: '/etapa-1', label: 'Dados Pessoais' },
+  { path: '/etapa-2', label: 'Endereço' },
+  { path: '/etapa-3', label: 'Profissional' },
+  { path: '/resumo', label: 'Resumo' },
+]
+ 
+export function StepIndicator() {
+  const { pathname } = useLocation()
+  const currentIndex = STEPS.findIndex((step) => step.path === pathname)
+ 
+  return (
+    <ol className="step-indicator">
+      {STEPS.map((step, index) => {
+        const status = index < currentIndex ? 'done' : index === currentIndex ? 'active' : 'pending'
+        return (
+          <li key={step.path} className={`step step-${status}`}>
+            <span className="step-index">{status === 'done' ? '✓' : index + 1}</span>
+            <span className="step-label">{step.label}</span>
+          </li>
+        )
+      })}
+    </ol>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/wizard/SummarySection.tsx.html b/frontend/coverage/lcov-report/src/components/wizard/SummarySection.tsx.html new file mode 100644 index 0000000..802e536 --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/wizard/SummarySection.tsx.html @@ -0,0 +1,178 @@ + + + + + + Code coverage report for src/components/wizard/SummarySection.tsx + + + + + + + + + +
+
+

All files / src/components/wizard SummarySection.tsx

+
+ +
+ 100% + Statements + 2/2 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 2/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32  +  +  +  +  +  +  +  +  +  +  +  +32x +  +  +  +  +  +  +  +  +  +142x +  +  +  +  +  +  +  +  + 
interface SummaryItem {
+  label: string
+  value: string
+}
+ 
+interface SummarySectionProps {
+  title: string
+  items: SummaryItem[]
+  onEdit: () => void
+}
+ 
+export function SummarySection({ title, items, onEdit }: Readonly<SummarySectionProps>) {
+  return (
+    <section className="summary-section">
+      <div className="summary-section-header">
+        <h3>{title}</h3>
+        <button type="button" className="btn-link" onClick={onEdit}>
+          Editar
+        </button>
+      </div>
+      <dl className="summary-list">
+        {items.map((item) => (
+          <div className="summary-row" key={item.label}>
+            <dt>{item.label}</dt>
+            <dd>{item.value || '—'}</dd>
+          </div>
+        ))}
+      </dl>
+    </section>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/wizard/WizardActions.tsx.html b/frontend/coverage/lcov-report/src/components/wizard/WizardActions.tsx.html new file mode 100644 index 0000000..eaf7dbe --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/wizard/WizardActions.tsx.html @@ -0,0 +1,148 @@ + + + + + + Code coverage report for src/components/wizard/WizardActions.tsx + + + + + + + + + +
+
+

All files / src/components/wizard WizardActions.tsx

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22  +  +  +  +  +  +  +  +77x +  +  +  +  +  +  +  +  +  +  +  +  + 
interface WizardActionsProps {
+  onBack?: () => void
+  onNext: () => void
+  nextLabel?: string
+  backLabel?: string
+}
+ 
+export function WizardActions({ onBack, onNext, nextLabel = 'Próximo', backLabel = 'Voltar' }: Readonly<WizardActionsProps>) {
+  return (
+    <div className="wizard-actions">
+      {onBack && (
+        <button type="button" className="btn btn-secondary" onClick={onBack}>
+          {backLabel}
+        </button>
+      )}
+      <button type="button" className="btn btn-primary" onClick={onNext}>
+        {nextLabel}
+      </button>
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/wizard/WizardStepShell.tsx.html b/frontend/coverage/lcov-report/src/components/wizard/WizardStepShell.tsx.html new file mode 100644 index 0000000..bca69fd --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/wizard/WizardStepShell.tsx.html @@ -0,0 +1,136 @@ + + + + + + Code coverage report for src/components/wizard/WizardStepShell.tsx + + + + + + + + + +
+
+

All files / src/components/wizard WizardStepShell.tsx

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18  +  +  +  +  +  +  +  +  +87x +  +  +  +  +  +  +  + 
import type { ReactNode } from 'react'
+ 
+interface WizardStepShellProps {
+  title: string
+  description?: string
+  children: ReactNode
+}
+ 
+export function WizardStepShell({ title, description, children }: Readonly<WizardStepShellProps>) {
+  return (
+    <section>
+      <h1 className="step-title">{title}</h1>
+      {description && <p className="step-description">{description}</p>}
+      {children}
+    </section>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/wizard/index.html b/frontend/coverage/lcov-report/src/components/wizard/index.html new file mode 100644 index 0000000..f9a820f --- /dev/null +++ b/frontend/coverage/lcov-report/src/components/wizard/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for src/components/wizard + + + + + + + + + +
+
+

All files src/components/wizard

+
+ +
+ 100% + Statements + 11/11 +
+ + +
+ 100% + Branches + 14/14 +
+ + +
+ 100% + Functions + 7/7 +
+ + +
+ 100% + Lines + 10/10 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
StepIndicator.tsx +
+
100%7/7100%6/6100%3/3100%6/6
SummarySection.tsx +
+
100%2/2100%2/2100%2/2100%2/2
WizardActions.tsx +
+
100%1/1100%4/4100%1/1100%1/1
WizardStepShell.tsx +
+
100%1/1100%2/2100%1/1100%1/1
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/constants/index.html b/frontend/coverage/lcov-report/src/constants/index.html new file mode 100644 index 0000000..9865a11 --- /dev/null +++ b/frontend/coverage/lcov-report/src/constants/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for src/constants + + + + + + + + + +
+
+

All files src/constants

+
+ +
+ 100% + Statements + 8/8 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 7/7 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
masks.ts +
+
100%4/4100%0/0100%0/0100%4/4
options.ts +
+
100%4/4100%2/2100%2/2100%3/3
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/constants/masks.ts.html b/frontend/coverage/lcov-report/src/constants/masks.ts.html new file mode 100644 index 0000000..8c878d6 --- /dev/null +++ b/frontend/coverage/lcov-report/src/constants/masks.ts.html @@ -0,0 +1,112 @@ + + + + + + Code coverage report for src/constants/masks.ts + + + + + + + + + +
+
+

All files / src/constants masks.ts

+
+ +
+ 100% + Statements + 4/4 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 4/4 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +104x +  +4x +  +4x +  +4x +  +  + 
export const CPF_MASK = { mask: '000.000.000-00' }
+ 
+export const DATE_MASK = { mask: '00/00/0000' }
+ 
+export const CEP_MASK = { mask: '00000-000' }
+ 
+export const PHONE_MASK = {
+  mask: [{ mask: '(00) 0000-0000' }, { mask: '(00) 00000-0000' }],
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/constants/options.ts.html b/frontend/coverage/lcov-report/src/constants/options.ts.html new file mode 100644 index 0000000..062c8e2 --- /dev/null +++ b/frontend/coverage/lcov-report/src/constants/options.ts.html @@ -0,0 +1,223 @@ + + + + + + Code coverage report for src/constants/options.ts + + + + + + + + + +
+
+

All files / src/constants options.ts

+
+ +
+ 100% + Statements + 4/4 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47  +  +  +  +  +15x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +15x +  +  +  +  +  +  +  +  +407x +  + 
export interface SelectOption {
+  value: string
+  label: string
+}
+ 
+export const UF_OPTIONS: SelectOption[] = [
+  { value: 'AC', label: 'Acre' },
+  { value: 'AL', label: 'Alagoas' },
+  { value: 'AP', label: 'Amapá' },
+  { value: 'AM', label: 'Amazonas' },
+  { value: 'BA', label: 'Bahia' },
+  { value: 'CE', label: 'Ceará' },
+  { value: 'DF', label: 'Distrito Federal' },
+  { value: 'ES', label: 'Espírito Santo' },
+  { value: 'GO', label: 'Goiás' },
+  { value: 'MA', label: 'Maranhão' },
+  { value: 'MT', label: 'Mato Grosso' },
+  { value: 'MS', label: 'Mato Grosso do Sul' },
+  { value: 'MG', label: 'Minas Gerais' },
+  { value: 'PA', label: 'Pará' },
+  { value: 'PB', label: 'Paraíba' },
+  { value: 'PR', label: 'Paraná' },
+  { value: 'PE', label: 'Pernambuco' },
+  { value: 'PI', label: 'Piauí' },
+  { value: 'RJ', label: 'Rio de Janeiro' },
+  { value: 'RN', label: 'Rio Grande do Norte' },
+  { value: 'RS', label: 'Rio Grande do Sul' },
+  { value: 'RO', label: 'Rondônia' },
+  { value: 'RR', label: 'Roraima' },
+  { value: 'SC', label: 'Santa Catarina' },
+  { value: 'SP', label: 'São Paulo' },
+  { value: 'SE', label: 'Sergipe' },
+  { value: 'TO', label: 'Tocantins' },
+]
+ 
+export const TEMPO_EMPRESA_OPTIONS: SelectOption[] = [
+  { value: 'menos-6-meses', label: 'Menos de 6 meses' },
+  { value: '6-meses-1-ano', label: 'De 6 meses a 1 ano' },
+  { value: '1-2-anos', label: 'De 1 a 2 anos' },
+  { value: '2-5-anos', label: 'De 2 a 5 anos' },
+  { value: 'mais-5-anos', label: 'Mais de 5 anos' },
+]
+ 
+export function getOptionLabel(options: SelectOption[], value: string): string {
+  return options.find((option) => option.value === value)?.label ?? value
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/context/WizardFormProvider.tsx.html b/frontend/coverage/lcov-report/src/context/WizardFormProvider.tsx.html new file mode 100644 index 0000000..f79a0dd --- /dev/null +++ b/frontend/coverage/lcov-report/src/context/WizardFormProvider.tsx.html @@ -0,0 +1,163 @@ + + + + + + Code coverage report for src/context/WizardFormProvider.tsx + + + + + + + + + +
+
+

All files / src/context WizardFormProvider.tsx

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 6/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27  +  +  +  +  +  +  +17x +  +  +  +  +  +  +  +  +  +17x +5x +98x +  +5x +  +  +17x +  + 
import { useEffect, type ReactNode } from 'react'
+import { FormProvider, useForm } from 'react-hook-form'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { defaultFormValues, formSchema, type FormValues } from '../schemas/formSchema'
+import { loadStoredFormValues, persistFormValues } from '../lib/storage'
+ 
+export function WizardFormProvider({ children }: { children: ReactNode }) {
+  const methods = useForm<FormValues>({
+    resolver: zodResolver(formSchema),
+    mode: 'onSubmit',
+    reValidateMode: 'onChange',
+    defaultValues: {
+      ...defaultFormValues,
+      ...loadStoredFormValues<FormValues>(),
+    },
+  })
+ 
+  useEffect(() => {
+    const subscription = methods.watch((values) => {
+      persistFormValues(values)
+    })
+    return () => subscription.unsubscribe()
+  }, [methods])
+ 
+  return <FormProvider {...methods}>{children}</FormProvider>
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/context/index.html b/frontend/coverage/lcov-report/src/context/index.html new file mode 100644 index 0000000..7e5fcd3 --- /dev/null +++ b/frontend/coverage/lcov-report/src/context/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for src/context + + + + + + + + + +
+
+

All files src/context

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 6/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
WizardFormProvider.tsx +
+
100%7/7100%0/0100%4/4100%6/6
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/hooks/index.html b/frontend/coverage/lcov-report/src/hooks/index.html new file mode 100644 index 0000000..9e9d301 --- /dev/null +++ b/frontend/coverage/lcov-report/src/hooks/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for src/hooks + + + + + + + + + +
+
+

All files src/hooks

+
+ +
+ 100% + Statements + 12/12 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 100% + Lines + 12/12 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
useRequireStepData.ts +
+
100%5/5100%2/2100%2/2100%5/5
useStepNavigation.ts +
+
100%7/7100%2/2100%3/3100%7/7
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/hooks/useRequireStepData.ts.html b/frontend/coverage/lcov-report/src/hooks/useRequireStepData.ts.html new file mode 100644 index 0000000..3ade7ae --- /dev/null +++ b/frontend/coverage/lcov-report/src/hooks/useRequireStepData.ts.html @@ -0,0 +1,145 @@ + + + + + + Code coverage report for src/hooks/useRequireStepData.ts + + + + + + + + + +
+
+

All files / src/hooks useRequireStepData.ts

+
+ +
+ 100% + Statements + 5/5 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21  +  +  +  +  +  +  +  +  +  +110x +110x +  +110x +16x +1x +  +  +  +  + 
import { useEffect } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { useFormContext } from 'react-hook-form'
+import type { FormValues } from '../schemas/formSchema'
+ 
+/**
+ * Guards a step against direct URL access before its prerequisite data exists,
+ * e.g. opening /resumo without having gone through the earlier steps.
+ */
+export function useRequireStepData(requiredField: keyof FormValues, redirectTo: string) {
+  const { getValues } = useFormContext<FormValues>()
+  const navigate = useNavigate()
+ 
+  useEffect(() => {
+    if (!getValues(requiredField)) {
+      navigate(redirectTo, { replace: true })
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [])
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/hooks/useStepNavigation.ts.html b/frontend/coverage/lcov-report/src/hooks/useStepNavigation.ts.html new file mode 100644 index 0000000..2e5e5db --- /dev/null +++ b/frontend/coverage/lcov-report/src/hooks/useStepNavigation.ts.html @@ -0,0 +1,148 @@ + + + + + + Code coverage report for src/hooks/useStepNavigation.ts + + + + + + + + + +
+
+

All files / src/hooks useStepNavigation.ts

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 7/7 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22  +  +  +  +  +115x +115x +  +  +9x +9x +7x +  +  +  +  +2x +  +  +115x +  + 
import { useNavigate } from 'react-router-dom'
+import { useFormContext } from 'react-hook-form'
+import type { FormValues } from '../schemas/formSchema'
+ 
+export function useStepNavigation(fields: (keyof FormValues)[]) {
+  const { trigger } = useFormContext<FormValues>()
+  const navigate = useNavigate()
+ 
+  async function goNext(path: string) {
+    const isValid = await trigger(fields)
+    if (isValid) {
+      navigate(path)
+    }
+  }
+ 
+  function goBack(path: string) {
+    navigate(path)
+  }
+ 
+  return { goNext, goBack }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/index.html b/frontend/coverage/lcov-report/src/index.html new file mode 100644 index 0000000..aa70c48 --- /dev/null +++ b/frontend/coverage/lcov-report/src/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for src + + + + + + + + + +
+
+

All files src

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
App.tsx +
+
100%1/1100%0/0100%1/1100%1/1
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/api.ts.html b/frontend/coverage/lcov-report/src/lib/api.ts.html new file mode 100644 index 0000000..3a2708b --- /dev/null +++ b/frontend/coverage/lcov-report/src/lib/api.ts.html @@ -0,0 +1,277 @@ + + + + + + Code coverage report for src/lib/api.ts + + + + + + + + + +
+
+

All files / src/lib api.ts

+
+ +
+ 100% + Statements + 17/17 +
+ + +
+ 83.33% + Branches + 5/6 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 15/15 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +4x +1x +  +3x +  +  +  +  +3x +3x +2x +1x +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +4x +4x +3x +  +  +  +  +  +  +  +  +1x +1x +  + 
import axios from 'axios'
+ 
+export const mockApi = axios.create({
+  baseURL: import.meta.env.VITE_MOCK_API_URL ?? 'http://localhost:3001',
+})
+ 
+export interface CepResult {
+  endereco: string
+  bairro: string
+  cidade: string
+  estado: string
+}
+ 
+interface ViaCepResponse {
+  logradouro: string
+  bairro: string
+  localidade: string
+  uf: string
+  erro?: boolean
+}
+ 
+async function fetchCepFromMock(cep: string): Promise<CepResult | null> {
+  try {
+    const { data } = await mockApi.get<CepResult>(`/ceps/${cep}`)
+    return data
+  } catch {
+    return null
+  }
+}
+ 
+async function fetchCepFromViaCep(cep: string): Promise<CepResult | null> {
+  try {
+    const { data } = await axios.get<ViaCepResponse>(`https://viacep.com.br/ws/${cep}/json/`)
+    if (data.erro) return null
+    return {
+      endereco: data.logradouro,
+      bairro: data.bairro,
+      cidade: data.localidade,
+      estado: data.uf,
+    }
+  } catch {
+    return null
+  }
+}
+ 
+/**
+ * Tries the mocked json-server first (challenge requirement) and falls back
+ * to the public ViaCEP service when the CEP isn't present in the mock db (bonus requirement).
+ */
+export async function fetchAddressByCep(cep: string): Promise<CepResult | null> {
+  const mockResult = await fetchCepFromMock(cep)
+  if (mockResult) return mockResult
+  return fetchCepFromViaCep(cep)
+}
+ 
+export interface Profession {
+  id: number | string
+  name: string
+}
+ 
+export async function fetchProfessions(): Promise<Profession[]> {
+  const { data } = await mockApi.get<Profession[]>('/professions')
+  return data
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/cpf.ts.html b/frontend/coverage/lcov-report/src/lib/cpf.ts.html new file mode 100644 index 0000000..7ade177 --- /dev/null +++ b/frontend/coverage/lcov-report/src/lib/cpf.ts.html @@ -0,0 +1,163 @@ + + + + + + Code coverage report for src/lib/cpf.ts + + + + + + + + + +
+
+

All files / src/lib cpf.ts

+
+ +
+ 100% + Statements + 14/14 +
+ + +
+ 87.5% + Branches + 7/8 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 13/13 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27  +39x +  +  +  +34x +34x +323x +  +34x +34x +  +  +  +37x +  +37x +20x +  +  +17x +17x +17x +  +17x +  + 
export function onlyDigits(value: string): string {
+  return value.replace(/\D/g, '')
+}
+ 
+function calcCheckDigit(digits: number[], length: number): number {
+  let sum = 0
+  for (let i = 0; i < length; i++) {
+    sum += digits[i] * (length + 1 - i)
+  }
+  const remainder = (sum * 10) % 11
+  return remainder === 10 ? 0 : remainder
+}
+ 
+export function isValidCPF(rawValue: string): boolean {
+  const cpf = onlyDigits(rawValue)
+ 
+  if (cpf.length !== 11 || /^(\d)\1{10}$/.test(cpf)) {
+    return false
+  }
+ 
+  const digits = cpf.split('').map(Number)
+  const firstCheckDigit = calcCheckDigit(digits, 9)
+  const secondCheckDigit = calcCheckDigit(digits, 10)
+ 
+  return firstCheckDigit === digits[9] && secondCheckDigit === digits[10]
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/index.html b/frontend/coverage/lcov-report/src/lib/index.html new file mode 100644 index 0000000..b815449 --- /dev/null +++ b/frontend/coverage/lcov-report/src/lib/index.html @@ -0,0 +1,176 @@ + + + + + + Code coverage report for src/lib + + + + + + + + + +
+
+

All files src/lib

+
+ +
+ 100% + Statements + 99/99 +
+ + +
+ 93.54% + Branches + 29/31 +
+ + +
+ 100% + Functions + 15/15 +
+ + +
+ 100% + Lines + 93/93 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
api.ts +
+
100%17/1783.33%5/6100%4/4100%15/15
cpf.ts +
+
100%14/1487.5%7/8100%3/3100%13/13
masks.ts +
+
100%17/17100%11/11100%3/3100%15/15
pdf.ts +
+
100%41/41100%4/4100%2/2100%41/41
storage.ts +
+
100%10/10100%2/2100%3/3100%9/9
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/masks.ts.html b/frontend/coverage/lcov-report/src/lib/masks.ts.html new file mode 100644 index 0000000..3c7fe8e --- /dev/null +++ b/frontend/coverage/lcov-report/src/lib/masks.ts.html @@ -0,0 +1,178 @@ + + + + + + Code coverage report for src/lib/masks.ts + + + + + + + + + +
+
+

All files / src/lib masks.ts

+
+ +
+ 100% + Statements + 17/17 +
+ + +
+ 100% + Branches + 11/11 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 15/15 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32  +69x +  +  +  +37x +37x +  +21x +21x +21x +21x +  +  +21x +  +  +  +37x +  +17x +17x +  +17x +  +  +  +31x +31x +31x +  + 
export function onlyDigits(value: string): string {
+  return value.replace(/\D/g, '')
+}
+ 
+export function isValidBirthDate(value: string): boolean {
+  const match = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value)
+  if (!match) return false
+ 
+  const day = Number(match[1])
+  const month = Number(match[2])
+  const year = Number(match[3])
+  const date = new Date(year, month - 1, day)
+ 
+  const isRealCalendarDate =
+    date.getFullYear() === year &&
+    date.getMonth() === month - 1 &&
+    date.getDate() === day
+ 
+  if (!isRealCalendarDate) return false
+ 
+  const today = new Date()
+  today.setHours(0, 0, 0, 0)
+ 
+  return date <= today && year >= 1900
+}
+ 
+export function parseCurrencyToNumber(value: string): number {
+  const normalized = value.replace(/[^\d,-]/g, '').replace(',', '.')
+  const parsed = Number.parseFloat(normalized)
+  return Number.isNaN(parsed) ? 0 : parsed
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/pdf.ts.html b/frontend/coverage/lcov-report/src/lib/pdf.ts.html new file mode 100644 index 0000000..81aeb55 --- /dev/null +++ b/frontend/coverage/lcov-report/src/lib/pdf.ts.html @@ -0,0 +1,370 @@ + + + + + + Code coverage report for src/lib/pdf.ts + + + + + + + + + +
+
+

All files / src/lib pdf.ts

+
+ +
+ 100% + Statements + 41/41 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 41/41 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96  +  +  +  +1x +1x +1x +1x +  +  +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +2x +  +2x +2x +2x +2x +2x +2x +2x +2x +2x +  +2x +  +2x +6x +6x +6x +6x +6x +6x +6x +6x +  +6x +6x +28x +28x +28x +28x +28x +28x +  +  +6x +  +  +2x +2x +2x +2x +  +2x +2x +  + 
import { jsPDF } from 'jspdf'
+import type { FormValues } from '../schemas/formSchema'
+import { getOptionLabel, TEMPO_EMPRESA_OPTIONS, UF_OPTIONS } from '../constants/options'
+ 
+const VERITY_BLUE: [number, number, number] = [13, 71, 161]
+const VERITY_BLUE_DARK: [number, number, number] = [8, 43, 99]
+const TEXT_GRAY: [number, number, number] = [60, 68, 82]
+const MARGIN_X = 16
+ 
+interface Section {
+  title: string
+  rows: [string, string][]
+}
+ 
+function buildSections(data: FormValues): Section[] {
+  return [
+    {
+      title: 'Dados Pessoais',
+      rows: [
+        ['Nome completo', data.fullName],
+        ['Data de nascimento', data.birthDate],
+        ['CPF', data.cpf],
+        ['Telefone', data.phone],
+        ['E-mail', data.email],
+      ],
+    },
+    {
+      title: 'Informações Residenciais',
+      rows: [
+        ['CEP', data.cep],
+        ['Endereço', data.endereco],
+        ['Bairro', data.bairro],
+        ['Cidade', data.cidade],
+        ['Estado', getOptionLabel(UF_OPTIONS, data.estado)],
+      ],
+    },
+    {
+      title: 'Informações Profissionais',
+      rows: [
+        ['Empresa', data.empresa],
+        ['Profissão', data.profissao],
+        ['Salário', data.salario],
+        ['Tempo de empresa', getOptionLabel(TEMPO_EMPRESA_OPTIONS, data.tempoDeEmpresa)],
+      ],
+    },
+  ]
+}
+ 
+export function generateSummaryPdf(data: FormValues): void {
+  const doc = new jsPDF()
+  const pageWidth = doc.internal.pageSize.getWidth()
+ 
+  doc.setFillColor(...VERITY_BLUE)
+  doc.rect(0, 0, pageWidth, 30, 'F')
+  doc.setTextColor(255, 255, 255)
+  doc.setFont('helvetica', 'bold')
+  doc.setFontSize(18)
+  doc.text('verity', MARGIN_X, 19)
+  doc.setFontSize(11)
+  doc.setFont('helvetica', 'normal')
+  doc.text('Resumo do Cadastro', pageWidth - MARGIN_X, 19, { align: 'right' })
+ 
+  let cursorY = 44
+ 
+  for (const section of buildSections(data)) {
+    doc.setTextColor(...VERITY_BLUE_DARK)
+    doc.setFont('helvetica', 'bold')
+    doc.setFontSize(13)
+    doc.text(section.title, MARGIN_X, cursorY)
+    cursorY += 3
+    doc.setDrawColor(...VERITY_BLUE)
+    doc.line(MARGIN_X, cursorY, pageWidth - MARGIN_X, cursorY)
+    cursorY += 8
+ 
+    doc.setFontSize(11)
+    for (const [label, value] of section.rows) {
+      doc.setFont('helvetica', 'bold')
+      doc.setTextColor(...TEXT_GRAY)
+      doc.text(`${label}:`, MARGIN_X, cursorY)
+      doc.setFont('helvetica', 'normal')
+      doc.text(value || '—', MARGIN_X + 55, cursorY)
+      cursorY += 7
+    }
+ 
+    cursorY += 6
+  }
+ 
+  const generatedAt = new Date().toLocaleString('pt-BR')
+  doc.setFontSize(9)
+  doc.setTextColor(...TEXT_GRAY)
+  doc.text(`Gerado em ${generatedAt}`, MARGIN_X, doc.internal.pageSize.getHeight() - 10)
+ 
+  const fileSuffix = data.fullName.trim().toLowerCase().replace(/\s+/g, '-') || 'cadastro'
+  doc.save(`verity-cadastro-${fileSuffix}.pdf`)
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/storage.ts.html b/frontend/coverage/lcov-report/src/lib/storage.ts.html new file mode 100644 index 0000000..8897289 --- /dev/null +++ b/frontend/coverage/lcov-report/src/lib/storage.ts.html @@ -0,0 +1,154 @@ + + + + + + Code coverage report for src/lib/storage.ts + + + + + + + + + +
+
+

All files / src/lib storage.ts

+
+ +
+ 100% + Statements + 10/10 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 9/9 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +243x +  +  +21x +21x +21x +11x +  +1x +  +  +  +  +102x +102x +  +  +  +  +  +  +1x +  + 
const STORAGE_KEY = 'verity:cadastro-wizard'
+ 
+export function loadStoredFormValues<T>(): Partial<T> | undefined {
+  try {
+    const raw = window.localStorage.getItem(STORAGE_KEY)
+    if (!raw) return undefined
+    return JSON.parse(raw) as Partial<T>
+  } catch {
+    return undefined
+  }
+}
+ 
+export function persistFormValues(values: unknown): void {
+  try {
+    window.localStorage.setItem(STORAGE_KEY, JSON.stringify(values))
+  } catch {
+    // storage unavailable (e.g. private mode) — form still works in-memory
+  }
+}
+ 
+export function clearStoredFormValues(): void {
+  window.localStorage.removeItem(STORAGE_KEY)
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/pages/Step1PersonalData.tsx.html b/frontend/coverage/lcov-report/src/pages/Step1PersonalData.tsx.html new file mode 100644 index 0000000..24de838 --- /dev/null +++ b/frontend/coverage/lcov-report/src/pages/Step1PersonalData.tsx.html @@ -0,0 +1,193 @@ + + + + + + Code coverage report for src/pages/Step1PersonalData.tsx + + + + + + + + + +
+
+

All files / src/pages Step1PersonalData.tsx

+
+ +
+ 100% + Statements + 3/3 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37  +  +  +  +  +  +  +  +  +11x +  +11x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +  +  +  + 
import { TextField } from '../components/form/TextField'
+import { MaskedTextField } from '../components/form/MaskedTextField'
+import { WizardActions } from '../components/wizard/WizardActions'
+import { WizardStepShell } from '../components/wizard/WizardStepShell'
+import { useStepNavigation } from '../hooks/useStepNavigation'
+import { STEP1_FIELDS } from '../schemas/formSchema'
+import { CPF_MASK, DATE_MASK, PHONE_MASK } from '../constants/masks'
+ 
+export function Step1PersonalData() {
+  const { goNext } = useStepNavigation(STEP1_FIELDS)
+ 
+  return (
+    <WizardStepShell title="Dados Pessoais" description="Preencha suas informações pessoais para começar.">
+      <div className="field-grid">
+        <TextField name="fullName" label="Nome completo" autoComplete="name" fullWidth />
+        <MaskedTextField
+          name="birthDate"
+          label="Data de nascimento"
+          maskOptions={DATE_MASK}
+          placeholder="dd/mm/aaaa"
+          inputMode="numeric"
+        />
+        <MaskedTextField name="cpf" label="CPF" maskOptions={CPF_MASK} placeholder="000.000.000-00" inputMode="numeric" />
+        <MaskedTextField
+          name="phone"
+          label="Telefone"
+          maskOptions={PHONE_MASK}
+          placeholder="(00) 00000-0000"
+          inputMode="numeric"
+        />
+        <TextField name="email" label="E-mail" type="email" autoComplete="email" />
+      </div>
+      <WizardActions onNext={() => goNext('/etapa-2')} />
+    </WizardStepShell>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/pages/Step2Address.tsx.html b/frontend/coverage/lcov-report/src/pages/Step2Address.tsx.html new file mode 100644 index 0000000..aa7fba2 --- /dev/null +++ b/frontend/coverage/lcov-report/src/pages/Step2Address.tsx.html @@ -0,0 +1,328 @@ + + + + + + Code coverage report for src/pages/Step2Address.tsx + + + + + + + + + +
+
+

All files / src/pages Step2Address.tsx

+
+ +
+ 96.42% + Statements + 27/28 +
+ + +
+ 75% + Branches + 6/8 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 100% + Lines + 26/26 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  +  +83x +83x +83x +83x +83x +  +83x +37x +37x +33x +33x +  +  +4x +4x +  +4x +4x +4x +3x +3x +3x +3x +3x +  +1x +  +  +  +4x +4x +  +  +  +83x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  + 
import { useEffect, useState } from 'react'
+import { useFormContext, useWatch } from 'react-hook-form'
+import { TextField } from '../components/form/TextField'
+import { SelectField } from '../components/form/SelectField'
+import { MaskedTextField } from '../components/form/MaskedTextField'
+import { WizardActions } from '../components/wizard/WizardActions'
+import { WizardStepShell } from '../components/wizard/WizardStepShell'
+import { useStepNavigation } from '../hooks/useStepNavigation'
+import { useRequireStepData } from '../hooks/useRequireStepData'
+import { STEP2_FIELDS, type FormValues } from '../schemas/formSchema'
+import { CEP_MASK } from '../constants/masks'
+import { UF_OPTIONS } from '../constants/options'
+import { fetchAddressByCep } from '../lib/api'
+import { onlyDigits } from '../lib/masks'
+ 
+type CepStatus = 'idle' | 'loading' | 'success' | 'not-found'
+ 
+const CEP_STATUS_HINTS: Partial<Record<CepStatus, string>> = {
+  loading: 'Buscando endereço...',
+  'not-found': 'CEP não encontrado, preencha manualmente.',
+}
+ 
+export function Step2Address() {
+  useRequireStepData('fullName', '/etapa-1')
+  const { control, setValue } = useFormContext<FormValues>()
+  const { goNext, goBack } = useStepNavigation(STEP2_FIELDS)
+  const [cepStatus, setCepStatus] = useState<CepStatus>('idle')
+  const cep = useWatch({ control, name: 'cep' })
+ 
+  useEffect(() => {
+    const digits = onlyDigits(cep ?? '')
+    if (digits.length !== 8) {
+      setCepStatus('idle')
+      return
+    }
+ 
+    let cancelled = false
+    setCepStatus('loading')
+ 
+    fetchAddressByCep(digits).then((result) => {
+      Iif (cancelled) return
+      if (result) {
+        setValue('endereco', result.endereco, { shouldValidate: true })
+        setValue('bairro', result.bairro, { shouldValidate: true })
+        setValue('cidade', result.cidade, { shouldValidate: true })
+        setValue('estado', result.estado.toUpperCase(), { shouldValidate: true })
+        setCepStatus('success')
+      } else {
+        setCepStatus('not-found')
+      }
+    })
+ 
+    return () => {
+      cancelled = true
+    }
+  }, [cep, setValue])
+ 
+  return (
+    <WizardStepShell
+      title="Informações Residenciais"
+      description="Informe o CEP para preenchermos o endereço automaticamente."
+    >
+      <div className="field-grid">
+        <MaskedTextField
+          name="cep"
+          label="CEP"
+          maskOptions={CEP_MASK}
+          placeholder="00000-000"
+          inputMode="numeric"
+          fullWidth
+          hint={CEP_STATUS_HINTS[cepStatus]}
+        />
+        <TextField name="endereco" label="Endereço" fullWidth />
+        <TextField name="bairro" label="Bairro" />
+        <TextField name="cidade" label="Cidade" />
+        <SelectField name="estado" label="Estado" options={UF_OPTIONS} placeholder="UF" />
+      </div>
+      <WizardActions onBack={() => goBack('/etapa-1')} onNext={() => goNext('/etapa-3')} />
+    </WizardStepShell>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/pages/Step3Professional.tsx.html b/frontend/coverage/lcov-report/src/pages/Step3Professional.tsx.html new file mode 100644 index 0000000..113c915 --- /dev/null +++ b/frontend/coverage/lcov-report/src/pages/Step3Professional.tsx.html @@ -0,0 +1,253 @@ + + + + + + Code coverage report for src/pages/Step3Professional.tsx + + + + + + + + + +
+
+

All files / src/pages Step3Professional.tsx

+
+ +
+ 80% + Statements + 16/20 +
+ + +
+ 50% + Branches + 4/8 +
+ + +
+ 77.77% + Functions + 7/9 +
+ + +
+ 93.33% + Lines + 14/15 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57  +  +  +  +  +  +  +  +  +  +  +  +  +13x +13x +13x +13x +  +13x +4x +  +4x +  +4x +4x +  +  +  +  +  +4x +  +  +4x +4x +  +  +  +13x +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  + 
import { useEffect, useState } from 'react'
+import { TextField } from '../components/form/TextField'
+import { SelectField } from '../components/form/SelectField'
+import { CurrencyField } from '../components/form/CurrencyField'
+import { WizardActions } from '../components/wizard/WizardActions'
+import { WizardStepShell } from '../components/wizard/WizardStepShell'
+import { useStepNavigation } from '../hooks/useStepNavigation'
+import { useRequireStepData } from '../hooks/useRequireStepData'
+import { STEP3_FIELDS } from '../schemas/formSchema'
+import { TEMPO_EMPRESA_OPTIONS, type SelectOption } from '../constants/options'
+import { fetchProfessions } from '../lib/api'
+ 
+export function Step3Professional() {
+  useRequireStepData('endereco', '/etapa-2')
+  const { goNext, goBack } = useStepNavigation(STEP3_FIELDS)
+  const [professionOptions, setProfessionOptions] = useState<SelectOption[]>([])
+  const [loadingProfessions, setLoadingProfessions] = useState(true)
+ 
+  useEffect(() => {
+    let cancelled = false
+ 
+    fetchProfessions()
+      .then((professions) => {
+        Iif (cancelled) return
+        setProfessionOptions(professions.map((p) => ({ value: p.name, label: p.name })))
+      })
+      .catch(() => {
+        if (!cancelled) setProfessionOptions([])
+      })
+      .finally(() => {
+        Eif (!cancelled) setLoadingProfessions(false)
+      })
+ 
+    return () => {
+      cancelled = true
+    }
+  }, [])
+ 
+  return (
+    <WizardStepShell title="Informações Profissionais" description="Conte-nos um pouco sobre sua vida profissional.">
+      <div className="field-grid">
+        <TextField name="empresa" label="Empresa" fullWidth />
+        <SelectField
+          name="profissao"
+          label="Profissão"
+          options={professionOptions}
+          placeholder={loadingProfessions ? 'Carregando profissões...' : 'Selecione...'}
+          disabled={loadingProfessions}
+        />
+        <CurrencyField name="salario" label="Salário" />
+        <SelectField name="tempoDeEmpresa" label="Tempo de empresa" options={TEMPO_EMPRESA_OPTIONS} fullWidth />
+      </div>
+      <WizardActions onBack={() => goBack('/etapa-2')} onNext={() => goNext('/resumo')} nextLabel="Ver resumo" />
+    </WizardStepShell>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/pages/SummaryPage.tsx.html b/frontend/coverage/lcov-report/src/pages/SummaryPage.tsx.html new file mode 100644 index 0000000..8a30abe --- /dev/null +++ b/frontend/coverage/lcov-report/src/pages/SummaryPage.tsx.html @@ -0,0 +1,298 @@ + + + + + + Code coverage report for src/pages/SummaryPage.tsx + + + + + + + + + +
+
+

All files / src/pages SummaryPage.tsx

+
+ +
+ 83.33% + Statements + 10/12 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 66.66% + Functions + 4/6 +
+ + +
+ 83.33% + Lines + 10/12 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72  +  +  +  +  +  +  +  +  +  +  +10x +10x +10x +10x +  +  +2x +  +  +  +1x +1x +1x +  +  +10x +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { useNavigate } from 'react-router-dom'
+import { useFormContext } from 'react-hook-form'
+import { WizardStepShell } from '../components/wizard/WizardStepShell'
+import { SummarySection } from '../components/wizard/SummarySection'
+import { useRequireStepData } from '../hooks/useRequireStepData'
+import { defaultFormValues, type FormValues } from '../schemas/formSchema'
+import { getOptionLabel, TEMPO_EMPRESA_OPTIONS, UF_OPTIONS } from '../constants/options'
+import { generateSummaryPdf } from '../lib/pdf'
+import { clearStoredFormValues } from '../lib/storage'
+ 
+export function SummaryPage() {
+  useRequireStepData('profissao', '/etapa-3')
+  const { getValues, reset } = useFormContext<FormValues>()
+  const navigate = useNavigate()
+  const values = getValues()
+ 
+  function handleExport() {
+    generateSummaryPdf(values)
+  }
+ 
+  function handleNewRegistration() {
+    clearStoredFormValues()
+    reset(defaultFormValues)
+    navigate('/etapa-1')
+  }
+ 
+  return (
+    <WizardStepShell title="Resumo do Cadastro" description="Confira seus dados antes de exportar o PDF.">
+      <SummarySection
+        title="Dados Pessoais"
+        onEdit={() => navigate('/etapa-1')}
+        items={[
+          { label: 'Nome completo', value: values.fullName },
+          { label: 'Data de nascimento', value: values.birthDate },
+          { label: 'CPF', value: values.cpf },
+          { label: 'Telefone', value: values.phone },
+          { label: 'E-mail', value: values.email },
+        ]}
+      />
+      <SummarySection
+        title="Informações Residenciais"
+        onEdit={() => navigate('/etapa-2')}
+        items={[
+          { label: 'CEP', value: values.cep },
+          { label: 'Endereço', value: values.endereco },
+          { label: 'Bairro', value: values.bairro },
+          { label: 'Cidade', value: values.cidade },
+          { label: 'Estado', value: getOptionLabel(UF_OPTIONS, values.estado) },
+        ]}
+      />
+      <SummarySection
+        title="Informações Profissionais"
+        onEdit={() => navigate('/etapa-3')}
+        items={[
+          { label: 'Empresa', value: values.empresa },
+          { label: 'Profissão', value: values.profissao },
+          { label: 'Salário', value: values.salario },
+          { label: 'Tempo de empresa', value: getOptionLabel(TEMPO_EMPRESA_OPTIONS, values.tempoDeEmpresa) },
+        ]}
+      />
+      <div className="wizard-actions">
+        <button type="button" className="btn btn-secondary" onClick={handleNewRegistration}>
+          Novo cadastro
+        </button>
+        <button type="button" className="btn btn-primary" onClick={handleExport}>
+          Exportar PDF
+        </button>
+      </div>
+    </WizardStepShell>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/pages/index.html b/frontend/coverage/lcov-report/src/pages/index.html new file mode 100644 index 0000000..944d25e --- /dev/null +++ b/frontend/coverage/lcov-report/src/pages/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for src/pages + + + + + + + + + +
+
+

All files src/pages

+
+ +
+ 88.88% + Statements + 56/63 +
+ + +
+ 62.5% + Branches + 10/16 +
+ + +
+ 82.6% + Functions + 19/23 +
+ + +
+ 94.64% + Lines + 53/56 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
Step1PersonalData.tsx +
+
100%3/3100%0/0100%2/2100%3/3
Step2Address.tsx +
+
96.42%27/2875%6/8100%6/6100%26/26
Step3Professional.tsx +
+
80%16/2050%4/877.77%7/993.33%14/15
SummaryPage.tsx +
+
83.33%10/12100%0/066.66%4/683.33%10/12
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/schemas/formSchema.ts.html b/frontend/coverage/lcov-report/src/schemas/formSchema.ts.html new file mode 100644 index 0000000..50f44d2 --- /dev/null +++ b/frontend/coverage/lcov-report/src/schemas/formSchema.ts.html @@ -0,0 +1,328 @@ + + + + + + Code coverage report for src/schemas/formSchema.ts + + + + + + + + + +
+
+

All files / src/schemas formSchema.ts

+
+ +
+ 100% + Statements + 15/15 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 14/14 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82  +  +  +  +  +13x +13x +351x +  +13x +  +  +  +  +29x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +13x +  +  +  +  +  +  +  +26x +  +  +  +  +13x +  +  +  +  +  +26x +  +  +  +  +  +13x +  +  +  +13x +13x +13x +  +13x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { z } from 'zod'
+import { isValidCPF } from '../lib/cpf'
+import { isValidBirthDate, parseCurrencyToNumber } from '../lib/masks'
+import { UF_OPTIONS } from '../constants/options'
+ 
+const PHONE_REGEX = /^\(\d{2}\) \d{4,5}-\d{4}$/
+const CEP_REGEX = /^\d{5}-\d{3}$/
+const UF_CODES = UF_OPTIONS.map((option) => option.value)
+ 
+export const personalDataSchema = z.object({
+  fullName: z
+    .string()
+    .trim()
+    .min(1, 'Informe o nome completo')
+    .refine((value) => value.trim().split(/\s+/).length >= 2, {
+      message: 'Informe nome e sobrenome',
+    }),
+  birthDate: z
+    .string()
+    .min(1, 'Informe a data de nascimento')
+    .refine(isValidBirthDate, { message: 'Data de nascimento inválida' }),
+  cpf: z
+    .string()
+    .min(1, 'Informe o CPF')
+    .refine(isValidCPF, { message: 'CPF inválido' }),
+  phone: z
+    .string()
+    .min(1, 'Informe o telefone')
+    .regex(PHONE_REGEX, 'Telefone inválido'),
+  email: z.string().min(1, 'Informe o e-mail').email('E-mail inválido'),
+})
+ 
+export const addressSchema = z.object({
+  cep: z.string().min(1, 'Informe o CEP').regex(CEP_REGEX, 'CEP inválido'),
+  endereco: z.string().min(1, 'Informe o endereço'),
+  bairro: z.string().min(1, 'Informe o bairro'),
+  cidade: z.string().min(1, 'Informe a cidade'),
+  estado: z
+    .string()
+    .min(1, 'Informe o estado')
+    .refine((value) => UF_CODES.includes(value.toUpperCase()), {
+      message: 'Estado inválido',
+    }),
+})
+ 
+export const professionalSchema = z.object({
+  empresa: z.string().min(1, 'Informe a empresa'),
+  profissao: z.string().min(1, 'Selecione uma profissão'),
+  salario: z
+    .string()
+    .min(1, 'Informe o salário')
+    .refine((value) => parseCurrencyToNumber(value) > 0, {
+      message: 'Informe um salário válido',
+    }),
+  tempoDeEmpresa: z.string().min(1, 'Selecione o tempo de empresa'),
+})
+ 
+export const formSchema = personalDataSchema.merge(addressSchema).merge(professionalSchema)
+ 
+export type FormValues = z.infer<typeof formSchema>
+ 
+export const STEP1_FIELDS = Object.keys(personalDataSchema.shape) as (keyof FormValues)[]
+export const STEP2_FIELDS = Object.keys(addressSchema.shape) as (keyof FormValues)[]
+export const STEP3_FIELDS = Object.keys(professionalSchema.shape) as (keyof FormValues)[]
+ 
+export const defaultFormValues: FormValues = {
+  fullName: '',
+  birthDate: '',
+  cpf: '',
+  phone: '',
+  email: '',
+  cep: '',
+  endereco: '',
+  bairro: '',
+  cidade: '',
+  estado: '',
+  empresa: '',
+  profissao: '',
+  salario: '',
+  tempoDeEmpresa: '',
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/schemas/index.html b/frontend/coverage/lcov-report/src/schemas/index.html new file mode 100644 index 0000000..5f0223d --- /dev/null +++ b/frontend/coverage/lcov-report/src/schemas/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for src/schemas + + + + + + + + + +
+
+

All files src/schemas

+
+ +
+ 100% + Statements + 15/15 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 14/14 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
formSchema.ts +
+
100%15/15100%0/0100%4/4100%14/14
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/lcov.info b/frontend/coverage/lcov.info new file mode 100644 index 0000000..7170785 --- /dev/null +++ b/frontend/coverage/lcov.info @@ -0,0 +1,707 @@ +TN: +SF:src/App.tsx +FN:9,App +FNF:1 +FNH:1 +FNDA:2,App +DA:10,2 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/components/form/CurrencyField.tsx +FN:7,formatBRLFromDigits +FN:21,CurrencyField +FN:34,(anonymous_2) +FN:42,(anonymous_3) +FNF:4 +FNH:4 +FNDA:30,formatBRLFromDigits +FNDA:19,CurrencyField +FNDA:50,(anonymous_2) +FNDA:31,(anonymous_3) +DA:5,3 +DA:8,30 +DA:10,30 +DA:25,19 +DA:26,19 +DA:28,19 +DA:35,50 +DA:45,31 +DA:46,31 +LF:9 +LH:9 +BRDA:8,0,0,30 +BRDA:8,0,1,0 +BRDA:29,1,0,0 +BRDA:29,1,1,19 +BRDA:40,2,0,0 +BRDA:40,2,1,50 +BRDA:41,3,0,50 +BRDA:41,3,1,0 +BRDA:46,4,0,30 +BRDA:46,4,1,1 +BRDA:52,5,0,19 +BRDA:52,5,1,0 +BRF:12 +BRH:7 +end_of_record +TN: +SF:src/components/form/MaskedTextField.tsx +FN:15,MaskedTextField +FN:36,(anonymous_1) +FN:47,(anonymous_2) +FNF:3 +FNH:3 +FNDA:88,MaskedTextField +FNDA:170,(anonymous_1) +FNDA:114,(anonymous_2) +DA:27,88 +DA:28,88 +DA:30,88 +DA:37,170 +DA:47,114 +LF:5 +LH:5 +BRDA:31,0,0,51 +BRDA:31,0,1,37 +BRDA:41,1,0,170 +BRDA:41,1,1,0 +BRDA:46,2,0,3 +BRDA:46,2,1,167 +BRDA:52,3,0,88 +BRDA:52,3,1,3 +BRDA:57,4,0,88 +BRDA:57,4,1,85 +BRDA:57,4,2,5 +BRF:11 +BRH:10 +end_of_record +TN: +SF:src/components/form/SelectField.tsx +FN:14,SelectField +FN:35,(anonymous_1) +FNF:2 +FNH:2 +FNDA:83,SelectField +FNDA:1456,(anonymous_1) +DA:18,83 +DA:19,83 +DA:21,83 +DA:36,1456 +LF:4 +LH:4 +BRDA:22,0,0,13 +BRDA:22,0,1,70 +BRDA:28,1,0,0 +BRDA:28,1,1,83 +BRDA:33,2,0,83 +BRDA:33,2,1,17 +BRDA:41,3,0,83 +BRDA:41,3,1,0 +BRF:8 +BRH:6 +end_of_record +TN: +SF:src/components/form/TextField.tsx +FN:14,TextField +FNF:1 +FNH:1 +FNDA:200,TextField +DA:26,200 +DA:27,200 +DA:29,200 +LF:3 +LH:3 +BRDA:18,0,0,200 +BRDA:30,1,0,75 +BRDA:30,1,1,125 +BRDA:39,2,0,3 +BRDA:39,2,1,197 +BRDA:42,3,0,200 +BRDA:42,3,1,3 +BRF:7 +BRH:7 +end_of_record +TN: +SF:src/components/layout/Layout.tsx +FN:5,Layout +FNF:1 +FNH:1 +FNDA:3,Layout +DA:6,3 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/components/layout/Logo.tsx +FN:1,Logo +FNF:1 +FNH:1 +FNDA:4,Logo +DA:2,4 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/components/wizard/StepIndicator.tsx +FN:10,StepIndicator +FN:12,(anonymous_1) +FN:16,(anonymous_2) +FNF:3 +FNH:3 +FNDA:10,StepIndicator +FNDA:26,(anonymous_1) +FNDA:40,(anonymous_2) +DA:3,3 +DA:11,10 +DA:12,26 +DA:14,10 +DA:17,40 +DA:18,40 +LF:6 +LH:6 +BRDA:17,0,0,10 +BRDA:17,0,1,30 +BRDA:17,1,0,8 +BRDA:17,1,1,22 +BRDA:20,2,0,10 +BRDA:20,2,1,30 +BRF:6 +BRH:6 +end_of_record +TN: +SF:src/components/wizard/SummarySection.tsx +FN:12,SummarySection +FN:22,(anonymous_1) +FNF:2 +FNH:2 +FNDA:32,SummarySection +FNDA:142,(anonymous_1) +DA:13,32 +DA:23,142 +LF:2 +LH:2 +BRDA:25,0,0,142 +BRDA:25,0,1,15 +BRF:2 +BRH:2 +end_of_record +TN: +SF:src/components/wizard/WizardActions.tsx +FN:8,WizardActions +FNF:1 +FNH:1 +FNDA:77,WizardActions +DA:9,77 +LF:1 +LH:1 +BRDA:8,0,0,77 +BRDA:8,1,0,77 +BRDA:11,2,0,77 +BRDA:11,2,1,65 +BRF:4 +BRH:4 +end_of_record +TN: +SF:src/components/wizard/WizardStepShell.tsx +FN:9,WizardStepShell +FNF:1 +FNH:1 +FNDA:87,WizardStepShell +DA:10,87 +LF:1 +LH:1 +BRDA:13,0,0,87 +BRDA:13,0,1,86 +BRF:2 +BRH:2 +end_of_record +TN: +SF:src/constants/masks.ts +FNF:0 +FNH:0 +DA:1,4 +DA:3,4 +DA:5,4 +DA:7,4 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/constants/options.ts +FN:44,getOptionLabel +FN:45,(anonymous_1) +FNF:2 +FNH:2 +FNDA:27,getOptionLabel +FNDA:407,(anonymous_1) +DA:6,15 +DA:36,15 +DA:45,407 +LF:3 +LH:3 +BRDA:45,0,0,27 +BRDA:45,0,1,3 +BRF:2 +BRH:2 +end_of_record +TN: +SF:src/context/WizardFormProvider.tsx +FN:7,WizardFormProvider +FN:18,(anonymous_1) +FN:19,(anonymous_2) +FN:22,(anonymous_3) +FNF:4 +FNH:4 +FNDA:17,WizardFormProvider +FNDA:5,(anonymous_1) +FNDA:98,(anonymous_2) +FNDA:5,(anonymous_3) +DA:8,17 +DA:18,17 +DA:19,5 +DA:20,98 +DA:22,5 +DA:25,17 +LF:6 +LH:6 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/hooks/useRequireStepData.ts +FN:10,useRequireStepData +FN:14,(anonymous_1) +FNF:2 +FNH:2 +FNDA:110,useRequireStepData +FNDA:16,(anonymous_1) +DA:11,110 +DA:12,110 +DA:14,110 +DA:15,16 +DA:16,1 +LF:5 +LH:5 +BRDA:15,0,0,1 +BRDA:15,0,1,15 +BRF:2 +BRH:2 +end_of_record +TN: +SF:src/hooks/useStepNavigation.ts +FN:5,useStepNavigation +FN:9,goNext +FN:16,goBack +FNF:3 +FNH:3 +FNDA:115,useStepNavigation +FNDA:9,goNext +FNDA:2,goBack +DA:6,115 +DA:7,115 +DA:10,9 +DA:11,9 +DA:12,7 +DA:17,2 +DA:20,115 +LF:7 +LH:7 +BRDA:11,0,0,7 +BRDA:11,0,1,2 +BRF:2 +BRH:2 +end_of_record +TN: +SF:src/lib/api.ts +FN:22,fetchCepFromMock +FN:31,fetchCepFromViaCep +FN:50,fetchAddressByCep +FN:61,fetchProfessions +FNF:4 +FNH:4 +FNDA:4,fetchCepFromMock +FNDA:3,fetchCepFromViaCep +FNDA:4,fetchAddressByCep +FNDA:1,fetchProfessions +DA:3,1 +DA:23,4 +DA:24,4 +DA:25,1 +DA:27,3 +DA:32,3 +DA:33,3 +DA:34,2 +DA:35,1 +DA:42,1 +DA:51,4 +DA:52,4 +DA:53,3 +DA:62,1 +DA:63,1 +LF:15 +LH:15 +BRDA:4,0,0,1 +BRDA:4,0,1,0 +BRDA:34,1,0,1 +BRDA:34,1,1,1 +BRDA:52,2,0,1 +BRDA:52,2,1,3 +BRF:6 +BRH:5 +end_of_record +TN: +SF:src/lib/cpf.ts +FN:1,onlyDigits +FN:5,calcCheckDigit +FN:14,isValidCPF +FNF:3 +FNH:3 +FNDA:39,onlyDigits +FNDA:34,calcCheckDigit +FNDA:37,isValidCPF +DA:2,39 +DA:6,34 +DA:7,34 +DA:8,323 +DA:10,34 +DA:11,34 +DA:15,37 +DA:17,37 +DA:18,20 +DA:21,17 +DA:22,17 +DA:23,17 +DA:25,17 +LF:13 +LH:13 +BRDA:11,0,0,0 +BRDA:11,0,1,34 +BRDA:17,1,0,20 +BRDA:17,1,1,17 +BRDA:17,2,0,37 +BRDA:17,2,1,20 +BRDA:25,3,0,17 +BRDA:25,3,1,17 +BRF:8 +BRH:7 +end_of_record +TN: +SF:src/lib/masks.ts +FN:1,onlyDigits +FN:5,isValidBirthDate +FN:27,parseCurrencyToNumber +FNF:3 +FNH:3 +FNDA:69,onlyDigits +FNDA:37,isValidBirthDate +FNDA:31,parseCurrencyToNumber +DA:2,69 +DA:6,37 +DA:7,37 +DA:9,21 +DA:10,21 +DA:11,21 +DA:12,21 +DA:15,21 +DA:19,37 +DA:21,17 +DA:22,17 +DA:24,17 +DA:28,31 +DA:29,31 +DA:30,31 +LF:15 +LH:15 +BRDA:7,0,0,16 +BRDA:7,0,1,21 +BRDA:15,1,0,21 +BRDA:15,1,1,20 +BRDA:15,1,2,17 +BRDA:19,2,0,4 +BRDA:19,2,1,33 +BRDA:24,3,0,17 +BRDA:24,3,1,16 +BRDA:30,4,0,22 +BRDA:30,4,1,9 +BRF:11 +BRH:11 +end_of_record +TN: +SF:src/lib/pdf.ts +FN:15,buildSections +FN:49,generateSummaryPdf +FNF:2 +FNH:2 +FNDA:2,buildSections +FNDA:2,generateSummaryPdf +DA:5,1 +DA:6,1 +DA:7,1 +DA:8,1 +DA:16,2 +DA:50,2 +DA:51,2 +DA:53,2 +DA:54,2 +DA:55,2 +DA:56,2 +DA:57,2 +DA:58,2 +DA:59,2 +DA:60,2 +DA:61,2 +DA:63,2 +DA:65,2 +DA:66,6 +DA:67,6 +DA:68,6 +DA:69,6 +DA:70,6 +DA:71,6 +DA:72,6 +DA:73,6 +DA:75,6 +DA:76,6 +DA:77,28 +DA:78,28 +DA:79,28 +DA:80,28 +DA:81,28 +DA:82,28 +DA:85,6 +DA:88,2 +DA:89,2 +DA:90,2 +DA:91,2 +DA:93,2 +DA:94,2 +LF:41 +LH:41 +BRDA:81,0,0,28 +BRDA:81,0,1,1 +BRDA:93,1,0,2 +BRDA:93,1,1,1 +BRF:4 +BRH:4 +end_of_record +TN: +SF:src/lib/storage.ts +FN:3,loadStoredFormValues +FN:13,persistFormValues +FN:21,clearStoredFormValues +FNF:3 +FNH:3 +FNDA:21,loadStoredFormValues +FNDA:102,persistFormValues +FNDA:1,clearStoredFormValues +DA:1,3 +DA:4,21 +DA:5,21 +DA:6,21 +DA:7,11 +DA:9,1 +DA:14,102 +DA:15,102 +DA:22,1 +LF:9 +LH:9 +BRDA:6,0,0,10 +BRDA:6,0,1,11 +BRF:2 +BRH:2 +end_of_record +TN: +SF:src/pages/Step1PersonalData.tsx +FN:9,Step1PersonalData +FN:33,(anonymous_1) +FNF:2 +FNH:2 +FNDA:11,Step1PersonalData +FNDA:3,(anonymous_1) +DA:10,11 +DA:12,11 +DA:33,3 +LF:3 +LH:3 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/pages/Step2Address.tsx +FN:23,Step2Address +FN:30,(anonymous_1) +FN:40,(anonymous_2) +FN:53,(anonymous_3) +FN:78,(anonymous_4) +FN:78,(anonymous_5) +FNF:6 +FNH:6 +FNDA:83,Step2Address +FNDA:37,(anonymous_1) +FNDA:4,(anonymous_2) +FNDA:4,(anonymous_3) +FNDA:1,(anonymous_4) +FNDA:2,(anonymous_5) +DA:18,2 +DA:24,83 +DA:25,83 +DA:26,83 +DA:27,83 +DA:28,83 +DA:30,83 +DA:31,37 +DA:32,37 +DA:33,33 +DA:34,33 +DA:37,4 +DA:38,4 +DA:40,4 +DA:41,4 +DA:42,4 +DA:43,3 +DA:44,3 +DA:45,3 +DA:46,3 +DA:47,3 +DA:49,1 +DA:53,4 +DA:54,4 +DA:58,83 +DA:78,2 +LF:26 +LH:26 +BRDA:31,0,0,37 +BRDA:31,0,1,0 +BRDA:32,1,0,33 +BRDA:32,1,1,4 +BRDA:41,2,0,0 +BRDA:41,2,1,4 +BRDA:42,3,0,3 +BRDA:42,3,1,1 +BRF:8 +BRH:6 +end_of_record +TN: +SF:src/pages/Step3Professional.tsx +FN:13,Step3Professional +FN:19,(anonymous_1) +FN:23,(anonymous_2) +FN:25,(anonymous_3) +FN:27,(anonymous_4) +FN:30,(anonymous_5) +FN:34,(anonymous_6) +FN:53,(anonymous_7) +FN:53,(anonymous_8) +FNF:9 +FNH:7 +FNDA:13,Step3Professional +FNDA:4,(anonymous_1) +FNDA:4,(anonymous_2) +FNDA:4,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:4,(anonymous_5) +FNDA:4,(anonymous_6) +FNDA:0,(anonymous_7) +FNDA:2,(anonymous_8) +DA:14,13 +DA:15,13 +DA:16,13 +DA:17,13 +DA:19,13 +DA:20,4 +DA:22,4 +DA:24,4 +DA:25,4 +DA:28,0 +DA:31,4 +DA:34,4 +DA:35,4 +DA:39,13 +DA:53,2 +LF:15 +LH:14 +BRDA:24,0,0,0 +BRDA:24,0,1,4 +BRDA:28,1,0,0 +BRDA:28,1,1,0 +BRDA:31,2,0,4 +BRDA:31,2,1,0 +BRDA:47,3,0,7 +BRDA:47,3,1,6 +BRF:8 +BRH:4 +end_of_record +TN: +SF:src/pages/SummaryPage.tsx +FN:11,SummaryPage +FN:17,handleExport +FN:21,handleNewRegistration +FN:31,(anonymous_3) +FN:42,(anonymous_4) +FN:53,(anonymous_5) +FNF:6 +FNH:4 +FNDA:10,SummaryPage +FNDA:2,handleExport +FNDA:1,handleNewRegistration +FNDA:1,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:0,(anonymous_5) +DA:12,10 +DA:13,10 +DA:14,10 +DA:15,10 +DA:18,2 +DA:22,1 +DA:23,1 +DA:24,1 +DA:27,10 +DA:31,1 +DA:42,0 +DA:53,0 +LF:12 +LH:10 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/schemas/formSchema.ts +FN:8,(anonymous_0) +FN:15,(anonymous_1) +FN:41,(anonymous_2) +FN:52,(anonymous_3) +FNF:4 +FNH:4 +FNDA:351,(anonymous_0) +FNDA:29,(anonymous_1) +FNDA:26,(anonymous_2) +FNDA:26,(anonymous_3) +DA:6,13 +DA:7,13 +DA:8,351 +DA:10,13 +DA:15,29 +DA:33,13 +DA:41,26 +DA:46,13 +DA:52,26 +DA:58,13 +DA:62,13 +DA:63,13 +DA:64,13 +DA:66,13 +LF:14 +LH:14 +BRF:0 +BRH:0 +end_of_record diff --git a/frontend/coverage/prettify.css b/frontend/coverage/prettify.css new file mode 100644 index 0000000..b317a7c --- /dev/null +++ b/frontend/coverage/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/frontend/coverage/prettify.js b/frontend/coverage/prettify.js new file mode 100644 index 0000000..b322523 --- /dev/null +++ b/frontend/coverage/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/frontend/coverage/sort-arrow-sprite.png b/frontend/coverage/sort-arrow-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed68316eb3f65dec9063332d2f69bf3093bbfab GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc literal 0 HcmV?d00001 diff --git a/frontend/coverage/sorter.js b/frontend/coverage/sorter.js new file mode 100644 index 0000000..4ed70ae --- /dev/null +++ b/frontend/coverage/sorter.js @@ -0,0 +1,210 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + + // Try to create a RegExp from the searchValue. If it fails (invalid regex), + // it will be treated as a plain text search + let searchRegex; + try { + searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive + } catch (error) { + searchRegex = null; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let isMatch = false; + + if (searchRegex) { + // If a valid regex was created, use it for matching + isMatch = searchRegex.test(row.textContent); + } else { + // Otherwise, fall back to the original plain text search + isMatch = row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()); + } + + row.style.display = isMatch ? '' : 'none'; + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/frontend/coverage/src/App.tsx.html b/frontend/coverage/src/App.tsx.html new file mode 100644 index 0000000..b5e71d9 --- /dev/null +++ b/frontend/coverage/src/App.tsx.html @@ -0,0 +1,169 @@ + + + + + + Code coverage report for src/App.tsx + + + + + + + + + +
+
+

All files / src App.tsx

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29  +  +  +  +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'
+import { Layout } from './components/layout/Layout'
+import { WizardFormProvider } from './context/WizardFormProvider'
+import { Step1PersonalData } from './pages/Step1PersonalData'
+import { Step2Address } from './pages/Step2Address'
+import { Step3Professional } from './pages/Step3Professional'
+import { SummaryPage } from './pages/SummaryPage'
+ 
+function App() {
+  return (
+    <BrowserRouter>
+      <WizardFormProvider>
+        <Layout>
+          <Routes>
+            <Route path="/" element={<Navigate to="/etapa-1" replace />} />
+            <Route path="/etapa-1" element={<Step1PersonalData />} />
+            <Route path="/etapa-2" element={<Step2Address />} />
+            <Route path="/etapa-3" element={<Step3Professional />} />
+            <Route path="/resumo" element={<SummaryPage />} />
+            <Route path="*" element={<Navigate to="/etapa-1" replace />} />
+          </Routes>
+        </Layout>
+      </WizardFormProvider>
+    </BrowserRouter>
+  )
+}
+ 
+export default App
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/form/CurrencyField.tsx.html b/frontend/coverage/src/components/form/CurrencyField.tsx.html new file mode 100644 index 0000000..7008ed1 --- /dev/null +++ b/frontend/coverage/src/components/form/CurrencyField.tsx.html @@ -0,0 +1,262 @@ + + + + + + Code coverage report for src/components/form/CurrencyField.tsx + + + + + + + + + +
+
+

All files / src/components/form CurrencyField.tsx

+
+ +
+ 100% + Statements + 9/9 +
+ + +
+ 58.33% + Branches + 7/12 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 9/9 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60  +  +  +  +3x +  +  +30x +  +30x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +19x +19x +  +19x +  +  +  +  +  +  +50x +  +  +  +  +  +  +  +  +  +31x +31x +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { Controller, useFormContext } from 'react-hook-form'
+import type { FormValues } from '../../schemas/formSchema'
+import { onlyDigits } from '../../lib/masks'
+ 
+const NON_BREAKING_SPACE = ' '
+ 
+function formatBRLFromDigits(digits: string): string {
+  const numeric = Number(digits || '0') / 100
+  // toLocaleString inserts a non-breaking space after "R$"; normalize to a regular space.
+  return numeric
+    .toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })
+    .replaceAll(NON_BREAKING_SPACE, ' ')
+}
+ 
+interface CurrencyFieldProps {
+  name: keyof FormValues
+  label: string
+  fullWidth?: boolean
+}
+ 
+export function CurrencyField({ name, label, fullWidth }: Readonly<CurrencyFieldProps>) {
+  const {
+    control,
+    formState: { errors },
+  } = useFormContext<FormValues>()
+  const error = errors[name]?.message as string | undefined
+ 
+  return (
+    <div className={`field${fullWidth ? ' field-full' : ''}`}>
+      <label htmlFor={name}>{label}</label>
+      <Controller
+        name={name}
+        control={control}
+        render={({ field }) => (
+          <input
+            id={name}
+            inputMode="decimal"
+            placeholder="R$ 0,00"
+            aria-invalid={!!error}
+            aria-describedby={error ? `${name}-error` : undefined}
+            value={field.value ?? ''}
+            onChange={(event) => {
+              // Strip leading zeros so backspacing down to nothing actually clears the
+              // field instead of getting stuck re-formatting to "R$ 0,00" forever.
+              const digits = onlyDigits(event.target.value).replace(/^0+/, '').slice(0, 12)
+              field.onChange(digits ? formatBRLFromDigits(digits) : '')
+            }}
+            onBlur={field.onBlur}
+          />
+        )}
+      />
+      {error && (
+        <span className="field-error" id={`${name}-error`} role="alert">
+          {error}
+        </span>
+      )}
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/form/MaskedTextField.tsx.html b/frontend/coverage/src/components/form/MaskedTextField.tsx.html new file mode 100644 index 0000000..30afe2b --- /dev/null +++ b/frontend/coverage/src/components/form/MaskedTextField.tsx.html @@ -0,0 +1,265 @@ + + + + + + Code coverage report for src/components/form/MaskedTextField.tsx + + + + + + + + + +
+
+

All files / src/components/form MaskedTextField.tsx

+
+ +
+ 100% + Statements + 5/5 +
+ + +
+ 90.9% + Branches + 10/11 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +88x +88x +  +88x +  +  +  +  +  +  +170x +  +  +  +  +  +  +  +  +  +114x +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { Controller, useFormContext } from 'react-hook-form'
+import { IMaskInput } from 'react-imask'
+import type { FormValues } from '../../schemas/formSchema'
+ 
+interface MaskedTextFieldProps {
+  name: keyof FormValues
+  label: string
+  maskOptions: Record<string, unknown>
+  placeholder?: string
+  inputMode?: 'text' | 'numeric' | 'decimal'
+  hint?: string
+  fullWidth?: boolean
+}
+ 
+export function MaskedTextField({
+  name,
+  label,
+  maskOptions,
+  placeholder,
+  inputMode,
+  hint,
+  fullWidth,
+}: Readonly<MaskedTextFieldProps>) {
+  const {
+    control,
+    formState: { errors },
+  } = useFormContext<FormValues>()
+  const error = errors[name]?.message as string | undefined
+ 
+  return (
+    <div className={`field${fullWidth ? ' field-full' : ''}`}>
+      <label htmlFor={name}>{label}</label>
+      <Controller
+        name={name}
+        control={control}
+        render={({ field }) => (
+          <IMaskInput
+            {...maskOptions}
+            id={name}
+            name={field.name}
+            value={field.value ?? ''}
+            unmask={false}
+            inputMode={inputMode}
+            placeholder={placeholder}
+            aria-invalid={!!error}
+            aria-describedby={error ? `${name}-error` : undefined}
+            onAccept={(value: string) => field.onChange(value)}
+            onBlur={field.onBlur}
+          />
+        )}
+      />
+      {error && (
+        <span className="field-error" id={`${name}-error`} role="alert">
+          {error}
+        </span>
+      )}
+      {!error && hint && <span className="field-hint">{hint}</span>}
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/form/SelectField.tsx.html b/frontend/coverage/src/components/form/SelectField.tsx.html new file mode 100644 index 0000000..15efc16 --- /dev/null +++ b/frontend/coverage/src/components/form/SelectField.tsx.html @@ -0,0 +1,229 @@ + + + + + + Code coverage report for src/components/form/SelectField.tsx + + + + + + + + + +
+
+

All files / src/components/form SelectField.tsx

+
+ +
+ 100% + Statements + 4/4 +
+ + +
+ 75% + Branches + 6/8 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 4/4 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +83x +83x +  +83x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1456x +  +  +  +  +  +  +  +  +  +  +  +  + 
import { useFormContext } from 'react-hook-form'
+import type { FormValues } from '../../schemas/formSchema'
+import type { SelectOption } from '../../constants/options'
+ 
+interface SelectFieldProps {
+  name: keyof FormValues
+  label: string
+  options: SelectOption[]
+  placeholder?: string
+  disabled?: boolean
+  fullWidth?: boolean
+}
+ 
+export function SelectField({ name, label, options, placeholder, disabled, fullWidth }: Readonly<SelectFieldProps>) {
+  const {
+    register,
+    formState: { errors },
+  } = useFormContext<FormValues>()
+  const error = errors[name]?.message as string | undefined
+ 
+  return (
+    <div className={`field${fullWidth ? ' field-full' : ''}`}>
+      <label htmlFor={name}>{label}</label>
+      <select
+        id={name}
+        disabled={disabled}
+        aria-invalid={!!error}
+        aria-describedby={error ? `${name}-error` : undefined}
+        defaultValue=""
+        {...register(name)}
+      >
+        <option value="" disabled>
+          {placeholder ?? 'Selecione...'}
+        </option>
+        {options.map((option) => (
+          <option key={option.value} value={option.value}>
+            {option.label}
+          </option>
+        ))}
+      </select>
+      {error && (
+        <span className="field-error" id={`${name}-error`} role="alert">
+          {error}
+        </span>
+      )}
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/form/TextField.tsx.html b/frontend/coverage/src/components/form/TextField.tsx.html new file mode 100644 index 0000000..d7cbc28 --- /dev/null +++ b/frontend/coverage/src/components/form/TextField.tsx.html @@ -0,0 +1,232 @@ + + + + + + Code coverage report for src/components/form/TextField.tsx + + + + + + + + + +
+
+

All files / src/components/form TextField.tsx

+
+ +
+ 100% + Statements + 3/3 +
+ + +
+ 100% + Branches + 7/7 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +200x +200x +  +200x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { useFormContext } from 'react-hook-form'
+import type { FormValues } from '../../schemas/formSchema'
+ 
+interface TextFieldProps {
+  name: keyof FormValues
+  label: string
+  placeholder?: string
+  type?: string
+  autoComplete?: string
+  fullWidth?: boolean
+  readOnly?: boolean
+}
+ 
+export function TextField({
+  name,
+  label,
+  placeholder,
+  type = 'text',
+  autoComplete,
+  fullWidth,
+  readOnly,
+}: Readonly<TextFieldProps>) {
+  const {
+    register,
+    formState: { errors },
+  } = useFormContext<FormValues>()
+  const error = errors[name]?.message as string | undefined
+ 
+  return (
+    <div className={`field${fullWidth ? ' field-full' : ''}`}>
+      <label htmlFor={name}>{label}</label>
+      <input
+        id={name}
+        type={type}
+        placeholder={placeholder}
+        autoComplete={autoComplete}
+        readOnly={readOnly}
+        aria-invalid={!!error}
+        aria-describedby={error ? `${name}-error` : undefined}
+        {...register(name)}
+      />
+      {error && (
+        <span className="field-error" id={`${name}-error`} role="alert">
+          {error}
+        </span>
+      )}
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/form/index.html b/frontend/coverage/src/components/form/index.html new file mode 100644 index 0000000..15bbd60 --- /dev/null +++ b/frontend/coverage/src/components/form/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for src/components/form + + + + + + + + + +
+
+

All files src/components/form

+
+ +
+ 100% + Statements + 21/21 +
+ + +
+ 78.94% + Branches + 30/38 +
+ + +
+ 100% + Functions + 10/10 +
+ + +
+ 100% + Lines + 21/21 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
CurrencyField.tsx +
+
100%9/958.33%7/12100%4/4100%9/9
MaskedTextField.tsx +
+
100%5/590.9%10/11100%3/3100%5/5
SelectField.tsx +
+
100%4/475%6/8100%2/2100%4/4
TextField.tsx +
+
100%3/3100%7/7100%1/1100%3/3
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/layout/Layout.tsx.html b/frontend/coverage/src/components/layout/Layout.tsx.html new file mode 100644 index 0000000..c9212bb --- /dev/null +++ b/frontend/coverage/src/components/layout/Layout.tsx.html @@ -0,0 +1,145 @@ + + + + + + Code coverage report for src/components/layout/Layout.tsx + + + + + + + + + +
+
+

All files / src/components/layout Layout.tsx

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21  +  +  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import type { ReactNode } from 'react'
+import { Logo } from './Logo'
+import { StepIndicator } from '../wizard/StepIndicator'
+ 
+export function Layout({ children }: { children: ReactNode }) {
+  return (
+    <div className="app-shell">
+      <header className="app-header">
+        <Logo />
+      </header>
+      <StepIndicator />
+      <main className="app-main">
+        <div className="card">{children}</div>
+      </main>
+      <footer className="app-footer">
+        <p>&copy; {new Date().getFullYear()} Verity. Todos os direitos reservados.</p>
+      </footer>
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/layout/Logo.tsx.html b/frontend/coverage/src/components/layout/Logo.tsx.html new file mode 100644 index 0000000..491bd28 --- /dev/null +++ b/frontend/coverage/src/components/layout/Logo.tsx.html @@ -0,0 +1,106 @@ + + + + + + Code coverage report for src/components/layout/Logo.tsx + + + + + + + + + +
+
+

All files / src/components/layout Logo.tsx

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8  +4x +  +  +  +  +  + 
export function Logo() {
+  return (
+    <div className="logo-mark">
+      <img src="/logo.avif" alt="Verity" className="logo-image" />
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/layout/index.html b/frontend/coverage/src/components/layout/index.html new file mode 100644 index 0000000..2552f6b --- /dev/null +++ b/frontend/coverage/src/components/layout/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for src/components/layout + + + + + + + + + +
+
+

All files src/components/layout

+
+ +
+ 100% + Statements + 2/2 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 2/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
Layout.tsx +
+
100%1/1100%0/0100%1/1100%1/1
Logo.tsx +
+
100%1/1100%0/0100%1/1100%1/1
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/wizard/StepIndicator.tsx.html b/frontend/coverage/src/components/wizard/StepIndicator.tsx.html new file mode 100644 index 0000000..abf7ea1 --- /dev/null +++ b/frontend/coverage/src/components/wizard/StepIndicator.tsx.html @@ -0,0 +1,166 @@ + + + + + + Code coverage report for src/components/wizard/StepIndicator.tsx + + + + + + + + + +
+
+

All files / src/components/wizard StepIndicator.tsx

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 100% + Branches + 6/6 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 6/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28  +  +3x +  +  +  +  +  +  +  +10x +26x +  +10x +  +  +40x +40x +  +  +  +  +  +  +  +  +  + 
import { useLocation } from 'react-router-dom'
+ 
+const STEPS = [
+  { path: '/etapa-1', label: 'Dados Pessoais' },
+  { path: '/etapa-2', label: 'Endereço' },
+  { path: '/etapa-3', label: 'Profissional' },
+  { path: '/resumo', label: 'Resumo' },
+]
+ 
+export function StepIndicator() {
+  const { pathname } = useLocation()
+  const currentIndex = STEPS.findIndex((step) => step.path === pathname)
+ 
+  return (
+    <ol className="step-indicator">
+      {STEPS.map((step, index) => {
+        const status = index < currentIndex ? 'done' : index === currentIndex ? 'active' : 'pending'
+        return (
+          <li key={step.path} className={`step step-${status}`}>
+            <span className="step-index">{status === 'done' ? '✓' : index + 1}</span>
+            <span className="step-label">{step.label}</span>
+          </li>
+        )
+      })}
+    </ol>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/wizard/SummarySection.tsx.html b/frontend/coverage/src/components/wizard/SummarySection.tsx.html new file mode 100644 index 0000000..e8c2240 --- /dev/null +++ b/frontend/coverage/src/components/wizard/SummarySection.tsx.html @@ -0,0 +1,178 @@ + + + + + + Code coverage report for src/components/wizard/SummarySection.tsx + + + + + + + + + +
+
+

All files / src/components/wizard SummarySection.tsx

+
+ +
+ 100% + Statements + 2/2 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 2/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32  +  +  +  +  +  +  +  +  +  +  +  +32x +  +  +  +  +  +  +  +  +  +142x +  +  +  +  +  +  +  +  + 
interface SummaryItem {
+  label: string
+  value: string
+}
+ 
+interface SummarySectionProps {
+  title: string
+  items: SummaryItem[]
+  onEdit: () => void
+}
+ 
+export function SummarySection({ title, items, onEdit }: Readonly<SummarySectionProps>) {
+  return (
+    <section className="summary-section">
+      <div className="summary-section-header">
+        <h3>{title}</h3>
+        <button type="button" className="btn-link" onClick={onEdit}>
+          Editar
+        </button>
+      </div>
+      <dl className="summary-list">
+        {items.map((item) => (
+          <div className="summary-row" key={item.label}>
+            <dt>{item.label}</dt>
+            <dd>{item.value || '—'}</dd>
+          </div>
+        ))}
+      </dl>
+    </section>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/wizard/WizardActions.tsx.html b/frontend/coverage/src/components/wizard/WizardActions.tsx.html new file mode 100644 index 0000000..7fb6a80 --- /dev/null +++ b/frontend/coverage/src/components/wizard/WizardActions.tsx.html @@ -0,0 +1,148 @@ + + + + + + Code coverage report for src/components/wizard/WizardActions.tsx + + + + + + + + + +
+
+

All files / src/components/wizard WizardActions.tsx

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22  +  +  +  +  +  +  +  +77x +  +  +  +  +  +  +  +  +  +  +  +  + 
interface WizardActionsProps {
+  onBack?: () => void
+  onNext: () => void
+  nextLabel?: string
+  backLabel?: string
+}
+ 
+export function WizardActions({ onBack, onNext, nextLabel = 'Próximo', backLabel = 'Voltar' }: Readonly<WizardActionsProps>) {
+  return (
+    <div className="wizard-actions">
+      {onBack && (
+        <button type="button" className="btn btn-secondary" onClick={onBack}>
+          {backLabel}
+        </button>
+      )}
+      <button type="button" className="btn btn-primary" onClick={onNext}>
+        {nextLabel}
+      </button>
+    </div>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/wizard/WizardStepShell.tsx.html b/frontend/coverage/src/components/wizard/WizardStepShell.tsx.html new file mode 100644 index 0000000..52156d8 --- /dev/null +++ b/frontend/coverage/src/components/wizard/WizardStepShell.tsx.html @@ -0,0 +1,136 @@ + + + + + + Code coverage report for src/components/wizard/WizardStepShell.tsx + + + + + + + + + +
+
+

All files / src/components/wizard WizardStepShell.tsx

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18  +  +  +  +  +  +  +  +  +87x +  +  +  +  +  +  +  + 
import type { ReactNode } from 'react'
+ 
+interface WizardStepShellProps {
+  title: string
+  description?: string
+  children: ReactNode
+}
+ 
+export function WizardStepShell({ title, description, children }: Readonly<WizardStepShellProps>) {
+  return (
+    <section>
+      <h1 className="step-title">{title}</h1>
+      {description && <p className="step-description">{description}</p>}
+      {children}
+    </section>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/components/wizard/index.html b/frontend/coverage/src/components/wizard/index.html new file mode 100644 index 0000000..ec56241 --- /dev/null +++ b/frontend/coverage/src/components/wizard/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for src/components/wizard + + + + + + + + + +
+
+

All files src/components/wizard

+
+ +
+ 100% + Statements + 11/11 +
+ + +
+ 100% + Branches + 14/14 +
+ + +
+ 100% + Functions + 7/7 +
+ + +
+ 100% + Lines + 10/10 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
StepIndicator.tsx +
+
100%7/7100%6/6100%3/3100%6/6
SummarySection.tsx +
+
100%2/2100%2/2100%2/2100%2/2
WizardActions.tsx +
+
100%1/1100%4/4100%1/1100%1/1
WizardStepShell.tsx +
+
100%1/1100%2/2100%1/1100%1/1
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/constants/index.html b/frontend/coverage/src/constants/index.html new file mode 100644 index 0000000..49d9b5f --- /dev/null +++ b/frontend/coverage/src/constants/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for src/constants + + + + + + + + + +
+
+

All files src/constants

+
+ +
+ 100% + Statements + 8/8 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 7/7 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
masks.ts +
+
100%4/4100%0/0100%0/0100%4/4
options.ts +
+
100%4/4100%2/2100%2/2100%3/3
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/constants/masks.ts.html b/frontend/coverage/src/constants/masks.ts.html new file mode 100644 index 0000000..c5fc181 --- /dev/null +++ b/frontend/coverage/src/constants/masks.ts.html @@ -0,0 +1,112 @@ + + + + + + Code coverage report for src/constants/masks.ts + + + + + + + + + +
+
+

All files / src/constants masks.ts

+
+ +
+ 100% + Statements + 4/4 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 4/4 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +104x +  +4x +  +4x +  +4x +  +  + 
export const CPF_MASK = { mask: '000.000.000-00' }
+ 
+export const DATE_MASK = { mask: '00/00/0000' }
+ 
+export const CEP_MASK = { mask: '00000-000' }
+ 
+export const PHONE_MASK = {
+  mask: [{ mask: '(00) 0000-0000' }, { mask: '(00) 00000-0000' }],
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/constants/options.ts.html b/frontend/coverage/src/constants/options.ts.html new file mode 100644 index 0000000..398c3d1 --- /dev/null +++ b/frontend/coverage/src/constants/options.ts.html @@ -0,0 +1,223 @@ + + + + + + Code coverage report for src/constants/options.ts + + + + + + + + + +
+
+

All files / src/constants options.ts

+
+ +
+ 100% + Statements + 4/4 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47  +  +  +  +  +15x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +15x +  +  +  +  +  +  +  +  +407x +  + 
export interface SelectOption {
+  value: string
+  label: string
+}
+ 
+export const UF_OPTIONS: SelectOption[] = [
+  { value: 'AC', label: 'Acre' },
+  { value: 'AL', label: 'Alagoas' },
+  { value: 'AP', label: 'Amapá' },
+  { value: 'AM', label: 'Amazonas' },
+  { value: 'BA', label: 'Bahia' },
+  { value: 'CE', label: 'Ceará' },
+  { value: 'DF', label: 'Distrito Federal' },
+  { value: 'ES', label: 'Espírito Santo' },
+  { value: 'GO', label: 'Goiás' },
+  { value: 'MA', label: 'Maranhão' },
+  { value: 'MT', label: 'Mato Grosso' },
+  { value: 'MS', label: 'Mato Grosso do Sul' },
+  { value: 'MG', label: 'Minas Gerais' },
+  { value: 'PA', label: 'Pará' },
+  { value: 'PB', label: 'Paraíba' },
+  { value: 'PR', label: 'Paraná' },
+  { value: 'PE', label: 'Pernambuco' },
+  { value: 'PI', label: 'Piauí' },
+  { value: 'RJ', label: 'Rio de Janeiro' },
+  { value: 'RN', label: 'Rio Grande do Norte' },
+  { value: 'RS', label: 'Rio Grande do Sul' },
+  { value: 'RO', label: 'Rondônia' },
+  { value: 'RR', label: 'Roraima' },
+  { value: 'SC', label: 'Santa Catarina' },
+  { value: 'SP', label: 'São Paulo' },
+  { value: 'SE', label: 'Sergipe' },
+  { value: 'TO', label: 'Tocantins' },
+]
+ 
+export const TEMPO_EMPRESA_OPTIONS: SelectOption[] = [
+  { value: 'menos-6-meses', label: 'Menos de 6 meses' },
+  { value: '6-meses-1-ano', label: 'De 6 meses a 1 ano' },
+  { value: '1-2-anos', label: 'De 1 a 2 anos' },
+  { value: '2-5-anos', label: 'De 2 a 5 anos' },
+  { value: 'mais-5-anos', label: 'Mais de 5 anos' },
+]
+ 
+export function getOptionLabel(options: SelectOption[], value: string): string {
+  return options.find((option) => option.value === value)?.label ?? value
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/context/WizardFormProvider.tsx.html b/frontend/coverage/src/context/WizardFormProvider.tsx.html new file mode 100644 index 0000000..0b80bbf --- /dev/null +++ b/frontend/coverage/src/context/WizardFormProvider.tsx.html @@ -0,0 +1,163 @@ + + + + + + Code coverage report for src/context/WizardFormProvider.tsx + + + + + + + + + +
+
+

All files / src/context WizardFormProvider.tsx

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 6/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27  +  +  +  +  +  +  +17x +  +  +  +  +  +  +  +  +  +17x +5x +98x +  +5x +  +  +17x +  + 
import { useEffect, type ReactNode } from 'react'
+import { FormProvider, useForm } from 'react-hook-form'
+import { zodResolver } from '@hookform/resolvers/zod'
+import { defaultFormValues, formSchema, type FormValues } from '../schemas/formSchema'
+import { loadStoredFormValues, persistFormValues } from '../lib/storage'
+ 
+export function WizardFormProvider({ children }: { children: ReactNode }) {
+  const methods = useForm<FormValues>({
+    resolver: zodResolver(formSchema),
+    mode: 'onSubmit',
+    reValidateMode: 'onChange',
+    defaultValues: {
+      ...defaultFormValues,
+      ...loadStoredFormValues<FormValues>(),
+    },
+  })
+ 
+  useEffect(() => {
+    const subscription = methods.watch((values) => {
+      persistFormValues(values)
+    })
+    return () => subscription.unsubscribe()
+  }, [methods])
+ 
+  return <FormProvider {...methods}>{children}</FormProvider>
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/context/index.html b/frontend/coverage/src/context/index.html new file mode 100644 index 0000000..3aba23c --- /dev/null +++ b/frontend/coverage/src/context/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for src/context + + + + + + + + + +
+
+

All files src/context

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 6/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
WizardFormProvider.tsx +
+
100%7/7100%0/0100%4/4100%6/6
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/hooks/index.html b/frontend/coverage/src/hooks/index.html new file mode 100644 index 0000000..9da27a6 --- /dev/null +++ b/frontend/coverage/src/hooks/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for src/hooks + + + + + + + + + +
+
+

All files src/hooks

+
+ +
+ 100% + Statements + 12/12 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 100% + Lines + 12/12 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
useRequireStepData.ts +
+
100%5/5100%2/2100%2/2100%5/5
useStepNavigation.ts +
+
100%7/7100%2/2100%3/3100%7/7
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/hooks/useRequireStepData.ts.html b/frontend/coverage/src/hooks/useRequireStepData.ts.html new file mode 100644 index 0000000..a644f11 --- /dev/null +++ b/frontend/coverage/src/hooks/useRequireStepData.ts.html @@ -0,0 +1,145 @@ + + + + + + Code coverage report for src/hooks/useRequireStepData.ts + + + + + + + + + +
+
+

All files / src/hooks useRequireStepData.ts

+
+ +
+ 100% + Statements + 5/5 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21  +  +  +  +  +  +  +  +  +  +110x +110x +  +110x +16x +1x +  +  +  +  + 
import { useEffect } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { useFormContext } from 'react-hook-form'
+import type { FormValues } from '../schemas/formSchema'
+ 
+/**
+ * Guards a step against direct URL access before its prerequisite data exists,
+ * e.g. opening /resumo without having gone through the earlier steps.
+ */
+export function useRequireStepData(requiredField: keyof FormValues, redirectTo: string) {
+  const { getValues } = useFormContext<FormValues>()
+  const navigate = useNavigate()
+ 
+  useEffect(() => {
+    if (!getValues(requiredField)) {
+      navigate(redirectTo, { replace: true })
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [])
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/hooks/useStepNavigation.ts.html b/frontend/coverage/src/hooks/useStepNavigation.ts.html new file mode 100644 index 0000000..c22075e --- /dev/null +++ b/frontend/coverage/src/hooks/useStepNavigation.ts.html @@ -0,0 +1,148 @@ + + + + + + Code coverage report for src/hooks/useStepNavigation.ts + + + + + + + + + +
+
+

All files / src/hooks useStepNavigation.ts

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 7/7 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22  +  +  +  +  +115x +115x +  +  +9x +9x +7x +  +  +  +  +2x +  +  +115x +  + 
import { useNavigate } from 'react-router-dom'
+import { useFormContext } from 'react-hook-form'
+import type { FormValues } from '../schemas/formSchema'
+ 
+export function useStepNavigation(fields: (keyof FormValues)[]) {
+  const { trigger } = useFormContext<FormValues>()
+  const navigate = useNavigate()
+ 
+  async function goNext(path: string) {
+    const isValid = await trigger(fields)
+    if (isValid) {
+      navigate(path)
+    }
+  }
+ 
+  function goBack(path: string) {
+    navigate(path)
+  }
+ 
+  return { goNext, goBack }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/index.html b/frontend/coverage/src/index.html new file mode 100644 index 0000000..09ec958 --- /dev/null +++ b/frontend/coverage/src/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for src + + + + + + + + + +
+
+

All files src

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
App.tsx +
+
100%1/1100%0/0100%1/1100%1/1
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/lib/api.ts.html b/frontend/coverage/src/lib/api.ts.html new file mode 100644 index 0000000..641b472 --- /dev/null +++ b/frontend/coverage/src/lib/api.ts.html @@ -0,0 +1,277 @@ + + + + + + Code coverage report for src/lib/api.ts + + + + + + + + + +
+
+

All files / src/lib api.ts

+
+ +
+ 100% + Statements + 17/17 +
+ + +
+ 83.33% + Branches + 5/6 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 15/15 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +4x +1x +  +3x +  +  +  +  +3x +3x +2x +1x +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +4x +4x +3x +  +  +  +  +  +  +  +  +1x +1x +  + 
import axios from 'axios'
+ 
+export const mockApi = axios.create({
+  baseURL: import.meta.env.VITE_MOCK_API_URL ?? 'http://localhost:3001',
+})
+ 
+export interface CepResult {
+  endereco: string
+  bairro: string
+  cidade: string
+  estado: string
+}
+ 
+interface ViaCepResponse {
+  logradouro: string
+  bairro: string
+  localidade: string
+  uf: string
+  erro?: boolean
+}
+ 
+async function fetchCepFromMock(cep: string): Promise<CepResult | null> {
+  try {
+    const { data } = await mockApi.get<CepResult>(`/ceps/${cep}`)
+    return data
+  } catch {
+    return null
+  }
+}
+ 
+async function fetchCepFromViaCep(cep: string): Promise<CepResult | null> {
+  try {
+    const { data } = await axios.get<ViaCepResponse>(`https://viacep.com.br/ws/${cep}/json/`)
+    if (data.erro) return null
+    return {
+      endereco: data.logradouro,
+      bairro: data.bairro,
+      cidade: data.localidade,
+      estado: data.uf,
+    }
+  } catch {
+    return null
+  }
+}
+ 
+/**
+ * Tries the mocked json-server first (challenge requirement) and falls back
+ * to the public ViaCEP service when the CEP isn't present in the mock db (bonus requirement).
+ */
+export async function fetchAddressByCep(cep: string): Promise<CepResult | null> {
+  const mockResult = await fetchCepFromMock(cep)
+  if (mockResult) return mockResult
+  return fetchCepFromViaCep(cep)
+}
+ 
+export interface Profession {
+  id: number | string
+  name: string
+}
+ 
+export async function fetchProfessions(): Promise<Profession[]> {
+  const { data } = await mockApi.get<Profession[]>('/professions')
+  return data
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/lib/cpf.ts.html b/frontend/coverage/src/lib/cpf.ts.html new file mode 100644 index 0000000..4458463 --- /dev/null +++ b/frontend/coverage/src/lib/cpf.ts.html @@ -0,0 +1,163 @@ + + + + + + Code coverage report for src/lib/cpf.ts + + + + + + + + + +
+
+

All files / src/lib cpf.ts

+
+ +
+ 100% + Statements + 14/14 +
+ + +
+ 87.5% + Branches + 7/8 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 13/13 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27  +39x +  +  +  +34x +34x +323x +  +34x +34x +  +  +  +37x +  +37x +20x +  +  +17x +17x +17x +  +17x +  + 
export function onlyDigits(value: string): string {
+  return value.replace(/\D/g, '')
+}
+ 
+function calcCheckDigit(digits: number[], length: number): number {
+  let sum = 0
+  for (let i = 0; i < length; i++) {
+    sum += digits[i] * (length + 1 - i)
+  }
+  const remainder = (sum * 10) % 11
+  return remainder === 10 ? 0 : remainder
+}
+ 
+export function isValidCPF(rawValue: string): boolean {
+  const cpf = onlyDigits(rawValue)
+ 
+  if (cpf.length !== 11 || /^(\d)\1{10}$/.test(cpf)) {
+    return false
+  }
+ 
+  const digits = cpf.split('').map(Number)
+  const firstCheckDigit = calcCheckDigit(digits, 9)
+  const secondCheckDigit = calcCheckDigit(digits, 10)
+ 
+  return firstCheckDigit === digits[9] && secondCheckDigit === digits[10]
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/lib/index.html b/frontend/coverage/src/lib/index.html new file mode 100644 index 0000000..0a3f97f --- /dev/null +++ b/frontend/coverage/src/lib/index.html @@ -0,0 +1,176 @@ + + + + + + Code coverage report for src/lib + + + + + + + + + +
+
+

All files src/lib

+
+ +
+ 100% + Statements + 99/99 +
+ + +
+ 93.54% + Branches + 29/31 +
+ + +
+ 100% + Functions + 15/15 +
+ + +
+ 100% + Lines + 93/93 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
api.ts +
+
100%17/1783.33%5/6100%4/4100%15/15
cpf.ts +
+
100%14/1487.5%7/8100%3/3100%13/13
masks.ts +
+
100%17/17100%11/11100%3/3100%15/15
pdf.ts +
+
100%41/41100%4/4100%2/2100%41/41
storage.ts +
+
100%10/10100%2/2100%3/3100%9/9
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/lib/masks.ts.html b/frontend/coverage/src/lib/masks.ts.html new file mode 100644 index 0000000..5825c2a --- /dev/null +++ b/frontend/coverage/src/lib/masks.ts.html @@ -0,0 +1,178 @@ + + + + + + Code coverage report for src/lib/masks.ts + + + + + + + + + +
+
+

All files / src/lib masks.ts

+
+ +
+ 100% + Statements + 17/17 +
+ + +
+ 100% + Branches + 11/11 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 15/15 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32  +69x +  +  +  +37x +37x +  +21x +21x +21x +21x +  +  +21x +  +  +  +37x +  +17x +17x +  +17x +  +  +  +31x +31x +31x +  + 
export function onlyDigits(value: string): string {
+  return value.replace(/\D/g, '')
+}
+ 
+export function isValidBirthDate(value: string): boolean {
+  const match = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value)
+  if (!match) return false
+ 
+  const day = Number(match[1])
+  const month = Number(match[2])
+  const year = Number(match[3])
+  const date = new Date(year, month - 1, day)
+ 
+  const isRealCalendarDate =
+    date.getFullYear() === year &&
+    date.getMonth() === month - 1 &&
+    date.getDate() === day
+ 
+  if (!isRealCalendarDate) return false
+ 
+  const today = new Date()
+  today.setHours(0, 0, 0, 0)
+ 
+  return date <= today && year >= 1900
+}
+ 
+export function parseCurrencyToNumber(value: string): number {
+  const normalized = value.replace(/[^\d,-]/g, '').replace(',', '.')
+  const parsed = Number.parseFloat(normalized)
+  return Number.isNaN(parsed) ? 0 : parsed
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/lib/pdf.ts.html b/frontend/coverage/src/lib/pdf.ts.html new file mode 100644 index 0000000..eacccba --- /dev/null +++ b/frontend/coverage/src/lib/pdf.ts.html @@ -0,0 +1,370 @@ + + + + + + Code coverage report for src/lib/pdf.ts + + + + + + + + + +
+
+

All files / src/lib pdf.ts

+
+ +
+ 100% + Statements + 41/41 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 41/41 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96  +  +  +  +1x +1x +1x +1x +  +  +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +2x +  +2x +2x +2x +2x +2x +2x +2x +2x +2x +  +2x +  +2x +6x +6x +6x +6x +6x +6x +6x +6x +  +6x +6x +28x +28x +28x +28x +28x +28x +  +  +6x +  +  +2x +2x +2x +2x +  +2x +2x +  + 
import { jsPDF } from 'jspdf'
+import type { FormValues } from '../schemas/formSchema'
+import { getOptionLabel, TEMPO_EMPRESA_OPTIONS, UF_OPTIONS } from '../constants/options'
+ 
+const VERITY_BLUE: [number, number, number] = [13, 71, 161]
+const VERITY_BLUE_DARK: [number, number, number] = [8, 43, 99]
+const TEXT_GRAY: [number, number, number] = [60, 68, 82]
+const MARGIN_X = 16
+ 
+interface Section {
+  title: string
+  rows: [string, string][]
+}
+ 
+function buildSections(data: FormValues): Section[] {
+  return [
+    {
+      title: 'Dados Pessoais',
+      rows: [
+        ['Nome completo', data.fullName],
+        ['Data de nascimento', data.birthDate],
+        ['CPF', data.cpf],
+        ['Telefone', data.phone],
+        ['E-mail', data.email],
+      ],
+    },
+    {
+      title: 'Informações Residenciais',
+      rows: [
+        ['CEP', data.cep],
+        ['Endereço', data.endereco],
+        ['Bairro', data.bairro],
+        ['Cidade', data.cidade],
+        ['Estado', getOptionLabel(UF_OPTIONS, data.estado)],
+      ],
+    },
+    {
+      title: 'Informações Profissionais',
+      rows: [
+        ['Empresa', data.empresa],
+        ['Profissão', data.profissao],
+        ['Salário', data.salario],
+        ['Tempo de empresa', getOptionLabel(TEMPO_EMPRESA_OPTIONS, data.tempoDeEmpresa)],
+      ],
+    },
+  ]
+}
+ 
+export function generateSummaryPdf(data: FormValues): void {
+  const doc = new jsPDF()
+  const pageWidth = doc.internal.pageSize.getWidth()
+ 
+  doc.setFillColor(...VERITY_BLUE)
+  doc.rect(0, 0, pageWidth, 30, 'F')
+  doc.setTextColor(255, 255, 255)
+  doc.setFont('helvetica', 'bold')
+  doc.setFontSize(18)
+  doc.text('verity', MARGIN_X, 19)
+  doc.setFontSize(11)
+  doc.setFont('helvetica', 'normal')
+  doc.text('Resumo do Cadastro', pageWidth - MARGIN_X, 19, { align: 'right' })
+ 
+  let cursorY = 44
+ 
+  for (const section of buildSections(data)) {
+    doc.setTextColor(...VERITY_BLUE_DARK)
+    doc.setFont('helvetica', 'bold')
+    doc.setFontSize(13)
+    doc.text(section.title, MARGIN_X, cursorY)
+    cursorY += 3
+    doc.setDrawColor(...VERITY_BLUE)
+    doc.line(MARGIN_X, cursorY, pageWidth - MARGIN_X, cursorY)
+    cursorY += 8
+ 
+    doc.setFontSize(11)
+    for (const [label, value] of section.rows) {
+      doc.setFont('helvetica', 'bold')
+      doc.setTextColor(...TEXT_GRAY)
+      doc.text(`${label}:`, MARGIN_X, cursorY)
+      doc.setFont('helvetica', 'normal')
+      doc.text(value || '—', MARGIN_X + 55, cursorY)
+      cursorY += 7
+    }
+ 
+    cursorY += 6
+  }
+ 
+  const generatedAt = new Date().toLocaleString('pt-BR')
+  doc.setFontSize(9)
+  doc.setTextColor(...TEXT_GRAY)
+  doc.text(`Gerado em ${generatedAt}`, MARGIN_X, doc.internal.pageSize.getHeight() - 10)
+ 
+  const fileSuffix = data.fullName.trim().toLowerCase().replace(/\s+/g, '-') || 'cadastro'
+  doc.save(`verity-cadastro-${fileSuffix}.pdf`)
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/lib/storage.ts.html b/frontend/coverage/src/lib/storage.ts.html new file mode 100644 index 0000000..675a357 --- /dev/null +++ b/frontend/coverage/src/lib/storage.ts.html @@ -0,0 +1,154 @@ + + + + + + Code coverage report for src/lib/storage.ts + + + + + + + + + +
+
+

All files / src/lib storage.ts

+
+ +
+ 100% + Statements + 10/10 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 9/9 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +243x +  +  +21x +21x +21x +11x +  +1x +  +  +  +  +102x +102x +  +  +  +  +  +  +1x +  + 
const STORAGE_KEY = 'verity:cadastro-wizard'
+ 
+export function loadStoredFormValues<T>(): Partial<T> | undefined {
+  try {
+    const raw = window.localStorage.getItem(STORAGE_KEY)
+    if (!raw) return undefined
+    return JSON.parse(raw) as Partial<T>
+  } catch {
+    return undefined
+  }
+}
+ 
+export function persistFormValues(values: unknown): void {
+  try {
+    window.localStorage.setItem(STORAGE_KEY, JSON.stringify(values))
+  } catch {
+    // storage unavailable (e.g. private mode) — form still works in-memory
+  }
+}
+ 
+export function clearStoredFormValues(): void {
+  window.localStorage.removeItem(STORAGE_KEY)
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/pages/Step1PersonalData.tsx.html b/frontend/coverage/src/pages/Step1PersonalData.tsx.html new file mode 100644 index 0000000..b79fed8 --- /dev/null +++ b/frontend/coverage/src/pages/Step1PersonalData.tsx.html @@ -0,0 +1,193 @@ + + + + + + Code coverage report for src/pages/Step1PersonalData.tsx + + + + + + + + + +
+
+

All files / src/pages Step1PersonalData.tsx

+
+ +
+ 100% + Statements + 3/3 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37  +  +  +  +  +  +  +  +  +11x +  +11x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +  +  +  + 
import { TextField } from '../components/form/TextField'
+import { MaskedTextField } from '../components/form/MaskedTextField'
+import { WizardActions } from '../components/wizard/WizardActions'
+import { WizardStepShell } from '../components/wizard/WizardStepShell'
+import { useStepNavigation } from '../hooks/useStepNavigation'
+import { STEP1_FIELDS } from '../schemas/formSchema'
+import { CPF_MASK, DATE_MASK, PHONE_MASK } from '../constants/masks'
+ 
+export function Step1PersonalData() {
+  const { goNext } = useStepNavigation(STEP1_FIELDS)
+ 
+  return (
+    <WizardStepShell title="Dados Pessoais" description="Preencha suas informações pessoais para começar.">
+      <div className="field-grid">
+        <TextField name="fullName" label="Nome completo" autoComplete="name" fullWidth />
+        <MaskedTextField
+          name="birthDate"
+          label="Data de nascimento"
+          maskOptions={DATE_MASK}
+          placeholder="dd/mm/aaaa"
+          inputMode="numeric"
+        />
+        <MaskedTextField name="cpf" label="CPF" maskOptions={CPF_MASK} placeholder="000.000.000-00" inputMode="numeric" />
+        <MaskedTextField
+          name="phone"
+          label="Telefone"
+          maskOptions={PHONE_MASK}
+          placeholder="(00) 00000-0000"
+          inputMode="numeric"
+        />
+        <TextField name="email" label="E-mail" type="email" autoComplete="email" />
+      </div>
+      <WizardActions onNext={() => goNext('/etapa-2')} />
+    </WizardStepShell>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/pages/Step2Address.tsx.html b/frontend/coverage/src/pages/Step2Address.tsx.html new file mode 100644 index 0000000..2160b84 --- /dev/null +++ b/frontend/coverage/src/pages/Step2Address.tsx.html @@ -0,0 +1,328 @@ + + + + + + Code coverage report for src/pages/Step2Address.tsx + + + + + + + + + +
+
+

All files / src/pages Step2Address.tsx

+
+ +
+ 96.42% + Statements + 27/28 +
+ + +
+ 75% + Branches + 6/8 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 100% + Lines + 26/26 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  +  +83x +83x +83x +83x +83x +  +83x +37x +37x +33x +33x +  +  +4x +4x +  +4x +4x +4x +3x +3x +3x +3x +3x +  +1x +  +  +  +4x +4x +  +  +  +83x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  + 
import { useEffect, useState } from 'react'
+import { useFormContext, useWatch } from 'react-hook-form'
+import { TextField } from '../components/form/TextField'
+import { SelectField } from '../components/form/SelectField'
+import { MaskedTextField } from '../components/form/MaskedTextField'
+import { WizardActions } from '../components/wizard/WizardActions'
+import { WizardStepShell } from '../components/wizard/WizardStepShell'
+import { useStepNavigation } from '../hooks/useStepNavigation'
+import { useRequireStepData } from '../hooks/useRequireStepData'
+import { STEP2_FIELDS, type FormValues } from '../schemas/formSchema'
+import { CEP_MASK } from '../constants/masks'
+import { UF_OPTIONS } from '../constants/options'
+import { fetchAddressByCep } from '../lib/api'
+import { onlyDigits } from '../lib/masks'
+ 
+type CepStatus = 'idle' | 'loading' | 'success' | 'not-found'
+ 
+const CEP_STATUS_HINTS: Partial<Record<CepStatus, string>> = {
+  loading: 'Buscando endereço...',
+  'not-found': 'CEP não encontrado, preencha manualmente.',
+}
+ 
+export function Step2Address() {
+  useRequireStepData('fullName', '/etapa-1')
+  const { control, setValue } = useFormContext<FormValues>()
+  const { goNext, goBack } = useStepNavigation(STEP2_FIELDS)
+  const [cepStatus, setCepStatus] = useState<CepStatus>('idle')
+  const cep = useWatch({ control, name: 'cep' })
+ 
+  useEffect(() => {
+    const digits = onlyDigits(cep ?? '')
+    if (digits.length !== 8) {
+      setCepStatus('idle')
+      return
+    }
+ 
+    let cancelled = false
+    setCepStatus('loading')
+ 
+    fetchAddressByCep(digits).then((result) => {
+      Iif (cancelled) return
+      if (result) {
+        setValue('endereco', result.endereco, { shouldValidate: true })
+        setValue('bairro', result.bairro, { shouldValidate: true })
+        setValue('cidade', result.cidade, { shouldValidate: true })
+        setValue('estado', result.estado.toUpperCase(), { shouldValidate: true })
+        setCepStatus('success')
+      } else {
+        setCepStatus('not-found')
+      }
+    })
+ 
+    return () => {
+      cancelled = true
+    }
+  }, [cep, setValue])
+ 
+  return (
+    <WizardStepShell
+      title="Informações Residenciais"
+      description="Informe o CEP para preenchermos o endereço automaticamente."
+    >
+      <div className="field-grid">
+        <MaskedTextField
+          name="cep"
+          label="CEP"
+          maskOptions={CEP_MASK}
+          placeholder="00000-000"
+          inputMode="numeric"
+          fullWidth
+          hint={CEP_STATUS_HINTS[cepStatus]}
+        />
+        <TextField name="endereco" label="Endereço" fullWidth />
+        <TextField name="bairro" label="Bairro" />
+        <TextField name="cidade" label="Cidade" />
+        <SelectField name="estado" label="Estado" options={UF_OPTIONS} placeholder="UF" />
+      </div>
+      <WizardActions onBack={() => goBack('/etapa-1')} onNext={() => goNext('/etapa-3')} />
+    </WizardStepShell>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/pages/Step3Professional.tsx.html b/frontend/coverage/src/pages/Step3Professional.tsx.html new file mode 100644 index 0000000..b8f0d3f --- /dev/null +++ b/frontend/coverage/src/pages/Step3Professional.tsx.html @@ -0,0 +1,253 @@ + + + + + + Code coverage report for src/pages/Step3Professional.tsx + + + + + + + + + +
+
+

All files / src/pages Step3Professional.tsx

+
+ +
+ 80% + Statements + 16/20 +
+ + +
+ 50% + Branches + 4/8 +
+ + +
+ 77.77% + Functions + 7/9 +
+ + +
+ 93.33% + Lines + 14/15 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57  +  +  +  +  +  +  +  +  +  +  +  +  +13x +13x +13x +13x +  +13x +4x +  +4x +  +4x +4x +  +  +  +  +  +4x +  +  +4x +4x +  +  +  +13x +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  + 
import { useEffect, useState } from 'react'
+import { TextField } from '../components/form/TextField'
+import { SelectField } from '../components/form/SelectField'
+import { CurrencyField } from '../components/form/CurrencyField'
+import { WizardActions } from '../components/wizard/WizardActions'
+import { WizardStepShell } from '../components/wizard/WizardStepShell'
+import { useStepNavigation } from '../hooks/useStepNavigation'
+import { useRequireStepData } from '../hooks/useRequireStepData'
+import { STEP3_FIELDS } from '../schemas/formSchema'
+import { TEMPO_EMPRESA_OPTIONS, type SelectOption } from '../constants/options'
+import { fetchProfessions } from '../lib/api'
+ 
+export function Step3Professional() {
+  useRequireStepData('endereco', '/etapa-2')
+  const { goNext, goBack } = useStepNavigation(STEP3_FIELDS)
+  const [professionOptions, setProfessionOptions] = useState<SelectOption[]>([])
+  const [loadingProfessions, setLoadingProfessions] = useState(true)
+ 
+  useEffect(() => {
+    let cancelled = false
+ 
+    fetchProfessions()
+      .then((professions) => {
+        Iif (cancelled) return
+        setProfessionOptions(professions.map((p) => ({ value: p.name, label: p.name })))
+      })
+      .catch(() => {
+        if (!cancelled) setProfessionOptions([])
+      })
+      .finally(() => {
+        Eif (!cancelled) setLoadingProfessions(false)
+      })
+ 
+    return () => {
+      cancelled = true
+    }
+  }, [])
+ 
+  return (
+    <WizardStepShell title="Informações Profissionais" description="Conte-nos um pouco sobre sua vida profissional.">
+      <div className="field-grid">
+        <TextField name="empresa" label="Empresa" fullWidth />
+        <SelectField
+          name="profissao"
+          label="Profissão"
+          options={professionOptions}
+          placeholder={loadingProfessions ? 'Carregando profissões...' : 'Selecione...'}
+          disabled={loadingProfessions}
+        />
+        <CurrencyField name="salario" label="Salário" />
+        <SelectField name="tempoDeEmpresa" label="Tempo de empresa" options={TEMPO_EMPRESA_OPTIONS} fullWidth />
+      </div>
+      <WizardActions onBack={() => goBack('/etapa-2')} onNext={() => goNext('/resumo')} nextLabel="Ver resumo" />
+    </WizardStepShell>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/pages/SummaryPage.tsx.html b/frontend/coverage/src/pages/SummaryPage.tsx.html new file mode 100644 index 0000000..08ec06b --- /dev/null +++ b/frontend/coverage/src/pages/SummaryPage.tsx.html @@ -0,0 +1,298 @@ + + + + + + Code coverage report for src/pages/SummaryPage.tsx + + + + + + + + + +
+
+

All files / src/pages SummaryPage.tsx

+
+ +
+ 83.33% + Statements + 10/12 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 66.66% + Functions + 4/6 +
+ + +
+ 83.33% + Lines + 10/12 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72  +  +  +  +  +  +  +  +  +  +  +10x +10x +10x +10x +  +  +2x +  +  +  +1x +1x +1x +  +  +10x +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { useNavigate } from 'react-router-dom'
+import { useFormContext } from 'react-hook-form'
+import { WizardStepShell } from '../components/wizard/WizardStepShell'
+import { SummarySection } from '../components/wizard/SummarySection'
+import { useRequireStepData } from '../hooks/useRequireStepData'
+import { defaultFormValues, type FormValues } from '../schemas/formSchema'
+import { getOptionLabel, TEMPO_EMPRESA_OPTIONS, UF_OPTIONS } from '../constants/options'
+import { generateSummaryPdf } from '../lib/pdf'
+import { clearStoredFormValues } from '../lib/storage'
+ 
+export function SummaryPage() {
+  useRequireStepData('profissao', '/etapa-3')
+  const { getValues, reset } = useFormContext<FormValues>()
+  const navigate = useNavigate()
+  const values = getValues()
+ 
+  function handleExport() {
+    generateSummaryPdf(values)
+  }
+ 
+  function handleNewRegistration() {
+    clearStoredFormValues()
+    reset(defaultFormValues)
+    navigate('/etapa-1')
+  }
+ 
+  return (
+    <WizardStepShell title="Resumo do Cadastro" description="Confira seus dados antes de exportar o PDF.">
+      <SummarySection
+        title="Dados Pessoais"
+        onEdit={() => navigate('/etapa-1')}
+        items={[
+          { label: 'Nome completo', value: values.fullName },
+          { label: 'Data de nascimento', value: values.birthDate },
+          { label: 'CPF', value: values.cpf },
+          { label: 'Telefone', value: values.phone },
+          { label: 'E-mail', value: values.email },
+        ]}
+      />
+      <SummarySection
+        title="Informações Residenciais"
+        onEdit={() => navigate('/etapa-2')}
+        items={[
+          { label: 'CEP', value: values.cep },
+          { label: 'Endereço', value: values.endereco },
+          { label: 'Bairro', value: values.bairro },
+          { label: 'Cidade', value: values.cidade },
+          { label: 'Estado', value: getOptionLabel(UF_OPTIONS, values.estado) },
+        ]}
+      />
+      <SummarySection
+        title="Informações Profissionais"
+        onEdit={() => navigate('/etapa-3')}
+        items={[
+          { label: 'Empresa', value: values.empresa },
+          { label: 'Profissão', value: values.profissao },
+          { label: 'Salário', value: values.salario },
+          { label: 'Tempo de empresa', value: getOptionLabel(TEMPO_EMPRESA_OPTIONS, values.tempoDeEmpresa) },
+        ]}
+      />
+      <div className="wizard-actions">
+        <button type="button" className="btn btn-secondary" onClick={handleNewRegistration}>
+          Novo cadastro
+        </button>
+        <button type="button" className="btn btn-primary" onClick={handleExport}>
+          Exportar PDF
+        </button>
+      </div>
+    </WizardStepShell>
+  )
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/pages/index.html b/frontend/coverage/src/pages/index.html new file mode 100644 index 0000000..b7f516f --- /dev/null +++ b/frontend/coverage/src/pages/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for src/pages + + + + + + + + + +
+
+

All files src/pages

+
+ +
+ 88.88% + Statements + 56/63 +
+ + +
+ 62.5% + Branches + 10/16 +
+ + +
+ 82.6% + Functions + 19/23 +
+ + +
+ 94.64% + Lines + 53/56 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
Step1PersonalData.tsx +
+
100%3/3100%0/0100%2/2100%3/3
Step2Address.tsx +
+
96.42%27/2875%6/8100%6/6100%26/26
Step3Professional.tsx +
+
80%16/2050%4/877.77%7/993.33%14/15
SummaryPage.tsx +
+
83.33%10/12100%0/066.66%4/683.33%10/12
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/schemas/formSchema.ts.html b/frontend/coverage/src/schemas/formSchema.ts.html new file mode 100644 index 0000000..b32bbf9 --- /dev/null +++ b/frontend/coverage/src/schemas/formSchema.ts.html @@ -0,0 +1,328 @@ + + + + + + Code coverage report for src/schemas/formSchema.ts + + + + + + + + + +
+
+

All files / src/schemas formSchema.ts

+
+ +
+ 100% + Statements + 15/15 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 14/14 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82  +  +  +  +  +13x +13x +351x +  +13x +  +  +  +  +29x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +13x +  +  +  +  +  +  +  +26x +  +  +  +  +13x +  +  +  +  +  +26x +  +  +  +  +  +13x +  +  +  +13x +13x +13x +  +13x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { z } from 'zod'
+import { isValidCPF } from '../lib/cpf'
+import { isValidBirthDate, parseCurrencyToNumber } from '../lib/masks'
+import { UF_OPTIONS } from '../constants/options'
+ 
+const PHONE_REGEX = /^\(\d{2}\) \d{4,5}-\d{4}$/
+const CEP_REGEX = /^\d{5}-\d{3}$/
+const UF_CODES = UF_OPTIONS.map((option) => option.value)
+ 
+export const personalDataSchema = z.object({
+  fullName: z
+    .string()
+    .trim()
+    .min(1, 'Informe o nome completo')
+    .refine((value) => value.trim().split(/\s+/).length >= 2, {
+      message: 'Informe nome e sobrenome',
+    }),
+  birthDate: z
+    .string()
+    .min(1, 'Informe a data de nascimento')
+    .refine(isValidBirthDate, { message: 'Data de nascimento inválida' }),
+  cpf: z
+    .string()
+    .min(1, 'Informe o CPF')
+    .refine(isValidCPF, { message: 'CPF inválido' }),
+  phone: z
+    .string()
+    .min(1, 'Informe o telefone')
+    .regex(PHONE_REGEX, 'Telefone inválido'),
+  email: z.string().min(1, 'Informe o e-mail').email('E-mail inválido'),
+})
+ 
+export const addressSchema = z.object({
+  cep: z.string().min(1, 'Informe o CEP').regex(CEP_REGEX, 'CEP inválido'),
+  endereco: z.string().min(1, 'Informe o endereço'),
+  bairro: z.string().min(1, 'Informe o bairro'),
+  cidade: z.string().min(1, 'Informe a cidade'),
+  estado: z
+    .string()
+    .min(1, 'Informe o estado')
+    .refine((value) => UF_CODES.includes(value.toUpperCase()), {
+      message: 'Estado inválido',
+    }),
+})
+ 
+export const professionalSchema = z.object({
+  empresa: z.string().min(1, 'Informe a empresa'),
+  profissao: z.string().min(1, 'Selecione uma profissão'),
+  salario: z
+    .string()
+    .min(1, 'Informe o salário')
+    .refine((value) => parseCurrencyToNumber(value) > 0, {
+      message: 'Informe um salário válido',
+    }),
+  tempoDeEmpresa: z.string().min(1, 'Selecione o tempo de empresa'),
+})
+ 
+export const formSchema = personalDataSchema.merge(addressSchema).merge(professionalSchema)
+ 
+export type FormValues = z.infer<typeof formSchema>
+ 
+export const STEP1_FIELDS = Object.keys(personalDataSchema.shape) as (keyof FormValues)[]
+export const STEP2_FIELDS = Object.keys(addressSchema.shape) as (keyof FormValues)[]
+export const STEP3_FIELDS = Object.keys(professionalSchema.shape) as (keyof FormValues)[]
+ 
+export const defaultFormValues: FormValues = {
+  fullName: '',
+  birthDate: '',
+  cpf: '',
+  phone: '',
+  email: '',
+  cep: '',
+  endereco: '',
+  bairro: '',
+  cidade: '',
+  estado: '',
+  empresa: '',
+  profissao: '',
+  salario: '',
+  tempoDeEmpresa: '',
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage/src/schemas/index.html b/frontend/coverage/src/schemas/index.html new file mode 100644 index 0000000..84a5dde --- /dev/null +++ b/frontend/coverage/src/schemas/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for src/schemas + + + + + + + + + +
+
+

All files src/schemas

+
+ +
+ 100% + Statements + 15/15 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 14/14 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
formSchema.ts +
+
100%15/15100%0/0100%4/4100%14/14
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..358e583 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Verity | Cadastro + + +
+ + + diff --git a/frontend/mock/db.json b/frontend/mock/db.json new file mode 100644 index 0000000..9193d88 --- /dev/null +++ b/frontend/mock/db.json @@ -0,0 +1,86 @@ +{ + "ceps": [ + { + "id": "01310100", + "endereco": "Avenida Paulista", + "bairro": "Bela Vista", + "cidade": "São Paulo", + "estado": "SP" + }, + { + "id": "01001000", + "endereco": "Praça da Sé", + "bairro": "Sé", + "cidade": "São Paulo", + "estado": "SP" + }, + { + "id": "20040020", + "endereco": "Avenida Rio Branco", + "bairro": "Centro", + "cidade": "Rio de Janeiro", + "estado": "RJ" + }, + { + "id": "30130010", + "endereco": "Avenida Afonso Pena", + "bairro": "Centro", + "cidade": "Belo Horizonte", + "estado": "MG" + }, + { + "id": "80010000", + "endereco": "Rua XV de Novembro", + "bairro": "Centro", + "cidade": "Curitiba", + "estado": "PR" + }, + { + "id": "90010000", + "endereco": "Rua dos Andradas", + "bairro": "Centro Histórico", + "cidade": "Porto Alegre", + "estado": "RS" + }, + { + "id": "40010000", + "endereco": "Avenida Sete de Setembro", + "bairro": "Centro", + "cidade": "Salvador", + "estado": "BA" + }, + { + "id": "70040010", + "endereco": "Praça dos Três Poderes", + "bairro": "Zona Cívico-Administrativa", + "cidade": "Brasília", + "estado": "DF" + }, + { + "id": "60060170", + "endereco": "Avenida Beira Mar", + "bairro": "Praia de Iracema", + "cidade": "Fortaleza", + "estado": "CE" + }, + { + "id": "50030230", + "endereco": "Avenida Guararapes", + "bairro": "Santo Antônio", + "cidade": "Recife", + "estado": "PE" + } + ], + "professions": [ + { "id": 1, "name": "Desenvolvedor(a) de Software" }, + { "id": 2, "name": "Analista de Sistemas" }, + { "id": 3, "name": "Designer UX/UI" }, + { "id": 4, "name": "Gerente de Projetos" }, + { "id": 5, "name": "Analista Financeiro" }, + { "id": 6, "name": "Advogado(a)" }, + { "id": 7, "name": "Médico(a)" }, + { "id": 8, "name": "Professor(a)" }, + { "id": 9, "name": "Contador(a)" }, + { "id": 10, "name": "Engenheiro(a) Civil" } + ] +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..0a5a04b --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,45 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "dev:all": "run-p dev mock:api", + "mock:api": "json-server --port 3001 mock/db.json", + "build": "tsc -b && vite build", + "lint": "oxlint", + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" + }, + "dependencies": { + "@hookform/resolvers": "^5.4.0", + "axios": "^1.18.1", + "jspdf": "^4.2.1", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-hook-form": "^7.81.0", + "react-imask": "^7.6.1", + "react-router-dom": "^7.18.1", + "zod": "^4.4.3" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^24.13.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "@vitest/coverage-v8": "^4.1.10", + "jsdom": "^29.1.1", + "json-server": "1.0.0-beta.15", + "npm-run-all2": "^9.0.2", + "oxlint": "^1.71.0", + "typescript": "~6.0.2", + "vite": "^8.1.1", + "vitest": "^4.1.10" + } +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..bd2a33d --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,2748 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@hookform/resolvers': + specifier: ^5.4.0 + version: 5.4.0(react-hook-form@7.81.0(react@19.2.7)) + axios: + specifier: ^1.18.1 + version: 1.18.1 + jspdf: + specifier: ^4.2.1 + version: 4.2.1 + react: + specifier: ^19.2.7 + version: 19.2.7 + react-dom: + specifier: ^19.2.7 + version: 19.2.7(react@19.2.7) + react-hook-form: + specifier: ^7.81.0 + version: 7.81.0(react@19.2.7) + react-imask: + specifier: ^7.6.1 + version: 7.6.1(react@19.2.7) + react-router-dom: + specifier: ^7.18.1 + version: 7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/node': + specifier: ^24.13.2 + version: 24.13.3 + '@types/react': + specifier: ^19.2.17 + version: 19.2.17 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.17) + '@vitejs/plugin-react': + specifier: ^6.0.3 + version: 6.0.3(vite@8.1.4(@types/node@24.13.3)) + '@vitest/coverage-v8': + specifier: ^4.1.10 + version: 4.1.10(vitest@4.1.10) + jsdom: + specifier: ^29.1.1 + version: 29.1.1 + json-server: + specifier: 1.0.0-beta.15 + version: 1.0.0-beta.15 + npm-run-all2: + specifier: ^9.0.2 + version: 9.0.2 + oxlint: + specifier: ^1.71.0 + version: 1.73.0 + typescript: + specifier: ~6.0.2 + version: 6.0.3 + vite: + specifier: ^8.1.1 + version: 8.1.4(@types/node@24.13.3) + vitest: + specifier: ^4.1.10 + version: 4.1.10(@types/node@24.13.3)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1)(vite@8.1.4(@types/node@24.13.3)) + +packages: + + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.1.1': + resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime-corejs3@7.29.7': + resolution: {integrity: sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.9': + resolution: {integrity: sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.6': + resolution: {integrity: sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@hookform/resolvers@5.4.0': + resolution: {integrity: sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==} + peerDependencies: + react-hook-form: ^7.55.0 + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.139.0': + resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + + '@oxlint/binding-android-arm-eabi@1.73.0': + resolution: {integrity: sha512-HZQRN/UMBu+Ut+/9MiAChkbP4qZqrNOWBcNI45vOT40GVhbGR0JgHB87L48D4iAqFQIdVmeQYtV9RF89AjTKkg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.73.0': + resolution: {integrity: sha512-Gp+KJRylv2aW7thRpG5p1KTxZq4ZJFbWowrKzufNq9d3ssl3r3JviYV45/+p+7CN1Nv0zDd1e8Ex0b/HUDq4TQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.73.0': + resolution: {integrity: sha512-3de96NdtXhxERMjIz7wsp2HYMY6pMQycGxFWac2mFecAx6VeARF/IqFb1QIaqiCRIdfzBwzTed+pCTCoiS+CYA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.73.0': + resolution: {integrity: sha512-5zx/uPW32TiaOeVY1dQ/H5iOf0K1HOdFKOJhLqGl4o63+i1fpzoqqu/mKtd7OFgFjNCdhlyTGgjVkQTZm1ELcg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.73.0': + resolution: {integrity: sha512-qNe4gKHaGnLuZJ8toUg90JAa0S2vTVvDw+0bRi3q1avXZXDT4u5mMeECf3nD4HYrbdn1O7dXqWut4onY/yx/Xg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.73.0': + resolution: {integrity: sha512-cCehYh5hTbfShm/fxTD6wwrGUWIpvX+N5OxmAMhFhDeTGXvw+BeNj889tpxsFQ9ZLatQ6wImuY8tsKLZ+FMz7w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.73.0': + resolution: {integrity: sha512-d5j5GDU/2dMgjVhw7TQT9ITrsIr1Y02KEXKyVGIXUkD+KiaxE9TP65FS2ZdgTBemQvoRL+gSBdbrIm3cQIeacg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.73.0': + resolution: {integrity: sha512-Eyf1SrP3+yR1DI3OJgOY2Pvrr9dWP9TK37xPaDYycwTtlGlI45erJAVIfH5/m/xosDt6BupJYEFi47bvbTuuyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxlint/binding-linux-arm64-musl@1.73.0': + resolution: {integrity: sha512-IlT/OJApEDKaMmCooHuncgJZbbCe7T5QIWmTZBEtYscWvzPQuuEinVcid6kwQRVQOUdb7PUCz4jQHnaYXdfJXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxlint/binding-linux-ppc64-gnu@1.73.0': + resolution: {integrity: sha512-L+JYcb/vdg5fmcH08V6o0YYLU28cTH1SPNulwJdvK9NK49aXSkYy6oNpKBmddArVOXYqNepriDGiZ04G54kh1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxlint/binding-linux-riscv64-gnu@1.73.0': + resolution: {integrity: sha512-Qtk0g3bKV6OwWjIm7R8kQN1uOZRKQt/MODK2a8QfkwhTpXBD53ozx5XLVWLGDQAVyp2otLW4D2wB98XfAfMPGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxlint/binding-linux-riscv64-musl@1.73.0': + resolution: {integrity: sha512-wX0NQKZVxltkAOVmzFcpOaMpdaUvsq1Eqpx9tkAfl71UdkTlSo1R4AdAnGccR1Fm2+TzFgZ22CyyGuZ41RDr/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxlint/binding-linux-s390x-gnu@1.73.0': + resolution: {integrity: sha512-vPe7UGBMWyiLTtnqS4xxgMQFSFGmtQwhwCxuiw6lXygaO6bVt0D8dFVg8Xv05eaiN3ybC0HXXHUAohFMFvqoCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@oxlint/binding-linux-x64-gnu@1.73.0': + resolution: {integrity: sha512-2CwIWr9cemFC/CbRBWZvuk5mffz6ObmfFkfcC/9rTQ7f+icNhYr2kOjf9Rt8lLvugvkdGDOmkoVoFFHh6ClCTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxlint/binding-linux-x64-musl@1.73.0': + resolution: {integrity: sha512-nDadfJgg7NBBxG0N560wOe7LLX5QiYp6qBaI7viuk5EUORFBktU/NfV0MbTqU3gTqQDCh4VyxKdo5VADxk9w8Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxlint/binding-openharmony-arm64@1.73.0': + resolution: {integrity: sha512-wGjJC+NLH9xP+IKGn9RDW94ojJR/wPbg5WCnQjj/oReaOtCQthr8ws1zICe77JFmo4ouUdeTHHZL/ESGiF6Pmw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.73.0': + resolution: {integrity: sha512-I7X47GPGljw225YUQ5SbC/rb1Kkdrd0yQf0x+hYxeKS6DpfjMbo9ccQPQ6LNY6BoJQ1sHhgDUGuMn5Vg5gHT6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.73.0': + resolution: {integrity: sha512-5lWj+3h+74Fm1jYOO9qkJA4xkAlZA099DkXppuXsk7UpnpZLttsefrZU469vChGaG6hcSqrkKXQOvMTZtbjeNg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.73.0': + resolution: {integrity: sha512-WaNRvh4f6zY9CvUQk2YoA1O90ieWrIklI84+HXFr9Isjz9CSESrdqo/RtIYt4Dll/cAchqGDMehfaZd0vqEFZw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@rolldown/binding-android-arm64@1.1.5': + resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.5': + resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.5': + resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.5': + resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.1.5': + resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.1.5': + resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.1.5': + resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.1.5': + resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.5': + resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.5': + resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@tinyhttp/accepts@2.3.0': + resolution: {integrity: sha512-hdKkMGAUqnagpWO1R8rVBYqbu4sWQ2Fo682gkJmO0nl54DPvnzxx81b2WZtV3VwB7EdLfUoasj2BAkyTcyZ5aw==} + engines: {node: '>=14.13.1'} + + '@tinyhttp/app@3.0.7': + resolution: {integrity: sha512-btit/gSWisksJ19crNLct1mwZvX+/AYwh/H1x8SB/VGDmNFkSsdRDZaa54wW1Eq3bM7mCIr7+l7Oh3tAi/C+Bw==} + engines: {node: '>=16.10.0'} + + '@tinyhttp/content-disposition@2.2.4': + resolution: {integrity: sha512-5Kc5CM2Ysn3vTTArBs2vESUt0AQiWZA86yc1TI3B+lxXmtEq133C1nxXNOgnzhrivdPZIh3zLj5gDnZjoLL5GA==} + engines: {node: '>=12.17.0'} + + '@tinyhttp/content-type@0.1.4': + resolution: {integrity: sha512-dl6f3SHIJPYbhsW1oXdrqOmLSQF/Ctlv3JnNfXAE22kIP7FosqJHxkz/qj2gv465prG8ODKH5KEyhBkvwrueKQ==} + engines: {node: '>=12.4'} + + '@tinyhttp/cookie-signature@2.1.1': + resolution: {integrity: sha512-VDsSMY5OJfQJIAtUgeQYhqMPSZptehFSfvEEtxr+4nldPA8IImlp3QVcOVuK985g4AFR4Hl1sCbWCXoqBnVWnw==} + engines: {node: '>=12.20.0'} + + '@tinyhttp/cookie@2.1.1': + resolution: {integrity: sha512-h/kL9jY0e0Dvad+/QU3efKZww0aTvZJslaHj3JTPmIPC9Oan9+kYqmh3M6L5JUQRuTJYFK2nzgL2iJtH2S+6dA==} + engines: {node: '>=12.20.0'} + + '@tinyhttp/cors@2.0.1': + resolution: {integrity: sha512-qrmo6WJuaiCzKWagv2yA/kw6hIISfF/hOqPWwmI6w0o8apeTMmRN3DoCFvQ/wNVuWVdU5J4KU7OX8aaSOEq51A==} + engines: {node: '>=12.20 || 14.x || >=16'} + + '@tinyhttp/encode-url@2.1.1': + resolution: {integrity: sha512-AhY+JqdZ56qV77tzrBm0qThXORbsVjs/IOPgGCS7x/wWnsa/Bx30zDUU/jPAUcSzNOzt860x9fhdGpzdqbUeUw==} + engines: {node: '>=12.20.0'} + + '@tinyhttp/etag@2.1.2': + resolution: {integrity: sha512-j80fPKimGqdmMh6962y+BtQsnYPVCzZfJw0HXjyH70VaJBHLKGF+iYhcKqzI3yef6QBNa8DKIPsbEYpuwApXTw==} + engines: {node: '>=12.20.0'} + + '@tinyhttp/forwarded@2.1.2': + resolution: {integrity: sha512-9H/eulJ68ElY/+zYpTpNhZ7vxGV+cnwaR6+oQSm7bVgZMyuQfgROW/qvZuhmgDTIxnGMXst+Ba4ij6w6Krcs3w==} + engines: {node: '>=12.20.0'} + + '@tinyhttp/logger@2.1.0': + resolution: {integrity: sha512-Ma1fJ9CwUbn9r61/4HW6+nflsVoslpOnCrfQ6UeZq7GGIgwLzofms3HoSVG7M+AyRMJpxlfcDdbH5oFVroDMKA==} + engines: {node: '>=14.18 || >=16.20'} + + '@tinyhttp/proxy-addr@3.0.1': + resolution: {integrity: sha512-vP0JVsy9ZMIldsaP/QHdMF+sb3B6wn7e2QXRdqpX/Cqz1ie35Am29DK88DeVmiwdTQle3FtYaVNtU3RgTGYZ+w==} + engines: {node: '>=16.10.0'} + + '@tinyhttp/req@2.2.8': + resolution: {integrity: sha512-HCsceFNgMpssUsnRao16iJyyfdWRwKlhL7OMTPUEjZsZGREnBzpjlrPHA31G5xNNzR7XOWVXDXGyrGgSpcwGSA==} + engines: {node: '>=14.13.1'} + + '@tinyhttp/res@2.2.11': + resolution: {integrity: sha512-t7GJzjqpG2svJ11RYvqaYU+xTV9MsEr0usbTIAWa5B8d7IIzpMC6AlT2K6wCIu1XGUYRpZY7qMM5cMgluvsfeg==} + engines: {node: '>=14.13.1'} + + '@tinyhttp/router@2.2.5': + resolution: {integrity: sha512-HI9Mpo9+IVpCzx/36okjJvtvifBSh3Ufhl9n1vylAbNLEykceJiMBkr06+W0qqRlo8TiZeUtg2XinEJu+GFcRA==} + engines: {node: '>=14.0.0'} + + '@tinyhttp/send@2.2.5': + resolution: {integrity: sha512-XhBwziPOCydOJzb9rVw0xuKX6HmMA0gXKHHqec7V97jU6JtSCCIvwW2FBMh/XrG9S5W8DRETroPohASrIwf7Uw==} + engines: {node: '>=14.13.1'} + + '@tinyhttp/type-is@2.2.5': + resolution: {integrity: sha512-BCPEB+NV8v/9lzEE9GbfRPAKVsyayp84m6SSWn70j8yFkPBXeuVeq004pwVrjW1CRdmAZz9ZSH147pqqzAdr5g==} + engines: {node: '>=14.13.1'} + + '@tinyhttp/url@2.1.1': + resolution: {integrity: sha512-POJeq2GQ5jI7Zrdmj22JqOijB5/GeX+LEX7DUdml1hUnGbJOTWDx7zf2b5cCERj7RoXL67zTgyzVblBJC+NJWg==} + engines: {node: '>=12.20.0'} + + '@tinyhttp/vary@0.1.3': + resolution: {integrity: sha512-SoL83sQXAGiHN1jm2VwLUWQSQeDAAl1ywOm6T0b0Cg1CZhVsjoiZadmjhxF6FHCCY7OHHVaLnTgSMxTPIDLxMg==} + engines: {node: '>=12.20'} + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@24.13.3': + resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + + '@types/pako@2.0.4': + resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} + + '@types/raf@3.4.3': + resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@vitejs/plugin-react@6.0.3': + resolution: {integrity: sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + '@vitest/coverage-v8@4.1.10': + resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==} + peerDependencies: + '@vitest/browser': 4.1.10 + vitest: 4.1.10 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.4: + resolution: {integrity: sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.18.1: + resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==} + + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + canvg@3.0.11: + resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==} + engines: {node: '>=10.0.0'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + core-js-pure@3.49.0: + resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==} + + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-line-break@2.1.0: + resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dompurify@3.4.12: + resolution: {integrity: sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==} + + dot-prop@10.1.0: + resolution: {integrity: sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==} + engines: {node: '>=20'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eta@4.6.0: + resolution: {integrity: sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==} + engines: {node: '>=20'} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fast-png@6.4.0: + resolution: {integrity: sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + header-range-parser@1.1.5: + resolution: {integrity: sha512-n5JOx67HBL0MGqtu6NFoEYWb+xDYAOgBI5dBkyMDff1xHbhGnjCMglj1aiMNPHps6HwXO+2i5jbPU/zJSk7etQ==} + engines: {node: '>=12.22.0'} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + html2canvas@1.4.1: + resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} + engines: {node: '>=8.0.0'} + + http-status-emojis@2.2.0: + resolution: {integrity: sha512-ompKtgwpx8ff0hsbpIB7oE4ax1LXoHmftsHHStMELX56ivG3GhofTX8ZHWlUaFKfGjcGjw6G3rPk7dJRXMmbbg==} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + imask@7.6.1: + resolution: {integrity: sha512-sJlIFM7eathUEMChTh9Mrfw/IgiWgJqBKq2VNbyXvBZ7ev/IlO6/KQTKlV/Fm+viQMLrFLG/zCuudrLIwgK2dg==} + engines: {npm: '>=4.0.0'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflection@3.0.2: + resolution: {integrity: sha512-+Bg3+kg+J6JUWn8J6bzFmOWkTQ6L/NHfDRSYU+EVvuKHDxUDHAXgqixHfVlzuBQaPOTac8hn43aPhMNk6rMe3g==} + engines: {node: '>=18.0.0'} + + iobuffer@5.4.0: + resolution: {integrity: sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsdom@29.1.1: + resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + json-parse-even-better-errors@6.0.0: + resolution: {integrity: sha512-2/8adwnK1/+Fdjyts4r6wSpfANWw8zdNhU9U/Llk59c6O+DjSisPWPykwoL8gZmocP9Dy64S7oie2g+Mia123A==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + json-server@1.0.0-beta.15: + resolution: {integrity: sha512-I5UB/OWHLGoQW9IVld2yzZFYYiGYNBn2OYRlGbkfj5xDidT26yKYkV7Sr093zJtPh9zbofaLgyT89Ov5U0RRBQ==} + engines: {node: '>=22.12.0'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jspdf@4.2.1: + resolution: {integrity: sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lowdb@7.0.1: + resolution: {integrity: sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==} + engines: {node: '>=18'} + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + milliparsec@5.1.1: + resolution: {integrity: sha512-jkEDaSWZp4/Q3vprqdqukBqUEyNNqC1pwTjZ5cp9YkaR1wv5fvTCd8VFsecbw7i8DNBGjzhJ83MDoPZlcTaPQg==} + engines: {node: '>=18.13 || >=19.20 || >=20'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@4.1.0: + resolution: {integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==} + engines: {node: '>=16'} + hasBin: true + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + npm-normalize-package-bin@6.0.0: + resolution: {integrity: sha512-tdt4aFn9QamlhdN3HV2D2ccpBwO5/fyjjbXUxYA6uBjyekMZcZvDq0aSj9t5Jo+tih6AYFnt/cuIRn9013e0Uw==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + npm-run-all2@9.0.2: + resolution: {integrity: sha512-+dd4SO2jAlLE06OzmJKzIe6QvvjXezcbmobnh8usR0a8BzQCABTdqTXqVPji0ICOhSQpIIrkGd7IzNl5iDaRSA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0, npm: '>= 10'} + hasBin: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + obug@2.1.3: + resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + engines: {node: '>=12.20.0'} + + oxlint@1.73.0: + resolution: {integrity: sha512-u91G9TJzU6yqKWNZUYprQB07W7YvntZXaRxQ6CkoytepYhLWUXWsr1M8zUJ34VatNPuUAr3Z8GH+O2A331CluQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.24.0' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + + pako@2.2.0: + resolution: {integrity: sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==} + + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + pidtree@1.0.0: + resolution: {integrity: sha512-avfAvjB9Dd0wdj3rjJX//yS+G79OO0KrS5pJHFJENjYGX6N4SMgEDBBI/yFy0lloOYSaC6XQxzpOAMPfSYFV/Q==} + engines: {node: '>=18'} + hasBin: true + + postcss@8.5.18: + resolution: {integrity: sha512-xdB1oSLHbz1vRWgCDalrCqEFTWzFlhqFC5tIHLMOSUIjhm3XXQ1qrFy8S/ESr1JYRRXqM3c1QFiMZUJdUTqyMQ==} + engines: {node: ^10 || ^12 || >=14} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + raf@3.4.1: + resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react-hook-form@7.81.0: + resolution: {integrity: sha512-ocbmr2p5KBMoAfj4WCUvped33lVi1Kd5DuDUvQDnB6VEAacOjPI/jMbtDdbhco4y9ct4xUuCmMY0b/C9L0QHjw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + react-imask@7.6.1: + resolution: {integrity: sha512-vLNfzcCz62Yzx/GRGh5tiCph9Gbh2cZu+Tz8OiO5it2eNuuhpA0DWhhSlOtVtSJ80+Bx+vFK5De8eQ9AmbkXzA==} + engines: {npm: '>=4.0.0'} + peerDependencies: + react: '>=0.14.0' + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-router-dom@7.18.1: + resolution: {integrity: sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.18.1: + resolution: {integrity: sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + read-package-json-fast@6.0.0: + resolution: {integrity: sha512-PNaGjoCnw9DBA2Kl8D+8po957z778q/HOPuY2u3Bkw/JO3eC8MDx7jn/PgMtSgpcBbs+6UOjDbwReGpXmRvs0g==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + + regexparam@2.0.2: + resolution: {integrity: sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==} + engines: {node: '>=8'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + rgbcolor@1.0.1: + resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==} + engines: {node: '>= 0.8.15'} + + rolldown@1.1.5: + resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + sort-on@7.0.0: + resolution: {integrity: sha512-e+4RRxt7jsWdGPp4H5PKOER/ELYlemNB1plvW686Qi3j4WVaCjCpro2zaTD7Cn0VtBImq/hg3x1JfovMNXXfJQ==} + engines: {node: '>=20'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + stackblur-canvas@2.7.0: + resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==} + engines: {node: '>=0.1.14'} + + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + + steno@4.0.2: + resolution: {integrity: sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==} + engines: {node: '>=18'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + svg-pathdata@6.0.3: + resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==} + engines: {node: '>=12.0.0'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + text-segmentation@1.0.3: + resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.4.8: + resolution: {integrity: sha512-c1P7u0EhACHj7lPy4MJm8iTFEU8+nB0LCtddH0fhP7noaVoXAqafMtOOeX+ulpuPBqnrRgRhw494RICT3mbhnw==} + + tldts@7.4.8: + resolution: {integrity: sha512-htwgN/8KRB3z3vnC0BOETVh2m499g5GmyTK9Wq5JBLX3FNz6tSBveAd+fQhzy9hkjif8vy2jwDMR1sGhLtZl2A==} + hasBin: true + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-fest@5.8.0: + resolution: {integrity: sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==} + engines: {node: '>=20'} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} + + utrie@1.0.2: + resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + + vite@8.1.4: + resolution: {integrity: sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@7.0.0: + resolution: {integrity: sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@adobe/css-tools@4.5.0': {} + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.1.1': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/runtime-corejs3@7.29.7': + dependencies: + core-js-pure: 3.49.0 + + '@babel/runtime@7.29.7': {} + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.1.0': {} + + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.1.0 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.6(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@exodus/bytes@1.15.1': {} + + '@hookform/resolvers@5.4.0(react-hook-form@7.81.0(react@19.2.7))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.81.0(react@19.2.7) + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@oxc-project/types@0.139.0': {} + + '@oxlint/binding-android-arm-eabi@1.73.0': + optional: true + + '@oxlint/binding-android-arm64@1.73.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.73.0': + optional: true + + '@oxlint/binding-darwin-x64@1.73.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.73.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.73.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.73.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.73.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.73.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.73.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.73.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.73.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.73.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.73.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.73.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.73.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.73.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.73.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.73.0': + optional: true + + '@polka/url@1.0.0-next.29': {} + + '@rolldown/binding-android-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-x64@1.1.5': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.5': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.5': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.5': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.5': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.5': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.5': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.5.0 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@babel/runtime': 7.29.7 + '@testing-library/dom': 10.4.1 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@tinyhttp/accepts@2.3.0': + dependencies: + mime: 4.1.0 + + '@tinyhttp/app@3.0.7': + dependencies: + '@tinyhttp/accepts': 2.3.0 + '@tinyhttp/cookie': 2.1.1 + '@tinyhttp/proxy-addr': 3.0.1 + '@tinyhttp/req': 2.2.8 + '@tinyhttp/res': 2.2.11 + '@tinyhttp/router': 2.2.5 + regexparam: 2.0.2 + + '@tinyhttp/content-disposition@2.2.4': {} + + '@tinyhttp/content-type@0.1.4': {} + + '@tinyhttp/cookie-signature@2.1.1': {} + + '@tinyhttp/cookie@2.1.1': {} + + '@tinyhttp/cors@2.0.1': + dependencies: + '@tinyhttp/vary': 0.1.3 + + '@tinyhttp/encode-url@2.1.1': {} + + '@tinyhttp/etag@2.1.2': {} + + '@tinyhttp/forwarded@2.1.2': {} + + '@tinyhttp/logger@2.1.0': + dependencies: + colorette: 2.0.20 + dayjs: 1.11.21 + http-status-emojis: 2.2.0 + + '@tinyhttp/proxy-addr@3.0.1': + dependencies: + '@tinyhttp/forwarded': 2.1.2 + + '@tinyhttp/req@2.2.8': + dependencies: + '@tinyhttp/accepts': 2.3.0 + '@tinyhttp/type-is': 2.2.5 + '@tinyhttp/url': 2.1.1 + header-range-parser: 1.1.5 + + '@tinyhttp/res@2.2.11': + dependencies: + '@tinyhttp/content-disposition': 2.2.4 + '@tinyhttp/cookie': 2.1.1 + '@tinyhttp/cookie-signature': 2.1.1 + '@tinyhttp/encode-url': 2.1.1 + '@tinyhttp/req': 2.2.8 + '@tinyhttp/send': 2.2.5 + '@tinyhttp/vary': 0.1.3 + mime: 4.1.0 + + '@tinyhttp/router@2.2.5': + dependencies: + regexparam: 2.0.2 + + '@tinyhttp/send@2.2.5': + dependencies: + '@tinyhttp/content-type': 0.1.4 + '@tinyhttp/etag': 2.1.2 + mime: 4.1.0 + + '@tinyhttp/type-is@2.2.5': + dependencies: + '@tinyhttp/content-type': 0.1.4 + mime: 4.1.0 + + '@tinyhttp/url@2.1.1': {} + + '@tinyhttp/vary@0.1.3': {} + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/node@24.13.3': + dependencies: + undici-types: 7.18.2 + + '@types/pako@2.0.4': {} + + '@types/raf@3.4.3': + optional: true + + '@types/react-dom@19.2.3(@types/react@19.2.17)': + dependencies: + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@types/trusted-types@2.0.7': + optional: true + + '@vitejs/plugin-react@6.0.3(vite@8.1.4(@types/node@24.13.3))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.1.4(@types/node@24.13.3) + + '@vitest/coverage-v8@4.1.10(vitest@4.1.10)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.10 + ast-v8-to-istanbul: 1.0.4 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.3 + std-env: 4.2.0 + tinyrainbow: 3.1.0 + vitest: 4.1.10(@types/node@24.13.3)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1)(vite@8.1.4(@types/node@24.13.3)) + + '@vitest/expect@4.1.10': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.10(vite@8.1.4(@types/node@24.13.3))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.1.4(@types/node@24.13.3) + + '@vitest/pretty-format@4.1.10': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.10': + dependencies: + '@vitest/utils': 4.1.10 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} + + '@vitest/utils@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ansi-regex@5.0.1: {} + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.4: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + asynckit@0.4.0: {} + + axios@1.18.1: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + base64-arraybuffer@1.0.2: + optional: true + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + canvg@3.0.11: + dependencies: + '@babel/runtime': 7.29.7 + '@types/raf': 3.4.3 + core-js: 3.49.0 + raf: 3.4.1 + regenerator-runtime: 0.13.11 + rgbcolor: 1.0.1 + stackblur-canvas: 2.7.0 + svg-pathdata: 6.0.3 + optional: true + + chai@6.2.2: {} + + chalk@5.6.2: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + core-js-pure@3.49.0: {} + + core-js@3.49.0: + optional: true + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-line-break@2.1.0: + dependencies: + utrie: 1.0.2 + optional: true + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + + csstype@3.2.3: {} + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + dayjs@1.11.21: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dompurify@3.4.12: + optionalDependencies: + '@types/trusted-types': 2.0.7 + optional: true + + dot-prop@10.1.0: + dependencies: + type-fest: 5.8.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + entities@8.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.3.1: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + eta@4.6.0: {} + + expect-type@1.4.0: {} + + fast-png@6.4.0: + dependencies: + '@types/pako': 2.0.4 + iobuffer: 5.4.0 + pako: 2.2.0 + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + fflate@0.8.3: {} + + follow-redirects@1.16.0: {} + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + header-range-parser@1.1.5: {} + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' + + html-escaper@2.0.2: {} + + html2canvas@1.4.1: + dependencies: + css-line-break: 2.1.0 + text-segmentation: 1.0.3 + optional: true + + http-status-emojis@2.2.0: {} + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + imask@7.6.1: + dependencies: + '@babel/runtime-corejs3': 7.29.7 + + indent-string@4.0.0: {} + + inflection@3.0.2: {} + + iobuffer@5.4.0: {} + + is-potential-custom-element-name@1.0.1: {} + + isexe@2.0.0: {} + + isexe@4.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + jsdom@29.1.1: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.6(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + undici: 7.28.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + + json-parse-even-better-errors@6.0.0: {} + + json-server@1.0.0-beta.15: + dependencies: + '@tinyhttp/app': 3.0.7 + '@tinyhttp/cors': 2.0.1 + '@tinyhttp/logger': 2.1.0 + chalk: 5.6.2 + chokidar: 5.0.0 + dot-prop: 10.1.0 + eta: 4.6.0 + inflection: 3.0.2 + json5: 2.2.3 + lowdb: 7.0.1 + milliparsec: 5.1.1 + sirv: 3.0.2 + sort-on: 7.0.0 + + json5@2.2.3: {} + + jspdf@4.2.1: + dependencies: + '@babel/runtime': 7.29.7 + fast-png: 6.4.0 + fflate: 0.8.3 + optionalDependencies: + canvg: 3.0.11 + core-js: 3.49.0 + dompurify: 3.4.12 + html2canvas: 1.4.1 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lowdb@7.0.1: + dependencies: + steno: 4.0.2 + + lru-cache@11.5.2: {} + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.3: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + + math-intrinsics@1.1.0: {} + + mdn-data@2.27.1: {} + + memorystream@0.3.1: {} + + milliparsec@5.1.1: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@4.1.0: {} + + min-indent@1.0.1: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.16: {} + + npm-normalize-package-bin@6.0.0: {} + + npm-run-all2@9.0.2: + dependencies: + ansi-styles: 6.2.3 + cross-spawn: 7.0.6 + memorystream: 0.3.1 + picomatch: 4.0.5 + pidtree: 1.0.0 + read-package-json-fast: 6.0.0 + shell-quote: 1.10.0 + which: 7.0.0 + + object-assign@4.1.1: {} + + obug@2.1.3: {} + + oxlint@1.73.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.73.0 + '@oxlint/binding-android-arm64': 1.73.0 + '@oxlint/binding-darwin-arm64': 1.73.0 + '@oxlint/binding-darwin-x64': 1.73.0 + '@oxlint/binding-freebsd-x64': 1.73.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.73.0 + '@oxlint/binding-linux-arm-musleabihf': 1.73.0 + '@oxlint/binding-linux-arm64-gnu': 1.73.0 + '@oxlint/binding-linux-arm64-musl': 1.73.0 + '@oxlint/binding-linux-ppc64-gnu': 1.73.0 + '@oxlint/binding-linux-riscv64-gnu': 1.73.0 + '@oxlint/binding-linux-riscv64-musl': 1.73.0 + '@oxlint/binding-linux-s390x-gnu': 1.73.0 + '@oxlint/binding-linux-x64-gnu': 1.73.0 + '@oxlint/binding-linux-x64-musl': 1.73.0 + '@oxlint/binding-openharmony-arm64': 1.73.0 + '@oxlint/binding-win32-arm64-msvc': 1.73.0 + '@oxlint/binding-win32-ia32-msvc': 1.73.0 + '@oxlint/binding-win32-x64-msvc': 1.73.0 + + pako@2.2.0: {} + + parse5@8.0.1: + dependencies: + entities: 8.0.0 + + path-key@3.1.1: {} + + pathe@2.0.3: {} + + performance-now@2.1.0: + optional: true + + picocolors@1.1.1: {} + + picomatch@4.0.5: {} + + pidtree@1.0.0: {} + + postcss@8.5.18: + dependencies: + nanoid: 3.3.16 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-from-env@2.1.0: {} + + punycode@2.3.1: {} + + raf@3.4.1: + dependencies: + performance-now: 2.1.0 + optional: true + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react-hook-form@7.81.0(react@19.2.7): + dependencies: + react: 19.2.7 + + react-imask@7.6.1(react@19.2.7): + dependencies: + imask: 7.6.1 + prop-types: 15.8.1 + react: 19.2.7 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-router-dom@7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-router: 7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + + react-router@7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + cookie: 1.1.1 + react: 19.2.7 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.7(react@19.2.7) + + react@19.2.7: {} + + read-package-json-fast@6.0.0: + dependencies: + json-parse-even-better-errors: 6.0.0 + npm-normalize-package-bin: 6.0.0 + + readdirp@5.0.0: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + regenerator-runtime@0.13.11: + optional: true + + regexparam@2.0.2: {} + + require-from-string@2.0.2: {} + + rgbcolor@1.0.1: + optional: true + + rolldown@1.1.5: + dependencies: + '@oxc-project/types': 0.139.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.5 + '@rolldown/binding-darwin-arm64': 1.1.5 + '@rolldown/binding-darwin-x64': 1.1.5 + '@rolldown/binding-freebsd-x64': 1.1.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 + '@rolldown/binding-linux-arm64-gnu': 1.1.5 + '@rolldown/binding-linux-arm64-musl': 1.1.5 + '@rolldown/binding-linux-ppc64-gnu': 1.1.5 + '@rolldown/binding-linux-s390x-gnu': 1.1.5 + '@rolldown/binding-linux-x64-gnu': 1.1.5 + '@rolldown/binding-linux-x64-musl': 1.1.5 + '@rolldown/binding-openharmony-arm64': 1.1.5 + '@rolldown/binding-wasm32-wasi': 1.1.5 + '@rolldown/binding-win32-arm64-msvc': 1.1.5 + '@rolldown/binding-win32-x64-msvc': 1.1.5 + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + semver@7.8.5: {} + + set-cookie-parser@2.7.2: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.10.0: {} + + siginfo@2.0.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + sort-on@7.0.0: + dependencies: + dot-prop: 10.1.0 + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + stackblur-canvas@2.7.0: + optional: true + + std-env@4.2.0: {} + + steno@4.0.2: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + svg-pathdata@6.0.3: + optional: true + + symbol-tree@3.2.4: {} + + tagged-tag@1.0.0: {} + + text-segmentation@1.0.3: + dependencies: + utrie: 1.0.2 + optional: true + + tinybench@2.9.0: {} + + tinyexec@1.2.4: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tinyrainbow@3.1.0: {} + + tldts-core@7.4.8: {} + + tldts@7.4.8: + dependencies: + tldts-core: 7.4.8 + + totalist@3.0.1: {} + + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.8 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + tslib@2.8.1: + optional: true + + type-fest@5.8.0: + dependencies: + tagged-tag: 1.0.0 + + typescript@6.0.3: {} + + undici-types@7.18.2: {} + + undici@7.28.0: {} + + utrie@1.0.2: + dependencies: + base64-arraybuffer: 1.0.2 + optional: true + + vite@8.1.4(@types/node@24.13.3): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.5 + postcss: 8.5.18 + rolldown: 1.1.5 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.3 + fsevents: 2.3.3 + + vitest@4.1.10(@types/node@24.13.3)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1)(vite@8.1.4(@types/node@24.13.3)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.1.4(@types/node@24.13.3)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.3 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.1.4(@types/node@24.13.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.13.3 + '@vitest/coverage-v8': 4.1.10(vitest@4.1.10) + jsdom: 29.1.1 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@7.0.0: + dependencies: + isexe: 4.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + zod@4.4.3: {} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/logo.avif b/frontend/public/logo.avif new file mode 100644 index 0000000000000000000000000000000000000000..9d99f29bbf18e151f1431f336008632dc542ece2 GIT binary patch literal 3464 zcmYjSc|4Tu_kJd_X9*$uE=9;PmKeM2CRs-rgG|kgF_!GcHe_ES+j#9ugh8q7O9@3P z`@UoeQIQby8@-?R^ZTCXbKloF=eq85&i(xHJOBU)x%&iQ;eJSWfMTbJM!L(Rk#Ki2 zZFwC4041W`eBh@X#iTr3QMi9y0Kg&8Kx;yY|3;u^06-l}@pX_$%->1=G8ZX<+o|OA1l&(fUe^=l`FDW-XDIY3 zqm9CPz)zJIkvO-1D*ym9+}9uVx94fsZX_0mr63WBMR-z>1L=)*qu{BQ-#4*DN4->Dc(X{~$e_~I~X zXsp}aznhL40*Sz?c?Wp=xS_q(C>kUR3C9Jf!T%dy0EtEay$9tekiaqVz(2K)>wcgqsV_NKMe-ne^$T z72Ay%hHxt$a_dck5KDQ*^~*sUd3`U_kMxtiDk(^T51gk+Q#UlIdUYz$Gr7-(tQO`6 zNxRYVrXegzBD&}BFSlrq)|eakvv@+e`1I?*`TgJeZly^66E|F^g5D{(Q$H~|C1ikC zGpVU2;(x}lT6oJvMK@mW#YqY&mov|eQ$A=HBn$Z9vVQs>U`8t)C7T*Zdjjeh4y zgSpF0c`$@X}IBJi!E+0VkfhySA~2}TPTo98svQyItjrQX-2aj{3&<7 zC*g6Z!TyuOR(RNVBtUken8E%1$* z>oZ-JJtu2sKjT4DLF(GP?5tOwkt`VVSbt1zQUh98bZKqt-c9PRib;*H5tmBVQ8FJ| z6te~710|DO9GMi>rzfnw36CPRrOHfdQ3O7H%aIjtf!}1mPTBP@Sv0&R@@4_DhI+P; zsF8()l;Lsq%>2J$FBPGtB{MerLg z`RXk1^N}xgW>Xl5MZ;jnj|canPn1Tg)rO6J==tpxN?LJNP?a@Bera)6?k2Y{`*8S4 z@+L}-j+jWb)a=$ex`uCx1gHkp3AfW14oDf4JgzFW>$sb0EIhno825&dBkhRX?$?wt zN+}E4^?$ORJbwSF;)i$Vh)q5A7!-+oWPyYj;olaaAE}i-h0QuRv-mnlIuQCCpL2#Qd{1m zv!m?&g{3Q!FTsN2J@t{uuGDd*s3lo9OCp-Sm;QBNA&%Ow6;1UHKTWi!4F%_ zN0e2_O2z>J5=d|y3-jlzpK=wKIebj$Kk_Uhi2*4Zj3_xP`$xGIq7X#oZB}vX_TZDq zuSz5tMD`GhZ+`(y;4Qyd!Is#@<1`{m8aI%Tu=BX2V9A63U8xHmP)&Jl(SCS6 zujq+;2OCl}(k=s}Q<|n(IfeJH&emSl`J7?d*t3>XS%KfW9A~GD*1YkibZkT!>c!B! z!2C}N$bz#8awhEV>npC6M}5LRgc%RT6!Q$f_qVVQ=^mRxFlR<#tW57gqjn}2xbk3} zCFnf!HO6ad#SB_+31S8==f2H+D`^zMvTI?h#dCPTB>i4^aDN5Z4&mTILv?nTK5T`D z>7{DGly7$~KB_9#DQzXUS}qXoO{cfBv`|^Xj?1bxOIpBj*s<9q*}Ex z&|YI0;x&tl{I(&2`Qs~@%t z{k1u^lAr3%K{`ujxS4lE=xzKOCl0UOO($c?{L4|Biao&z)#<8x1fF=G4%2417+BG+Z zHh*NiZ4uRQ@8-q&2z>aXaF}PKlXfj;vqGUuvtvH@>z`P*Toq3YxSpW%NQ5I|J>R@i zWTSM*`(BVqsW#-@ynTXv>e*YN)k#Mda-^MQB^{VOe7~)k)>H~ew!nT@FZ`4fTVu1# zq`uz9lu^5EUHVJxfkbj$CwPKr2EEDw5pd9uaJK#J@Y^zJoiU_othWDAqVl4a8#DS( zUFw^BsCZhKe<<^br6FUmQmX14xBi-xPjl^BQZ)FWMVOE0SB+lITA53Z#3W0cXvGDs zuS3ym)H(!XgE@CGzL)DOS@4i}%>$yIcK-W!E&M>V=F4fo`9fKHtgF`RgJ>v2qHJlzr*G)O!uN(Pmk35@{fP zV%zB(RLp!<@@7R)SLx@7&$v*UsMv9phYWTd&ul44gY)WyddMGklQky{gR9{5fuPr^rTZY)V|wKoZo^$~ z!43J}^t?py@$-RJjXo=m?nIZlAZlh@!Rspg@HG`;TZR&Agqz-U<8i4`F1w33<$f5uIg8rL{C)j#xJ85(Efs`oPFbd~gEYmh7wUIOE8+JK%n zy0=ai+b|iJpBQIM^<+8`R^BIF&%)_N887_!?F(U-70pXSzY&;V?N@pZ><^QHOI-v>NpX za`3~R8VoYV@%Ti!_#}RGP~Zb!!z;dohDW2Sn1eozx#r#rcd8U!;RZtmyWv(#ObJ`( z=1r`Rr8tV&Dte17-^uDn{e3ep0sqhBNm~vk2Nt7DeIE1y+y^23vy&%=6h< zv?YN#mIzT_LrJ;Qv ({ + fetchAddressByCep: vi.fn(), + fetchProfessions: vi.fn(), +})) +vi.mock('./lib/pdf', () => ({ generateSummaryPdf: vi.fn() })) + +beforeEach(() => { + vi.mocked(fetchAddressByCep).mockReset() + vi.mocked(fetchProfessions).mockReset() + vi.mocked(generateSummaryPdf).mockReset() + window.localStorage.clear() + window.history.pushState({}, '', '/') +}) + +describe('App', () => { + it('completes the full wizard flow end-to-end and exports the PDF', async () => { + vi.mocked(fetchAddressByCep).mockResolvedValue({ + endereco: 'Avenida Paulista', + bairro: 'Bela Vista', + cidade: 'São Paulo', + estado: 'SP', + }) + vi.mocked(fetchProfessions).mockResolvedValue([{ id: 1, name: 'Desenvolvedor(a) de Software' }]) + + const user = userEvent.setup() + render() + + expect(await screen.findByRole('heading', { name: 'Dados Pessoais' })).toBeInTheDocument() + + await user.type(screen.getByLabelText('Nome completo'), 'Maria Souza') + await user.type(screen.getByLabelText('Data de nascimento'), '15051990') + await user.type(screen.getByLabelText('CPF'), '52998224725') + await user.type(screen.getByLabelText('Telefone'), '11987654321') + await user.type(screen.getByLabelText('E-mail'), 'maria@example.com') + await user.click(screen.getByText('Próximo')) + + expect(await screen.findByText('Informações Residenciais')).toBeInTheDocument() + await user.type(screen.getByLabelText('CEP'), '01310100') + await screen.findByDisplayValue('Avenida Paulista') + await user.click(screen.getByText('Próximo')) + + expect(await screen.findByText('Informações Profissionais')).toBeInTheDocument() + await screen.findByRole('option', { name: 'Desenvolvedor(a) de Software' }) + await user.type(screen.getByLabelText('Empresa'), 'Verity Tecnologia') + await user.selectOptions(screen.getByLabelText('Profissão'), 'Desenvolvedor(a) de Software') + await user.type(screen.getByLabelText('Salário'), '850000') + await user.selectOptions(screen.getByLabelText('Tempo de empresa'), 'De 2 a 5 anos') + await user.click(screen.getByText('Ver resumo')) + + expect(await screen.findByText('Resumo do Cadastro')).toBeInTheDocument() + expect(screen.getByText('Maria Souza')).toBeInTheDocument() + + await user.click(screen.getByText('Exportar PDF')) + expect(generateSummaryPdf).toHaveBeenCalledOnce() + }) + + it('redirects unknown routes to step 1', async () => { + window.history.pushState({}, '', '/rota-invalida') + render() + expect(await screen.findByRole('heading', { name: 'Dados Pessoais' })).toBeInTheDocument() + }) +}) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..7ba0cbb --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,28 @@ +import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom' +import { Layout } from './components/layout/Layout' +import { WizardFormProvider } from './context/WizardFormProvider' +import { Step1PersonalData } from './pages/Step1PersonalData' +import { Step2Address } from './pages/Step2Address' +import { Step3Professional } from './pages/Step3Professional' +import { SummaryPage } from './pages/SummaryPage' + +function App() { + return ( + + + + + } /> + } /> + } /> + } /> + } /> + } /> + + + + + ) +} + +export default App diff --git a/frontend/src/components/form/CurrencyField.test.tsx b/frontend/src/components/form/CurrencyField.test.tsx new file mode 100644 index 0000000..d2c431b --- /dev/null +++ b/frontend/src/components/form/CurrencyField.test.tsx @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest' +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { renderWithForm } from '../../test/renderWithForm' +import { CurrencyField } from './CurrencyField' + +describe('CurrencyField', () => { + it('formats typed digits as BRL currency', async () => { + const user = userEvent.setup() + renderWithForm() + + const input = screen.getByLabelText('Salário') + await user.type(input, '850050') + + expect(input).toHaveValue('R$ 8.500,50') + }) + + it('removes the last typed digit on backspace', async () => { + const user = userEvent.setup() + renderWithForm() + + const input = screen.getByLabelText('Salário') + await user.type(input, '1000') + expect(input).toHaveValue('R$ 10,00') + + await user.type(input, '{backspace}') + + expect(input).toHaveValue('R$ 1,00') + }) + + it('clears back to empty once every digit is removed', async () => { + const user = userEvent.setup() + renderWithForm() + + const input = screen.getByLabelText('Salário') + await user.type(input, '5') + expect(input).toHaveValue('R$ 0,05') + + await user.type(input, '{backspace}') + + expect(input).toHaveValue('') + }) +}) diff --git a/frontend/src/components/form/CurrencyField.tsx b/frontend/src/components/form/CurrencyField.tsx new file mode 100644 index 0000000..5fa69f1 --- /dev/null +++ b/frontend/src/components/form/CurrencyField.tsx @@ -0,0 +1,59 @@ +import { Controller, useFormContext } from 'react-hook-form' +import type { FormValues } from '../../schemas/formSchema' +import { onlyDigits } from '../../lib/masks' + +const NON_BREAKING_SPACE = ' ' + +function formatBRLFromDigits(digits: string): string { + const numeric = Number(digits || '0') / 100 + // toLocaleString inserts a non-breaking space after "R$"; normalize to a regular space. + return numeric + .toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }) + .replaceAll(NON_BREAKING_SPACE, ' ') +} + +interface CurrencyFieldProps { + name: keyof FormValues + label: string + fullWidth?: boolean +} + +export function CurrencyField({ name, label, fullWidth }: Readonly) { + const { + control, + formState: { errors }, + } = useFormContext() + const error = errors[name]?.message as string | undefined + + return ( +
+ + ( + { + // Strip leading zeros so backspacing down to nothing actually clears the + // field instead of getting stuck re-formatting to "R$ 0,00" forever. + const digits = onlyDigits(event.target.value).replace(/^0+/, '').slice(0, 12) + field.onChange(digits ? formatBRLFromDigits(digits) : '') + }} + onBlur={field.onBlur} + /> + )} + /> + {error && ( + + {error} + + )} +
+ ) +} diff --git a/frontend/src/components/form/MaskedTextField.test.tsx b/frontend/src/components/form/MaskedTextField.test.tsx new file mode 100644 index 0000000..0625150 --- /dev/null +++ b/frontend/src/components/form/MaskedTextField.test.tsx @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest' +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { renderWithForm } from '../../test/renderWithForm' +import { MaskedTextField } from './MaskedTextField' +import { CPF_MASK, PHONE_MASK } from '../../constants/masks' + +describe('MaskedTextField', () => { + it('applies a fixed pattern mask as the user types', async () => { + const user = userEvent.setup() + renderWithForm() + + const input = screen.getByLabelText('CPF') + await user.type(input, '52998224725') + + expect(input).toHaveValue('529.982.247-25') + }) + + it('picks the right mask variant for a dynamic pattern', async () => { + const user = userEvent.setup() + renderWithForm() + + const input = screen.getByLabelText('Telefone') + await user.type(input, '11987654321') + + expect(input).toHaveValue('(11) 98765-4321') + }) +}) diff --git a/frontend/src/components/form/MaskedTextField.tsx b/frontend/src/components/form/MaskedTextField.tsx new file mode 100644 index 0000000..50dec08 --- /dev/null +++ b/frontend/src/components/form/MaskedTextField.tsx @@ -0,0 +1,60 @@ +import { Controller, useFormContext } from 'react-hook-form' +import { IMaskInput } from 'react-imask' +import type { FormValues } from '../../schemas/formSchema' + +interface MaskedTextFieldProps { + name: keyof FormValues + label: string + maskOptions: Record + placeholder?: string + inputMode?: 'text' | 'numeric' | 'decimal' + hint?: string + fullWidth?: boolean +} + +export function MaskedTextField({ + name, + label, + maskOptions, + placeholder, + inputMode, + hint, + fullWidth, +}: Readonly) { + const { + control, + formState: { errors }, + } = useFormContext() + const error = errors[name]?.message as string | undefined + + return ( +
+ + ( + field.onChange(value)} + onBlur={field.onBlur} + /> + )} + /> + {error && ( + + {error} + + )} + {!error && hint && {hint}} +
+ ) +} diff --git a/frontend/src/components/form/SelectField.test.tsx b/frontend/src/components/form/SelectField.test.tsx new file mode 100644 index 0000000..3393ff8 --- /dev/null +++ b/frontend/src/components/form/SelectField.test.tsx @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest' +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { renderWithForm } from '../../test/renderWithForm' +import { SelectField } from './SelectField' + +const options = [ + { value: 'SP', label: 'São Paulo' }, + { value: 'RJ', label: 'Rio de Janeiro' }, +] + +describe('SelectField', () => { + it('renders the placeholder plus every option', () => { + renderWithForm() + + expect(screen.getByRole('option', { name: 'UF' })).toBeInTheDocument() + expect(screen.getByRole('option', { name: 'São Paulo' })).toBeInTheDocument() + expect(screen.getByRole('option', { name: 'Rio de Janeiro' })).toBeInTheDocument() + }) + + it('lets the user pick an option', async () => { + const user = userEvent.setup() + renderWithForm() + + await user.selectOptions(screen.getByLabelText('Estado'), 'RJ') + + expect(screen.getByLabelText('Estado')).toHaveValue('RJ') + }) + + it('can be disabled while options are loading', () => { + renderWithForm() + expect(screen.getByLabelText('Profissão')).toBeDisabled() + }) +}) diff --git a/frontend/src/components/form/SelectField.tsx b/frontend/src/components/form/SelectField.tsx new file mode 100644 index 0000000..3bc11c6 --- /dev/null +++ b/frontend/src/components/form/SelectField.tsx @@ -0,0 +1,48 @@ +import { useFormContext } from 'react-hook-form' +import type { FormValues } from '../../schemas/formSchema' +import type { SelectOption } from '../../constants/options' + +interface SelectFieldProps { + name: keyof FormValues + label: string + options: SelectOption[] + placeholder?: string + disabled?: boolean + fullWidth?: boolean +} + +export function SelectField({ name, label, options, placeholder, disabled, fullWidth }: Readonly) { + const { + register, + formState: { errors }, + } = useFormContext() + const error = errors[name]?.message as string | undefined + + return ( +
+ + + {error && ( + + {error} + + )} +
+ ) +} diff --git a/frontend/src/components/form/TextField.test.tsx b/frontend/src/components/form/TextField.test.tsx new file mode 100644 index 0000000..23bd939 --- /dev/null +++ b/frontend/src/components/form/TextField.test.tsx @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest' +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { renderWithForm } from '../../test/renderWithForm' +import { TextField } from './TextField' + +describe('TextField', () => { + it('renders a label wired to the input', () => { + renderWithForm() + expect(screen.getByLabelText('Nome completo')).toBeInTheDocument() + }) + + it('lets the user type into the field', async () => { + const user = userEvent.setup() + renderWithForm() + + const input = screen.getByLabelText('Nome completo') + await user.type(input, 'Maria Souza') + + expect(input).toHaveValue('Maria Souza') + }) +}) diff --git a/frontend/src/components/form/TextField.tsx b/frontend/src/components/form/TextField.tsx new file mode 100644 index 0000000..fc345a3 --- /dev/null +++ b/frontend/src/components/form/TextField.tsx @@ -0,0 +1,49 @@ +import { useFormContext } from 'react-hook-form' +import type { FormValues } from '../../schemas/formSchema' + +interface TextFieldProps { + name: keyof FormValues + label: string + placeholder?: string + type?: string + autoComplete?: string + fullWidth?: boolean + readOnly?: boolean +} + +export function TextField({ + name, + label, + placeholder, + type = 'text', + autoComplete, + fullWidth, + readOnly, +}: Readonly) { + const { + register, + formState: { errors }, + } = useFormContext() + const error = errors[name]?.message as string | undefined + + return ( +
+ + + {error && ( + + {error} + + )} +
+ ) +} diff --git a/frontend/src/components/layout/Layout.test.tsx b/frontend/src/components/layout/Layout.test.tsx new file mode 100644 index 0000000..c29adb2 --- /dev/null +++ b/frontend/src/components/layout/Layout.test.tsx @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest' +import { render, screen } from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' +import { Layout } from './Layout' + +describe('Layout', () => { + it('renders the logo, step indicator and page content', () => { + render( + + +

conteúdo da etapa

+
+
, + ) + + expect(screen.getByAltText('Verity')).toBeInTheDocument() + expect(screen.getByText('Dados Pessoais')).toBeInTheDocument() + expect(screen.getByText('conteúdo da etapa')).toBeInTheDocument() + expect(screen.getByText(/Verity\. Todos os direitos reservados\./)).toBeInTheDocument() + }) +}) diff --git a/frontend/src/components/layout/Layout.tsx b/frontend/src/components/layout/Layout.tsx new file mode 100644 index 0000000..273636c --- /dev/null +++ b/frontend/src/components/layout/Layout.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react' +import { Logo } from './Logo' +import { StepIndicator } from '../wizard/StepIndicator' + +export function Layout({ children }: { children: ReactNode }) { + return ( +
+
+ +
+ +
+
{children}
+
+
+

© {new Date().getFullYear()} Verity. Todos os direitos reservados.

+
+
+ ) +} diff --git a/frontend/src/components/layout/Logo.test.tsx b/frontend/src/components/layout/Logo.test.tsx new file mode 100644 index 0000000..17737b3 --- /dev/null +++ b/frontend/src/components/layout/Logo.test.tsx @@ -0,0 +1,12 @@ +import { describe, expect, it } from 'vitest' +import { render, screen } from '@testing-library/react' +import { Logo } from './Logo' + +describe('Logo', () => { + it('renders the Verity logo image', () => { + render() + const image = screen.getByAltText('Verity') + expect(image).toBeInTheDocument() + expect(image).toHaveAttribute('src', '/logo.avif') + }) +}) diff --git a/frontend/src/components/layout/Logo.tsx b/frontend/src/components/layout/Logo.tsx new file mode 100644 index 0000000..e2196dd --- /dev/null +++ b/frontend/src/components/layout/Logo.tsx @@ -0,0 +1,7 @@ +export function Logo() { + return ( +
+ Verity +
+ ) +} diff --git a/frontend/src/components/wizard/StepIndicator.test.tsx b/frontend/src/components/wizard/StepIndicator.test.tsx new file mode 100644 index 0000000..f9c9cfa --- /dev/null +++ b/frontend/src/components/wizard/StepIndicator.test.tsx @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest' +import { render, screen } from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' +import { StepIndicator } from './StepIndicator' + +describe('StepIndicator', () => { + it('marks the current step as active and earlier steps as done', () => { + render( + + + , + ) + + expect(screen.getByText('Endereço').closest('li')).toHaveClass('step-active') + expect(screen.getByText('Dados Pessoais').closest('li')).toHaveClass('step-done') + expect(screen.getByText('Profissional').closest('li')).toHaveClass('step-pending') + }) + + it('renders every step label', () => { + render( + + + , + ) + + expect(screen.getByText('Resumo').closest('li')).toHaveClass('step-active') + }) +}) diff --git a/frontend/src/components/wizard/StepIndicator.tsx b/frontend/src/components/wizard/StepIndicator.tsx new file mode 100644 index 0000000..5f30497 --- /dev/null +++ b/frontend/src/components/wizard/StepIndicator.tsx @@ -0,0 +1,27 @@ +import { useLocation } from 'react-router-dom' + +const STEPS = [ + { path: '/etapa-1', label: 'Dados Pessoais' }, + { path: '/etapa-2', label: 'Endereço' }, + { path: '/etapa-3', label: 'Profissional' }, + { path: '/resumo', label: 'Resumo' }, +] + +export function StepIndicator() { + const { pathname } = useLocation() + const currentIndex = STEPS.findIndex((step) => step.path === pathname) + + return ( +
    + {STEPS.map((step, index) => { + const status = index < currentIndex ? 'done' : index === currentIndex ? 'active' : 'pending' + return ( +
  1. + {status === 'done' ? '✓' : index + 1} + {step.label} +
  2. + ) + })} +
+ ) +} diff --git a/frontend/src/components/wizard/SummarySection.test.tsx b/frontend/src/components/wizard/SummarySection.test.tsx new file mode 100644 index 0000000..1a5e473 --- /dev/null +++ b/frontend/src/components/wizard/SummarySection.test.tsx @@ -0,0 +1,34 @@ +import { describe, expect, it, vi } from 'vitest' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { SummarySection } from './SummarySection' + +describe('SummarySection', () => { + it('renders every item label and value', () => { + render( + {}} + items={[ + { label: 'Nome completo', value: 'Maria Souza' }, + { label: 'E-mail', value: '' }, + ]} + />, + ) + + expect(screen.getByText('Dados Pessoais')).toBeInTheDocument() + expect(screen.getByText('Nome completo')).toBeInTheDocument() + expect(screen.getByText('Maria Souza')).toBeInTheDocument() + expect(screen.getByText('—')).toBeInTheDocument() + }) + + it('calls onEdit when the edit button is clicked', async () => { + const user = userEvent.setup() + const onEdit = vi.fn() + render() + + await user.click(screen.getByText('Editar')) + + expect(onEdit).toHaveBeenCalledOnce() + }) +}) diff --git a/frontend/src/components/wizard/SummarySection.tsx b/frontend/src/components/wizard/SummarySection.tsx new file mode 100644 index 0000000..a765e3c --- /dev/null +++ b/frontend/src/components/wizard/SummarySection.tsx @@ -0,0 +1,31 @@ +interface SummaryItem { + label: string + value: string +} + +interface SummarySectionProps { + title: string + items: SummaryItem[] + onEdit: () => void +} + +export function SummarySection({ title, items, onEdit }: Readonly) { + return ( +
+
+

{title}

+ +
+
+ {items.map((item) => ( +
+
{item.label}
+
{item.value || '—'}
+
+ ))} +
+
+ ) +} diff --git a/frontend/src/components/wizard/WizardActions.test.tsx b/frontend/src/components/wizard/WizardActions.test.tsx new file mode 100644 index 0000000..3d7af68 --- /dev/null +++ b/frontend/src/components/wizard/WizardActions.test.tsx @@ -0,0 +1,25 @@ +import { describe, expect, it, vi } from 'vitest' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { WizardActions } from './WizardActions' + +describe('WizardActions', () => { + it('omits the back button when onBack is not provided', () => { + render( {}} />) + expect(screen.queryByText('Voltar')).not.toBeInTheDocument() + expect(screen.getByText('Próximo')).toBeInTheDocument() + }) + + it('calls the provided callbacks', async () => { + const user = userEvent.setup() + const onNext = vi.fn() + const onBack = vi.fn() + render() + + await user.click(screen.getByText('Voltar')) + await user.click(screen.getByText('Ver resumo')) + + expect(onBack).toHaveBeenCalledOnce() + expect(onNext).toHaveBeenCalledOnce() + }) +}) diff --git a/frontend/src/components/wizard/WizardActions.tsx b/frontend/src/components/wizard/WizardActions.tsx new file mode 100644 index 0000000..6f7af0b --- /dev/null +++ b/frontend/src/components/wizard/WizardActions.tsx @@ -0,0 +1,21 @@ +interface WizardActionsProps { + onBack?: () => void + onNext: () => void + nextLabel?: string + backLabel?: string +} + +export function WizardActions({ onBack, onNext, nextLabel = 'Próximo', backLabel = 'Voltar' }: Readonly) { + return ( +
+ {onBack && ( + + )} + +
+ ) +} diff --git a/frontend/src/components/wizard/WizardStepShell.test.tsx b/frontend/src/components/wizard/WizardStepShell.test.tsx new file mode 100644 index 0000000..3928fd1 --- /dev/null +++ b/frontend/src/components/wizard/WizardStepShell.test.tsx @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest' +import { render, screen } from '@testing-library/react' +import { WizardStepShell } from './WizardStepShell' + +describe('WizardStepShell', () => { + it('renders the title, optional description and children', () => { + render( + +

conteúdo

+
, + ) + + expect(screen.getByRole('heading', { name: 'Dados Pessoais' })).toBeInTheDocument() + expect(screen.getByText('Preencha seus dados')).toBeInTheDocument() + expect(screen.getByText('conteúdo')).toBeInTheDocument() + }) + + it('omits the description when not provided', () => { + render( + +

conteúdo

+
, + ) + + expect(screen.getByRole('heading', { name: 'Resumo' })).toBeInTheDocument() + }) +}) diff --git a/frontend/src/components/wizard/WizardStepShell.tsx b/frontend/src/components/wizard/WizardStepShell.tsx new file mode 100644 index 0000000..d8504c8 --- /dev/null +++ b/frontend/src/components/wizard/WizardStepShell.tsx @@ -0,0 +1,17 @@ +import type { ReactNode } from 'react' + +interface WizardStepShellProps { + title: string + description?: string + children: ReactNode +} + +export function WizardStepShell({ title, description, children }: Readonly) { + return ( +
+

{title}

+ {description &&

{description}

} + {children} +
+ ) +} diff --git a/frontend/src/constants/masks.ts b/frontend/src/constants/masks.ts new file mode 100644 index 0000000..d904a11 --- /dev/null +++ b/frontend/src/constants/masks.ts @@ -0,0 +1,9 @@ +export const CPF_MASK = { mask: '000.000.000-00' } + +export const DATE_MASK = { mask: '00/00/0000' } + +export const CEP_MASK = { mask: '00000-000' } + +export const PHONE_MASK = { + mask: [{ mask: '(00) 0000-0000' }, { mask: '(00) 00000-0000' }], +} diff --git a/frontend/src/constants/options.test.ts b/frontend/src/constants/options.test.ts new file mode 100644 index 0000000..5c1ba0a --- /dev/null +++ b/frontend/src/constants/options.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'vitest' +import { getOptionLabel, TEMPO_EMPRESA_OPTIONS, UF_OPTIONS } from './options' + +describe('getOptionLabel', () => { + it('returns the matching label', () => { + expect(getOptionLabel(UF_OPTIONS, 'SP')).toBe('São Paulo') + expect(getOptionLabel(TEMPO_EMPRESA_OPTIONS, '2-5-anos')).toBe('De 2 a 5 anos') + }) + + it('falls back to the raw value when there is no match', () => { + expect(getOptionLabel(UF_OPTIONS, 'ZZ')).toBe('ZZ') + }) +}) diff --git a/frontend/src/constants/options.ts b/frontend/src/constants/options.ts new file mode 100644 index 0000000..79682b1 --- /dev/null +++ b/frontend/src/constants/options.ts @@ -0,0 +1,46 @@ +export interface SelectOption { + value: string + label: string +} + +export const UF_OPTIONS: SelectOption[] = [ + { value: 'AC', label: 'Acre' }, + { value: 'AL', label: 'Alagoas' }, + { value: 'AP', label: 'Amapá' }, + { value: 'AM', label: 'Amazonas' }, + { value: 'BA', label: 'Bahia' }, + { value: 'CE', label: 'Ceará' }, + { value: 'DF', label: 'Distrito Federal' }, + { value: 'ES', label: 'Espírito Santo' }, + { value: 'GO', label: 'Goiás' }, + { value: 'MA', label: 'Maranhão' }, + { value: 'MT', label: 'Mato Grosso' }, + { value: 'MS', label: 'Mato Grosso do Sul' }, + { value: 'MG', label: 'Minas Gerais' }, + { value: 'PA', label: 'Pará' }, + { value: 'PB', label: 'Paraíba' }, + { value: 'PR', label: 'Paraná' }, + { value: 'PE', label: 'Pernambuco' }, + { value: 'PI', label: 'Piauí' }, + { value: 'RJ', label: 'Rio de Janeiro' }, + { value: 'RN', label: 'Rio Grande do Norte' }, + { value: 'RS', label: 'Rio Grande do Sul' }, + { value: 'RO', label: 'Rondônia' }, + { value: 'RR', label: 'Roraima' }, + { value: 'SC', label: 'Santa Catarina' }, + { value: 'SP', label: 'São Paulo' }, + { value: 'SE', label: 'Sergipe' }, + { value: 'TO', label: 'Tocantins' }, +] + +export const TEMPO_EMPRESA_OPTIONS: SelectOption[] = [ + { value: 'menos-6-meses', label: 'Menos de 6 meses' }, + { value: '6-meses-1-ano', label: 'De 6 meses a 1 ano' }, + { value: '1-2-anos', label: 'De 1 a 2 anos' }, + { value: '2-5-anos', label: 'De 2 a 5 anos' }, + { value: 'mais-5-anos', label: 'Mais de 5 anos' }, +] + +export function getOptionLabel(options: SelectOption[], value: string): string { + return options.find((option) => option.value === value)?.label ?? value +} diff --git a/frontend/src/context/WizardFormProvider.test.tsx b/frontend/src/context/WizardFormProvider.test.tsx new file mode 100644 index 0000000..cb266ae --- /dev/null +++ b/frontend/src/context/WizardFormProvider.test.tsx @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'vitest' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { useFormContext } from 'react-hook-form' +import { WizardFormProvider } from './WizardFormProvider' +import type { FormValues } from '../schemas/formSchema' +import { persistFormValues } from '../lib/storage' + +function FullNameProbe() { + const { register, watch } = useFormContext() + return ( +
+ + valor: {watch('fullName')} +
+ ) +} + +describe('WizardFormProvider', () => { + it('starts from the default empty values when nothing is stored', () => { + render( + + + , + ) + + expect(screen.getByText('valor:')).toBeInTheDocument() + }) + + it('hydrates from previously persisted values', () => { + persistFormValues({ fullName: 'Maria Souza' }) + + render( + + + , + ) + + expect(screen.getByLabelText('Nome completo')).toHaveValue('Maria Souza') + }) + + it('persists changes to localStorage as the user types', async () => { + const user = userEvent.setup() + render( + + + , + ) + + await user.type(screen.getByLabelText('Nome completo'), 'Ana') + + expect(JSON.parse(window.localStorage.getItem('verity:cadastro-wizard') ?? '{}').fullName).toBe('Ana') + }) +}) diff --git a/frontend/src/context/WizardFormProvider.tsx b/frontend/src/context/WizardFormProvider.tsx new file mode 100644 index 0000000..2a24c89 --- /dev/null +++ b/frontend/src/context/WizardFormProvider.tsx @@ -0,0 +1,26 @@ +import { useEffect, type ReactNode } from 'react' +import { FormProvider, useForm } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' +import { defaultFormValues, formSchema, type FormValues } from '../schemas/formSchema' +import { loadStoredFormValues, persistFormValues } from '../lib/storage' + +export function WizardFormProvider({ children }: { children: ReactNode }) { + const methods = useForm({ + resolver: zodResolver(formSchema), + mode: 'onSubmit', + reValidateMode: 'onChange', + defaultValues: { + ...defaultFormValues, + ...loadStoredFormValues(), + }, + }) + + useEffect(() => { + const subscription = methods.watch((values) => { + persistFormValues(values) + }) + return () => subscription.unsubscribe() + }, [methods]) + + return {children} +} diff --git a/frontend/src/hooks/useRequireStepData.test.tsx b/frontend/src/hooks/useRequireStepData.test.tsx new file mode 100644 index 0000000..ecedb4b --- /dev/null +++ b/frontend/src/hooks/useRequireStepData.test.tsx @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest' +import { screen } from '@testing-library/react' +import { Route, Routes } from 'react-router-dom' +import { renderWithForm } from '../test/renderWithForm' +import { useRequireStepData } from './useRequireStepData' + +function GuardedStep() { + useRequireStepData('fullName', '/etapa-1') + return

conteúdo protegido

+} + +describe('useRequireStepData', () => { + it('redirects when the required field is empty', () => { + renderWithForm( + + } /> + etapa 1

} /> +
, + { initialPath: '/etapa-2' }, + ) + + expect(screen.getByText('etapa 1')).toBeInTheDocument() + expect(screen.queryByText('conteúdo protegido')).not.toBeInTheDocument() + }) + + it('renders normally when the required field is already filled', () => { + renderWithForm( + + } /> + etapa 1

} /> +
, + { initialPath: '/etapa-2', initialValues: { fullName: 'Maria Souza' } }, + ) + + expect(screen.getByText('conteúdo protegido')).toBeInTheDocument() + }) +}) diff --git a/frontend/src/hooks/useRequireStepData.ts b/frontend/src/hooks/useRequireStepData.ts new file mode 100644 index 0000000..89116ed --- /dev/null +++ b/frontend/src/hooks/useRequireStepData.ts @@ -0,0 +1,20 @@ +import { useEffect } from 'react' +import { useNavigate } from 'react-router-dom' +import { useFormContext } from 'react-hook-form' +import type { FormValues } from '../schemas/formSchema' + +/** + * Guards a step against direct URL access before its prerequisite data exists, + * e.g. opening /resumo without having gone through the earlier steps. + */ +export function useRequireStepData(requiredField: keyof FormValues, redirectTo: string) { + const { getValues } = useFormContext() + const navigate = useNavigate() + + useEffect(() => { + if (!getValues(requiredField)) { + navigate(redirectTo, { replace: true }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) +} diff --git a/frontend/src/hooks/useStepNavigation.test.tsx b/frontend/src/hooks/useStepNavigation.test.tsx new file mode 100644 index 0000000..1d402b3 --- /dev/null +++ b/frontend/src/hooks/useStepNavigation.test.tsx @@ -0,0 +1,71 @@ +import { describe, expect, it } from 'vitest' +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { Route, Routes } from 'react-router-dom' +import { renderWithForm } from '../test/renderWithForm' +import { useStepNavigation } from './useStepNavigation' +import { TextField } from '../components/form/TextField' +import type { FormValues } from '../schemas/formSchema' + +function TestStep({ fields, nextPath }: { fields: (keyof FormValues)[]; nextPath: string }) { + const { goNext, goBack } = useStepNavigation(fields) + return ( +
+ + + +
+ ) +} + +describe('useStepNavigation', () => { + it('does not navigate when the required fields are invalid', async () => { + const user = userEvent.setup() + renderWithForm( + + } /> + chegou na etapa 2

} /> +
, + { initialPath: '/etapa-1' }, + ) + + await user.click(screen.getByText('avançar')) + + expect(screen.queryByText('chegou na etapa 2')).not.toBeInTheDocument() + }) + + it('navigates once the required fields are valid', async () => { + const user = userEvent.setup() + renderWithForm( + + } /> + chegou na etapa 2

} /> +
, + { initialPath: '/etapa-1' }, + ) + + await user.type(screen.getByLabelText('Nome completo'), 'Maria Souza') + await user.click(screen.getByText('avançar')) + + expect(await screen.findByText('chegou na etapa 2')).toBeInTheDocument() + }) + + it('goBack always navigates regardless of validity', async () => { + const user = userEvent.setup() + renderWithForm( + + etapa 1

} /> + } /> +
, + { initialPath: '/etapa-2' }, + ) + + await user.click(screen.getByText('voltar')) + + expect(await screen.findByText('etapa 1')).toBeInTheDocument() + }) +}) diff --git a/frontend/src/hooks/useStepNavigation.ts b/frontend/src/hooks/useStepNavigation.ts new file mode 100644 index 0000000..9584ed2 --- /dev/null +++ b/frontend/src/hooks/useStepNavigation.ts @@ -0,0 +1,21 @@ +import { useNavigate } from 'react-router-dom' +import { useFormContext } from 'react-hook-form' +import type { FormValues } from '../schemas/formSchema' + +export function useStepNavigation(fields: (keyof FormValues)[]) { + const { trigger } = useFormContext() + const navigate = useNavigate() + + async function goNext(path: string) { + const isValid = await trigger(fields) + if (isValid) { + navigate(path) + } + } + + function goBack(path: string) { + navigate(path) + } + + return { goNext, goBack } +} diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..d9d9855 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,359 @@ +:root { + --verity-blue-900: #082b63; + --verity-blue-700: #0d47a1; + --verity-blue-600: #1257b8; + --verity-blue-500: #1e6fd9; + --verity-blue-100: #e8f1fc; + --verity-blue-50: #f4f8fe; + + --color-white: #ffffff; + --color-gray-900: #1a1f29; + --color-gray-600: #5b6472; + --color-gray-400: #8b93a1; + --color-gray-200: #e2e6ec; + --color-gray-100: #f1f3f7; + + --color-error: #c62828; + --color-success: #1e8e3e; + + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 16px; + + --shadow-card: 0 1px 3px rgba(8, 43, 99, 0.08), 0 8px 24px rgba(8, 43, 99, 0.06); + + color-scheme: light; + font-family: 'Segoe UI', 'Inter', system-ui, -apple-system, sans-serif; + color: var(--color-gray-900); + background-color: var(--verity-blue-50); +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + min-height: 100%; +} + +body { + margin: 0; + background-color: var(--verity-blue-50); +} + +h1, +h2, +h3, +p { + margin: 0; +} + +button { + font-family: inherit; +} + +.app-shell { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.app-header { + background-color: var(--color-white); + border-bottom: 1px solid var(--color-gray-200); + padding: 16px 24px; + display: flex; + align-items: center; +} + +.app-main { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 24px 16px 48px; +} + +.app-footer { + text-align: center; + padding: 16px; + color: var(--color-gray-400); + font-size: 12px; +} + +.logo-mark { + display: flex; + align-items: center; + gap: 8px; +} + +.logo-image { + height: 36px; + width: auto; + display: block; +} + +/* Step indicator */ +.step-indicator { + list-style: none; + display: flex; + justify-content: center; + gap: 8px; + margin: 24px auto 0; + padding: 0 16px; + max-width: 720px; + width: 100%; +} + +.step { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + text-align: center; +} + +.step-index { + width: 30px; + height: 30px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + font-weight: 700; + background-color: var(--color-gray-200); + color: var(--color-gray-600); +} + +.step-label { + font-size: 12px; + color: var(--color-gray-600); +} + +.step-active .step-index { + background-color: var(--verity-blue-600); + color: var(--color-white); +} + +.step-active .step-label { + color: var(--verity-blue-700); + font-weight: 600; +} + +.step-done .step-index { + background-color: var(--verity-blue-100); + color: var(--verity-blue-600); + border: 2px solid var(--verity-blue-500); +} + +/* Card / wizard shell */ +.card { + width: 100%; + max-width: 720px; + background-color: var(--color-white); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-card); + padding: 32px; + margin-top: 24px; +} + +.step-title { + font-size: 22px; + font-weight: 700; + color: var(--verity-blue-900); +} + +.step-description { + margin-top: 6px; + color: var(--color-gray-600); + font-size: 14px; +} + +.field-grid { + display: grid; + grid-template-columns: 1fr; + gap: 18px; + margin-top: 24px; +} + +@media (min-width: 640px) { + .field-grid { + grid-template-columns: 1fr 1fr; + } + + .field-full { + grid-column: 1 / -1; + } +} + +.field { + display: flex; + flex-direction: column; + gap: 6px; +} + +.field label { + font-size: 13px; + font-weight: 600; + color: var(--color-gray-900); +} + +.field input, +.field select { + border: 1px solid var(--color-gray-200); + border-radius: var(--radius-sm); + padding: 10px 12px; + font-size: 14px; + background-color: var(--color-white); + color: var(--color-gray-900); + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.field input:focus, +.field select:focus { + outline: none; + border-color: var(--verity-blue-500); + box-shadow: 0 0 0 3px var(--verity-blue-100); +} + +.field input[aria-invalid='true'], +.field select[aria-invalid='true'] { + border-color: var(--color-error); +} + +.field-error { + font-size: 12px; + color: var(--color-error); +} + +.field-hint { + font-size: 12px; + color: var(--color-gray-400); +} + +.field-hint.loading { + color: var(--verity-blue-600); +} + +.field-hint.error { + color: var(--color-error); +} + +/* Buttons */ +.wizard-actions { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 32px; +} + +.btn { + border: none; + border-radius: var(--radius-sm); + padding: 11px 22px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.15s ease, transform 0.05s ease; +} + +.btn:active { + transform: translateY(1px); +} + +.btn-primary { + background-color: var(--verity-blue-600); + color: var(--color-white); +} + +.btn-primary:hover { + background-color: var(--verity-blue-700); +} + +.btn-secondary { + background-color: var(--color-white); + color: var(--verity-blue-600); + border: 1px solid var(--verity-blue-500); +} + +.btn-secondary:hover { + background-color: var(--verity-blue-50); +} + +.btn-link { + background: none; + border: none; + color: var(--verity-blue-600); + font-weight: 600; + font-size: 13px; + cursor: pointer; + padding: 0; +} + +.btn-link:hover { + text-decoration: underline; +} + +/* Summary page */ +.summary-section { + margin-top: 20px; + padding: 18px; + background-color: var(--verity-blue-50); + border-radius: var(--radius-md); +} + +.summary-section-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.summary-section-header h3 { + color: var(--verity-blue-900); + font-size: 15px; +} + +.summary-list { + margin: 12px 0 0; + display: grid; + grid-template-columns: 1fr; + gap: 10px; +} + +@media (min-width: 640px) { + .summary-list { + grid-template-columns: 1fr 1fr; + } +} + +.summary-row dt { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--color-gray-400); +} + +.summary-row dd { + margin: 2px 0 0; + font-size: 14px; + color: var(--color-gray-900); + font-weight: 500; +} + +@media (max-width: 480px) { + .card { + padding: 20px; + } + + .wizard-actions { + flex-direction: column-reverse; + } + + .btn { + width: 100%; + } +} diff --git a/frontend/src/lib/api.test.ts b/frontend/src/lib/api.test.ts new file mode 100644 index 0000000..835ad78 --- /dev/null +++ b/frontend/src/lib/api.test.ts @@ -0,0 +1,74 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { instanceGet, topLevelGet } = vi.hoisted(() => ({ + instanceGet: vi.fn(), + topLevelGet: vi.fn(), +})) + +vi.mock('axios', () => ({ + default: { + create: vi.fn(() => ({ get: instanceGet })), + get: topLevelGet, + }, +})) + +import { fetchAddressByCep, fetchProfessions } from './api' + +beforeEach(() => { + instanceGet.mockReset() + topLevelGet.mockReset() +}) + +describe('fetchAddressByCep', () => { + it('returns the address from the mocked json-server when found', async () => { + instanceGet.mockResolvedValueOnce({ + data: { endereco: 'Avenida Paulista', bairro: 'Bela Vista', cidade: 'São Paulo', estado: 'SP' }, + }) + + const result = await fetchAddressByCep('01310100') + + expect(result).toEqual({ endereco: 'Avenida Paulista', bairro: 'Bela Vista', cidade: 'São Paulo', estado: 'SP' }) + expect(instanceGet).toHaveBeenCalledWith('/ceps/01310100') + expect(topLevelGet).not.toHaveBeenCalled() + }) + + it('falls back to ViaCEP when the CEP is not in the mock db', async () => { + instanceGet.mockRejectedValueOnce(new Error('Request failed with status code 404')) + topLevelGet.mockResolvedValueOnce({ + data: { logradouro: 'Rua das Flores', bairro: 'Centro', localidade: 'Rio de Janeiro', uf: 'RJ' }, + }) + + const result = await fetchAddressByCep('20000000') + + expect(result).toEqual({ endereco: 'Rua das Flores', bairro: 'Centro', cidade: 'Rio de Janeiro', estado: 'RJ' }) + }) + + it('returns null when ViaCEP reports the CEP does not exist', async () => { + instanceGet.mockRejectedValueOnce(new Error('Request failed with status code 404')) + topLevelGet.mockResolvedValueOnce({ data: { erro: true } }) + + const result = await fetchAddressByCep('00000000') + + expect(result).toBeNull() + }) + + it('returns null when both the mock and ViaCEP fail', async () => { + instanceGet.mockRejectedValueOnce(new Error('network error')) + topLevelGet.mockRejectedValueOnce(new Error('network error')) + + const result = await fetchAddressByCep('00000000') + + expect(result).toBeNull() + }) +}) + +describe('fetchProfessions', () => { + it('returns the list of professions from the mocked API', async () => { + instanceGet.mockResolvedValueOnce({ data: [{ id: 1, name: 'Engenheiro(a) Civil' }] }) + + const result = await fetchProfessions() + + expect(result).toEqual([{ id: 1, name: 'Engenheiro(a) Civil' }]) + expect(instanceGet).toHaveBeenCalledWith('/professions') + }) +}) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts new file mode 100644 index 0000000..c7c4d4e --- /dev/null +++ b/frontend/src/lib/api.ts @@ -0,0 +1,64 @@ +import axios from 'axios' + +export const mockApi = axios.create({ + baseURL: import.meta.env.VITE_MOCK_API_URL ?? 'http://localhost:3001', +}) + +export interface CepResult { + endereco: string + bairro: string + cidade: string + estado: string +} + +interface ViaCepResponse { + logradouro: string + bairro: string + localidade: string + uf: string + erro?: boolean +} + +async function fetchCepFromMock(cep: string): Promise { + try { + const { data } = await mockApi.get(`/ceps/${cep}`) + return data + } catch { + return null + } +} + +async function fetchCepFromViaCep(cep: string): Promise { + try { + const { data } = await axios.get(`https://viacep.com.br/ws/${cep}/json/`) + if (data.erro) return null + return { + endereco: data.logradouro, + bairro: data.bairro, + cidade: data.localidade, + estado: data.uf, + } + } catch { + return null + } +} + +/** + * Tries the mocked json-server first (challenge requirement) and falls back + * to the public ViaCEP service when the CEP isn't present in the mock db (bonus requirement). + */ +export async function fetchAddressByCep(cep: string): Promise { + const mockResult = await fetchCepFromMock(cep) + if (mockResult) return mockResult + return fetchCepFromViaCep(cep) +} + +export interface Profession { + id: number | string + name: string +} + +export async function fetchProfessions(): Promise { + const { data } = await mockApi.get('/professions') + return data +} diff --git a/frontend/src/lib/cpf.test.ts b/frontend/src/lib/cpf.test.ts new file mode 100644 index 0000000..4bcf52a --- /dev/null +++ b/frontend/src/lib/cpf.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest' +import { isValidCPF, onlyDigits } from './cpf' + +describe('onlyDigits', () => { + it('strips every non-digit character', () => { + expect(onlyDigits('529.982.247-25')).toBe('52998224725') + expect(onlyDigits('(11) 98765-4321')).toBe('11987654321') + }) +}) + +describe('isValidCPF', () => { + it('accepts a valid formatted CPF', () => { + expect(isValidCPF('529.982.247-25')).toBe(true) + }) + + it('accepts a valid unformatted CPF', () => { + expect(isValidCPF('52998224725')).toBe(true) + }) + + it('rejects a CPF with a wrong check digit', () => { + expect(isValidCPF('529.982.247-26')).toBe(false) + }) + + it('rejects a CPF made of the same repeated digit', () => { + expect(isValidCPF('111.111.111-11')).toBe(false) + expect(isValidCPF('000.000.000-00')).toBe(false) + }) + + it('rejects a CPF with the wrong length', () => { + expect(isValidCPF('1234567890')).toBe(false) + expect(isValidCPF('123456789012')).toBe(false) + }) + + it('rejects an empty value', () => { + expect(isValidCPF('')).toBe(false) + }) +}) diff --git a/frontend/src/lib/cpf.ts b/frontend/src/lib/cpf.ts new file mode 100644 index 0000000..5e06579 --- /dev/null +++ b/frontend/src/lib/cpf.ts @@ -0,0 +1,26 @@ +export function onlyDigits(value: string): string { + return value.replace(/\D/g, '') +} + +function calcCheckDigit(digits: number[], length: number): number { + let sum = 0 + for (let i = 0; i < length; i++) { + sum += digits[i] * (length + 1 - i) + } + const remainder = (sum * 10) % 11 + return remainder === 10 ? 0 : remainder +} + +export function isValidCPF(rawValue: string): boolean { + const cpf = onlyDigits(rawValue) + + if (cpf.length !== 11 || /^(\d)\1{10}$/.test(cpf)) { + return false + } + + const digits = cpf.split('').map(Number) + const firstCheckDigit = calcCheckDigit(digits, 9) + const secondCheckDigit = calcCheckDigit(digits, 10) + + return firstCheckDigit === digits[9] && secondCheckDigit === digits[10] +} diff --git a/frontend/src/lib/masks.test.ts b/frontend/src/lib/masks.test.ts new file mode 100644 index 0000000..7d6252a --- /dev/null +++ b/frontend/src/lib/masks.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest' +import { isValidBirthDate, onlyDigits, parseCurrencyToNumber } from './masks' + +describe('onlyDigits', () => { + it('removes non-numeric characters', () => { + expect(onlyDigits('01310-100')).toBe('01310100') + }) +}) + +describe('isValidBirthDate', () => { + it('accepts a real past date in dd/mm/yyyy format', () => { + expect(isValidBirthDate('15/05/1990')).toBe(true) + }) + + it('rejects an incomplete date', () => { + expect(isValidBirthDate('15/05/19')).toBe(false) + expect(isValidBirthDate('')).toBe(false) + }) + + it('rejects a calendar-impossible date', () => { + expect(isValidBirthDate('31/02/2020')).toBe(false) + expect(isValidBirthDate('32/01/2020')).toBe(false) + expect(isValidBirthDate('01/13/2020')).toBe(false) + }) + + it('rejects a date in the future', () => { + const future = new Date() + future.setFullYear(future.getFullYear() + 1) + const day = String(future.getDate()).padStart(2, '0') + const month = String(future.getMonth() + 1).padStart(2, '0') + expect(isValidBirthDate(`${day}/${month}/${future.getFullYear()}`)).toBe(false) + }) + + it('rejects unreasonable years', () => { + expect(isValidBirthDate('01/01/1800')).toBe(false) + }) +}) + +describe('parseCurrencyToNumber', () => { + it('parses a BRL formatted string into a number', () => { + expect(parseCurrencyToNumber('R$ 8.500,50')).toBe(8500.5) + expect(parseCurrencyToNumber('R$ 0,00')).toBe(0) + expect(parseCurrencyToNumber('R$ 1.234.567,89')).toBe(1234567.89) + }) + + it('returns 0 for an unparsable value', () => { + expect(parseCurrencyToNumber('')).toBe(0) + expect(parseCurrencyToNumber('abc')).toBe(0) + }) +}) diff --git a/frontend/src/lib/masks.ts b/frontend/src/lib/masks.ts new file mode 100644 index 0000000..12494bc --- /dev/null +++ b/frontend/src/lib/masks.ts @@ -0,0 +1,31 @@ +export function onlyDigits(value: string): string { + return value.replace(/\D/g, '') +} + +export function isValidBirthDate(value: string): boolean { + const match = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value) + if (!match) return false + + const day = Number(match[1]) + const month = Number(match[2]) + const year = Number(match[3]) + const date = new Date(year, month - 1, day) + + const isRealCalendarDate = + date.getFullYear() === year && + date.getMonth() === month - 1 && + date.getDate() === day + + if (!isRealCalendarDate) return false + + const today = new Date() + today.setHours(0, 0, 0, 0) + + return date <= today && year >= 1900 +} + +export function parseCurrencyToNumber(value: string): number { + const normalized = value.replace(/[^\d,-]/g, '').replace(',', '.') + const parsed = Number.parseFloat(normalized) + return Number.isNaN(parsed) ? 0 : parsed +} diff --git a/frontend/src/lib/pdf.test.ts b/frontend/src/lib/pdf.test.ts new file mode 100644 index 0000000..83c152a --- /dev/null +++ b/frontend/src/lib/pdf.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it, vi } from 'vitest' +import type { FormValues } from '../schemas/formSchema' + +const { docMock, MockJsPDF } = vi.hoisted(() => { + const docMock = { + internal: { + pageSize: { getWidth: () => 210, getHeight: () => 297 }, + }, + setFillColor: vi.fn(), + rect: vi.fn(), + setTextColor: vi.fn(), + setFont: vi.fn(), + setFontSize: vi.fn(), + text: vi.fn(), + setDrawColor: vi.fn(), + line: vi.fn(), + save: vi.fn(), + } + class MockJsPDF { + constructor() { + return docMock + } + } + return { docMock, MockJsPDF } +}) + +vi.mock('jspdf', () => ({ jsPDF: MockJsPDF })) + +const { generateSummaryPdf } = await import('./pdf') + +const values: FormValues = { + fullName: 'Maria da Silva Souza', + birthDate: '15/05/1990', + cpf: '529.982.247-25', + phone: '(11) 98765-4321', + email: 'maria@example.com', + cep: '01310-100', + endereco: 'Avenida Paulista', + bairro: 'Bela Vista', + cidade: 'São Paulo', + estado: 'SP', + empresa: 'Verity Tecnologia', + profissao: 'Desenvolvedor(a) de Software', + salario: 'R$ 8.500,50', + tempoDeEmpresa: '2-5-anos', +} + +describe('generateSummaryPdf', () => { + it('draws the branded header and every field, then saves the file', () => { + generateSummaryPdf(values) + + expect(docMock.text).toHaveBeenCalledWith('verity', expect.any(Number), expect.any(Number)) + expect(docMock.text).toHaveBeenCalledWith('Maria da Silva Souza', expect.any(Number), expect.any(Number)) + expect(docMock.text).toHaveBeenCalledWith('São Paulo', expect.any(Number), expect.any(Number)) + expect(docMock.save).toHaveBeenCalledWith('verity-cadastro-maria-da-silva-souza.pdf') + }) + + it('falls back to a generic filename when the name is blank', () => { + generateSummaryPdf({ ...values, fullName: '' }) + expect(docMock.save).toHaveBeenCalledWith('verity-cadastro-cadastro.pdf') + }) +}) diff --git a/frontend/src/lib/pdf.ts b/frontend/src/lib/pdf.ts new file mode 100644 index 0000000..be8dc85 --- /dev/null +++ b/frontend/src/lib/pdf.ts @@ -0,0 +1,95 @@ +import { jsPDF } from 'jspdf' +import type { FormValues } from '../schemas/formSchema' +import { getOptionLabel, TEMPO_EMPRESA_OPTIONS, UF_OPTIONS } from '../constants/options' + +const VERITY_BLUE: [number, number, number] = [13, 71, 161] +const VERITY_BLUE_DARK: [number, number, number] = [8, 43, 99] +const TEXT_GRAY: [number, number, number] = [60, 68, 82] +const MARGIN_X = 16 + +interface Section { + title: string + rows: [string, string][] +} + +function buildSections(data: FormValues): Section[] { + return [ + { + title: 'Dados Pessoais', + rows: [ + ['Nome completo', data.fullName], + ['Data de nascimento', data.birthDate], + ['CPF', data.cpf], + ['Telefone', data.phone], + ['E-mail', data.email], + ], + }, + { + title: 'Informações Residenciais', + rows: [ + ['CEP', data.cep], + ['Endereço', data.endereco], + ['Bairro', data.bairro], + ['Cidade', data.cidade], + ['Estado', getOptionLabel(UF_OPTIONS, data.estado)], + ], + }, + { + title: 'Informações Profissionais', + rows: [ + ['Empresa', data.empresa], + ['Profissão', data.profissao], + ['Salário', data.salario], + ['Tempo de empresa', getOptionLabel(TEMPO_EMPRESA_OPTIONS, data.tempoDeEmpresa)], + ], + }, + ] +} + +export function generateSummaryPdf(data: FormValues): void { + const doc = new jsPDF() + const pageWidth = doc.internal.pageSize.getWidth() + + doc.setFillColor(...VERITY_BLUE) + doc.rect(0, 0, pageWidth, 30, 'F') + doc.setTextColor(255, 255, 255) + doc.setFont('helvetica', 'bold') + doc.setFontSize(18) + doc.text('verity', MARGIN_X, 19) + doc.setFontSize(11) + doc.setFont('helvetica', 'normal') + doc.text('Resumo do Cadastro', pageWidth - MARGIN_X, 19, { align: 'right' }) + + let cursorY = 44 + + for (const section of buildSections(data)) { + doc.setTextColor(...VERITY_BLUE_DARK) + doc.setFont('helvetica', 'bold') + doc.setFontSize(13) + doc.text(section.title, MARGIN_X, cursorY) + cursorY += 3 + doc.setDrawColor(...VERITY_BLUE) + doc.line(MARGIN_X, cursorY, pageWidth - MARGIN_X, cursorY) + cursorY += 8 + + doc.setFontSize(11) + for (const [label, value] of section.rows) { + doc.setFont('helvetica', 'bold') + doc.setTextColor(...TEXT_GRAY) + doc.text(`${label}:`, MARGIN_X, cursorY) + doc.setFont('helvetica', 'normal') + doc.text(value || '—', MARGIN_X + 55, cursorY) + cursorY += 7 + } + + cursorY += 6 + } + + const generatedAt = new Date().toLocaleString('pt-BR') + doc.setFontSize(9) + doc.setTextColor(...TEXT_GRAY) + doc.text(`Gerado em ${generatedAt}`, MARGIN_X, doc.internal.pageSize.getHeight() - 10) + + const fileSuffix = data.fullName.trim().toLowerCase().replace(/\s+/g, '-') || 'cadastro' + doc.save(`verity-cadastro-${fileSuffix}.pdf`) +} diff --git a/frontend/src/lib/storage.test.ts b/frontend/src/lib/storage.test.ts new file mode 100644 index 0000000..ab74160 --- /dev/null +++ b/frontend/src/lib/storage.test.ts @@ -0,0 +1,42 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { clearStoredFormValues, loadStoredFormValues, persistFormValues } from './storage' + +interface Sample { + fullName: string +} + +afterEach(() => { + window.localStorage.clear() + vi.restoreAllMocks() +}) + +describe('persistFormValues / loadStoredFormValues', () => { + it('round-trips values through localStorage', () => { + persistFormValues({ fullName: 'Maria Souza' }) + expect(loadStoredFormValues()).toEqual({ fullName: 'Maria Souza' }) + }) + + it('returns undefined when nothing was stored', () => { + expect(loadStoredFormValues()).toBeUndefined() + }) + + it('returns undefined when stored value is not valid JSON', () => { + window.localStorage.setItem('verity:cadastro-wizard', '{not-json') + expect(loadStoredFormValues()).toBeUndefined() + }) + + it('silently ignores a localStorage write failure', () => { + vi.spyOn(window.localStorage.__proto__, 'setItem').mockImplementation(() => { + throw new Error('quota exceeded') + }) + expect(() => persistFormValues({ fullName: 'Maria' })).not.toThrow() + }) +}) + +describe('clearStoredFormValues', () => { + it('removes the stored value', () => { + persistFormValues({ fullName: 'Maria Souza' }) + clearStoredFormValues() + expect(loadStoredFormValues()).toBeUndefined() + }) +}) diff --git a/frontend/src/lib/storage.ts b/frontend/src/lib/storage.ts new file mode 100644 index 0000000..ef0971b --- /dev/null +++ b/frontend/src/lib/storage.ts @@ -0,0 +1,23 @@ +const STORAGE_KEY = 'verity:cadastro-wizard' + +export function loadStoredFormValues(): Partial | undefined { + try { + const raw = window.localStorage.getItem(STORAGE_KEY) + if (!raw) return undefined + return JSON.parse(raw) as Partial + } catch { + return undefined + } +} + +export function persistFormValues(values: unknown): void { + try { + window.localStorage.setItem(STORAGE_KEY, JSON.stringify(values)) + } catch { + // storage unavailable (e.g. private mode) — form still works in-memory + } +} + +export function clearStoredFormValues(): void { + window.localStorage.removeItem(STORAGE_KEY) +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/frontend/src/pages/Step1PersonalData.test.tsx b/frontend/src/pages/Step1PersonalData.test.tsx new file mode 100644 index 0000000..5154b70 --- /dev/null +++ b/frontend/src/pages/Step1PersonalData.test.tsx @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest' +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { Route, Routes } from 'react-router-dom' +import { renderWithForm } from '../test/renderWithForm' +import { Step1PersonalData } from './Step1PersonalData' + +function renderStep1() { + return renderWithForm( + + } /> + Informações Residenciais

} /> +
, + { initialPath: '/etapa-1' }, + ) +} + +describe('Step1PersonalData', () => { + it('renders every personal data field', () => { + renderStep1() + expect(screen.getByLabelText('Nome completo')).toBeInTheDocument() + expect(screen.getByLabelText('Data de nascimento')).toBeInTheDocument() + expect(screen.getByLabelText('CPF')).toBeInTheDocument() + expect(screen.getByLabelText('Telefone')).toBeInTheDocument() + expect(screen.getByLabelText('E-mail')).toBeInTheDocument() + }) + + it('blocks navigation and shows errors when the form is invalid', async () => { + const user = userEvent.setup() + renderStep1() + + await user.click(screen.getByText('Próximo')) + + expect(await screen.findByText('Informe o nome completo')).toBeInTheDocument() + expect(screen.queryByText('Informações Residenciais')).not.toBeInTheDocument() + }) + + it('navigates to step 2 once all fields are valid', async () => { + const user = userEvent.setup() + renderStep1() + + await user.type(screen.getByLabelText('Nome completo'), 'Maria Souza') + await user.type(screen.getByLabelText('Data de nascimento'), '15051990') + await user.type(screen.getByLabelText('CPF'), '52998224725') + await user.type(screen.getByLabelText('Telefone'), '11987654321') + await user.type(screen.getByLabelText('E-mail'), 'maria@example.com') + await user.click(screen.getByText('Próximo')) + + expect(await screen.findByText('Informações Residenciais')).toBeInTheDocument() + }) +}) diff --git a/frontend/src/pages/Step1PersonalData.tsx b/frontend/src/pages/Step1PersonalData.tsx new file mode 100644 index 0000000..d99d8e4 --- /dev/null +++ b/frontend/src/pages/Step1PersonalData.tsx @@ -0,0 +1,36 @@ +import { TextField } from '../components/form/TextField' +import { MaskedTextField } from '../components/form/MaskedTextField' +import { WizardActions } from '../components/wizard/WizardActions' +import { WizardStepShell } from '../components/wizard/WizardStepShell' +import { useStepNavigation } from '../hooks/useStepNavigation' +import { STEP1_FIELDS } from '../schemas/formSchema' +import { CPF_MASK, DATE_MASK, PHONE_MASK } from '../constants/masks' + +export function Step1PersonalData() { + const { goNext } = useStepNavigation(STEP1_FIELDS) + + return ( + +
+ + + + + +
+ goNext('/etapa-2')} /> +
+ ) +} diff --git a/frontend/src/pages/Step2Address.test.tsx b/frontend/src/pages/Step2Address.test.tsx new file mode 100644 index 0000000..6e31378 --- /dev/null +++ b/frontend/src/pages/Step2Address.test.tsx @@ -0,0 +1,82 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest' +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { Route, Routes } from 'react-router-dom' +import { renderWithForm } from '../test/renderWithForm' +import { Step2Address } from './Step2Address' +import { fetchAddressByCep } from '../lib/api' + +vi.mock('../lib/api', () => ({ + fetchAddressByCep: vi.fn(), +})) + +function renderStep2(initialValues = { fullName: 'Maria Souza' }) { + return renderWithForm( + + Dados Pessoais

} /> + } /> + Informações Profissionais

} /> +
, + { initialPath: '/etapa-2', initialValues }, + ) +} + +beforeEach(() => { + vi.mocked(fetchAddressByCep).mockReset() +}) + +describe('Step2Address', () => { + it('auto-fills the address fields once the CEP lookup resolves', async () => { + vi.mocked(fetchAddressByCep).mockResolvedValueOnce({ + endereco: 'Avenida Paulista', + bairro: 'Bela Vista', + cidade: 'São Paulo', + estado: 'SP', + }) + const user = userEvent.setup() + renderStep2() + + await user.type(screen.getByLabelText('CEP'), '01310100') + + expect(await screen.findByDisplayValue('Avenida Paulista')).toBeInTheDocument() + expect(screen.getByDisplayValue('Bela Vista')).toBeInTheDocument() + expect(screen.getByLabelText('Cidade')).toHaveValue('São Paulo') + expect(screen.getByLabelText('Estado')).toHaveValue('SP') + }) + + it('shows a not-found hint when no service has the CEP', async () => { + vi.mocked(fetchAddressByCep).mockResolvedValueOnce(null) + const user = userEvent.setup() + renderStep2() + + await user.type(screen.getByLabelText('CEP'), '99999999') + + expect(await screen.findByText('CEP não encontrado, preencha manualmente.')).toBeInTheDocument() + }) + + it('navigates back to step 1', async () => { + const user = userEvent.setup() + renderStep2() + + await user.click(screen.getByText('Voltar')) + + expect(await screen.findByText('Dados Pessoais')).toBeInTheDocument() + }) + + it('navigates to step 3 once the address is valid', async () => { + vi.mocked(fetchAddressByCep).mockResolvedValueOnce({ + endereco: 'Avenida Paulista', + bairro: 'Bela Vista', + cidade: 'São Paulo', + estado: 'SP', + }) + const user = userEvent.setup() + renderStep2() + + await user.type(screen.getByLabelText('CEP'), '01310100') + await screen.findByDisplayValue('Avenida Paulista') + await user.click(screen.getByText('Próximo')) + + expect(await screen.findByText('Informações Profissionais')).toBeInTheDocument() + }) +}) diff --git a/frontend/src/pages/Step2Address.tsx b/frontend/src/pages/Step2Address.tsx new file mode 100644 index 0000000..a4b9bd4 --- /dev/null +++ b/frontend/src/pages/Step2Address.tsx @@ -0,0 +1,81 @@ +import { useEffect, useState } from 'react' +import { useFormContext, useWatch } from 'react-hook-form' +import { TextField } from '../components/form/TextField' +import { SelectField } from '../components/form/SelectField' +import { MaskedTextField } from '../components/form/MaskedTextField' +import { WizardActions } from '../components/wizard/WizardActions' +import { WizardStepShell } from '../components/wizard/WizardStepShell' +import { useStepNavigation } from '../hooks/useStepNavigation' +import { useRequireStepData } from '../hooks/useRequireStepData' +import { STEP2_FIELDS, type FormValues } from '../schemas/formSchema' +import { CEP_MASK } from '../constants/masks' +import { UF_OPTIONS } from '../constants/options' +import { fetchAddressByCep } from '../lib/api' +import { onlyDigits } from '../lib/masks' + +type CepStatus = 'idle' | 'loading' | 'success' | 'not-found' + +const CEP_STATUS_HINTS: Partial> = { + loading: 'Buscando endereço...', + 'not-found': 'CEP não encontrado, preencha manualmente.', +} + +export function Step2Address() { + useRequireStepData('fullName', '/etapa-1') + const { control, setValue } = useFormContext() + const { goNext, goBack } = useStepNavigation(STEP2_FIELDS) + const [cepStatus, setCepStatus] = useState('idle') + const cep = useWatch({ control, name: 'cep' }) + + useEffect(() => { + const digits = onlyDigits(cep ?? '') + if (digits.length !== 8) { + setCepStatus('idle') + return + } + + let cancelled = false + setCepStatus('loading') + + fetchAddressByCep(digits).then((result) => { + if (cancelled) return + if (result) { + setValue('endereco', result.endereco, { shouldValidate: true }) + setValue('bairro', result.bairro, { shouldValidate: true }) + setValue('cidade', result.cidade, { shouldValidate: true }) + setValue('estado', result.estado.toUpperCase(), { shouldValidate: true }) + setCepStatus('success') + } else { + setCepStatus('not-found') + } + }) + + return () => { + cancelled = true + } + }, [cep, setValue]) + + return ( + +
+ + + + + +
+ goBack('/etapa-1')} onNext={() => goNext('/etapa-3')} /> +
+ ) +} diff --git a/frontend/src/pages/Step3Professional.test.tsx b/frontend/src/pages/Step3Professional.test.tsx new file mode 100644 index 0000000..fc38c15 --- /dev/null +++ b/frontend/src/pages/Step3Professional.test.tsx @@ -0,0 +1,66 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest' +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { Route, Routes } from 'react-router-dom' +import { renderWithForm } from '../test/renderWithForm' +import { Step3Professional } from './Step3Professional' +import { fetchProfessions } from '../lib/api' + +vi.mock('../lib/api', () => ({ + fetchProfessions: vi.fn(), +})) + +function renderStep3() { + return renderWithForm( + + Informações Residenciais

} /> + } /> + Resumo do Cadastro

} /> +
, + { initialPath: '/etapa-3', initialValues: { endereco: 'Avenida Paulista' } }, + ) +} + +beforeEach(() => { + vi.mocked(fetchProfessions).mockReset() +}) + +describe('Step3Professional', () => { + it('disables the profession select while loading, then populates it', async () => { + vi.mocked(fetchProfessions).mockResolvedValueOnce([ + { id: 1, name: 'Desenvolvedor(a) de Software' }, + { id: 2, name: 'Analista de Sistemas' }, + ]) + renderStep3() + + expect(screen.getByLabelText('Profissão')).toBeDisabled() + + await screen.findByRole('option', { name: 'Desenvolvedor(a) de Software' }) + expect(screen.getByLabelText('Profissão')).not.toBeDisabled() + expect(screen.getByRole('option', { name: 'Analista de Sistemas' })).toBeInTheDocument() + }) + + it('formats the salary field as currency', async () => { + vi.mocked(fetchProfessions).mockResolvedValueOnce([]) + const user = userEvent.setup() + renderStep3() + + await user.type(screen.getByLabelText('Salário'), '500000') + expect(screen.getByLabelText('Salário')).toHaveValue('R$ 5.000,00') + }) + + it('navigates to the summary once the step is valid', async () => { + vi.mocked(fetchProfessions).mockResolvedValueOnce([{ id: 1, name: 'Analista de Sistemas' }]) + const user = userEvent.setup() + renderStep3() + + await screen.findByRole('option', { name: 'Analista de Sistemas' }) + await user.type(screen.getByLabelText('Empresa'), 'Verity Tecnologia') + await user.selectOptions(screen.getByLabelText('Profissão'), 'Analista de Sistemas') + await user.type(screen.getByLabelText('Salário'), '500000') + await user.selectOptions(screen.getByLabelText('Tempo de empresa'), 'De 2 a 5 anos') + await user.click(screen.getByText('Ver resumo')) + + expect(await screen.findByText('Resumo do Cadastro')).toBeInTheDocument() + }) +}) diff --git a/frontend/src/pages/Step3Professional.tsx b/frontend/src/pages/Step3Professional.tsx new file mode 100644 index 0000000..396f475 --- /dev/null +++ b/frontend/src/pages/Step3Professional.tsx @@ -0,0 +1,56 @@ +import { useEffect, useState } from 'react' +import { TextField } from '../components/form/TextField' +import { SelectField } from '../components/form/SelectField' +import { CurrencyField } from '../components/form/CurrencyField' +import { WizardActions } from '../components/wizard/WizardActions' +import { WizardStepShell } from '../components/wizard/WizardStepShell' +import { useStepNavigation } from '../hooks/useStepNavigation' +import { useRequireStepData } from '../hooks/useRequireStepData' +import { STEP3_FIELDS } from '../schemas/formSchema' +import { TEMPO_EMPRESA_OPTIONS, type SelectOption } from '../constants/options' +import { fetchProfessions } from '../lib/api' + +export function Step3Professional() { + useRequireStepData('endereco', '/etapa-2') + const { goNext, goBack } = useStepNavigation(STEP3_FIELDS) + const [professionOptions, setProfessionOptions] = useState([]) + const [loadingProfessions, setLoadingProfessions] = useState(true) + + useEffect(() => { + let cancelled = false + + fetchProfessions() + .then((professions) => { + if (cancelled) return + setProfessionOptions(professions.map((p) => ({ value: p.name, label: p.name }))) + }) + .catch(() => { + if (!cancelled) setProfessionOptions([]) + }) + .finally(() => { + if (!cancelled) setLoadingProfessions(false) + }) + + return () => { + cancelled = true + } + }, []) + + return ( + +
+ + + + +
+ goBack('/etapa-2')} onNext={() => goNext('/resumo')} nextLabel="Ver resumo" /> +
+ ) +} diff --git a/frontend/src/pages/SummaryPage.test.tsx b/frontend/src/pages/SummaryPage.test.tsx new file mode 100644 index 0000000..7b89617 --- /dev/null +++ b/frontend/src/pages/SummaryPage.test.tsx @@ -0,0 +1,86 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest' +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { Route, Routes } from 'react-router-dom' +import { renderWithForm } from '../test/renderWithForm' +import { SummaryPage } from './SummaryPage' +import { generateSummaryPdf } from '../lib/pdf' +import { clearStoredFormValues } from '../lib/storage' +import type { FormValues } from '../schemas/formSchema' + +vi.mock('../lib/pdf', () => ({ generateSummaryPdf: vi.fn() })) +vi.mock('../lib/storage', () => ({ clearStoredFormValues: vi.fn(), persistFormValues: vi.fn() })) + +const filledValues: Partial = { + fullName: 'Maria da Silva Souza', + birthDate: '15/05/1990', + cpf: '529.982.247-25', + phone: '(11) 98765-4321', + email: 'maria@example.com', + cep: '01310-100', + endereco: 'Avenida Paulista', + bairro: 'Bela Vista', + cidade: 'São Paulo', + estado: 'SP', + empresa: 'Verity Tecnologia', + profissao: 'Desenvolvedor(a) de Software', + salario: 'R$ 8.500,50', + tempoDeEmpresa: '2-5-anos', +} + +function renderSummary() { + return renderWithForm( + + Dados Pessoais

} /> + Informações Profissionais

} /> + } /> +
, + { initialPath: '/resumo', initialValues: filledValues }, + ) +} + +beforeEach(() => { + vi.mocked(generateSummaryPdf).mockReset() + vi.mocked(clearStoredFormValues).mockReset() +}) + +describe('SummaryPage', () => { + it('renders every filled value grouped by section, translating select values to labels', () => { + renderSummary() + + expect(screen.getByText('Maria da Silva Souza')).toBeInTheDocument() + expect(screen.getByText('529.982.247-25')).toBeInTheDocument() + expect(screen.getByText('R$ 8.500,50')).toBeInTheDocument() + // "São Paulo" appears twice: once as the city, once as the translated UF label. + expect(screen.getAllByText('São Paulo')).toHaveLength(2) + expect(screen.getByText('De 2 a 5 anos')).toBeInTheDocument() + }) + + it('exports the PDF with the current form values', async () => { + const user = userEvent.setup() + renderSummary() + + await user.click(screen.getByText('Exportar PDF')) + + expect(generateSummaryPdf).toHaveBeenCalledWith(expect.objectContaining({ fullName: 'Maria da Silva Souza' })) + }) + + it('navigates to the matching step when Editar is clicked', async () => { + const user = userEvent.setup() + renderSummary() + + await user.click(screen.getAllByText('Editar')[0]) + + expect(await screen.findByText('Dados Pessoais')).toBeInTheDocument() + }) + + it('clears the stored data and restarts on "Novo cadastro"', async () => { + const user = userEvent.setup() + renderSummary() + + await user.click(screen.getByText('Novo cadastro')) + + expect(clearStoredFormValues).toHaveBeenCalledOnce() + expect(await screen.findByText('Dados Pessoais')).toBeInTheDocument() + }) +}) diff --git a/frontend/src/pages/SummaryPage.tsx b/frontend/src/pages/SummaryPage.tsx new file mode 100644 index 0000000..2370824 --- /dev/null +++ b/frontend/src/pages/SummaryPage.tsx @@ -0,0 +1,71 @@ +import { useNavigate } from 'react-router-dom' +import { useFormContext } from 'react-hook-form' +import { WizardStepShell } from '../components/wizard/WizardStepShell' +import { SummarySection } from '../components/wizard/SummarySection' +import { useRequireStepData } from '../hooks/useRequireStepData' +import { defaultFormValues, type FormValues } from '../schemas/formSchema' +import { getOptionLabel, TEMPO_EMPRESA_OPTIONS, UF_OPTIONS } from '../constants/options' +import { generateSummaryPdf } from '../lib/pdf' +import { clearStoredFormValues } from '../lib/storage' + +export function SummaryPage() { + useRequireStepData('profissao', '/etapa-3') + const { getValues, reset } = useFormContext() + const navigate = useNavigate() + const values = getValues() + + function handleExport() { + generateSummaryPdf(values) + } + + function handleNewRegistration() { + clearStoredFormValues() + reset(defaultFormValues) + navigate('/etapa-1') + } + + return ( + + navigate('/etapa-1')} + items={[ + { label: 'Nome completo', value: values.fullName }, + { label: 'Data de nascimento', value: values.birthDate }, + { label: 'CPF', value: values.cpf }, + { label: 'Telefone', value: values.phone }, + { label: 'E-mail', value: values.email }, + ]} + /> + navigate('/etapa-2')} + items={[ + { label: 'CEP', value: values.cep }, + { label: 'Endereço', value: values.endereco }, + { label: 'Bairro', value: values.bairro }, + { label: 'Cidade', value: values.cidade }, + { label: 'Estado', value: getOptionLabel(UF_OPTIONS, values.estado) }, + ]} + /> + navigate('/etapa-3')} + items={[ + { label: 'Empresa', value: values.empresa }, + { label: 'Profissão', value: values.profissao }, + { label: 'Salário', value: values.salario }, + { label: 'Tempo de empresa', value: getOptionLabel(TEMPO_EMPRESA_OPTIONS, values.tempoDeEmpresa) }, + ]} + /> +
+ + +
+
+ ) +} diff --git a/frontend/src/schemas/formSchema.test.ts b/frontend/src/schemas/formSchema.test.ts new file mode 100644 index 0000000..a6a002a --- /dev/null +++ b/frontend/src/schemas/formSchema.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, it } from 'vitest' +import { addressSchema, defaultFormValues, formSchema, personalDataSchema, professionalSchema } from './formSchema' + +const validValues = { + fullName: 'Maria da Silva Souza', + birthDate: '15/05/1990', + cpf: '529.982.247-25', + phone: '(11) 98765-4321', + email: 'maria@example.com', + cep: '01310-100', + endereco: 'Avenida Paulista', + bairro: 'Bela Vista', + cidade: 'São Paulo', + estado: 'SP', + empresa: 'Verity Tecnologia', + profissao: 'Desenvolvedor(a) de Software', + salario: 'R$ 8.500,50', + tempoDeEmpresa: '2-5-anos', +} + +describe('formSchema', () => { + it('accepts a fully valid payload', () => { + expect(formSchema.safeParse(validValues).success).toBe(true) + }) + + it('rejects the untouched default values', () => { + expect(formSchema.safeParse(defaultFormValues).success).toBe(false) + }) +}) + +describe('personalDataSchema', () => { + it('requires at least two words for the full name', () => { + const result = personalDataSchema.safeParse({ ...validValues, fullName: 'Maria' }) + expect(result.success).toBe(false) + }) + + it('rejects an invalid CPF', () => { + const result = personalDataSchema.safeParse({ ...validValues, cpf: '111.111.111-11' }) + expect(result.success).toBe(false) + }) + + it('rejects a malformed phone number', () => { + const result = personalDataSchema.safeParse({ ...validValues, phone: '11987654321' }) + expect(result.success).toBe(false) + }) + + it('accepts an 8-digit landline phone number', () => { + const result = personalDataSchema.safeParse({ ...validValues, phone: '(11) 3456-7890' }) + expect(result.success).toBe(true) + }) + + it('rejects an invalid email', () => { + const result = personalDataSchema.safeParse({ ...validValues, email: 'not-an-email' }) + expect(result.success).toBe(false) + }) + + it('rejects an impossible birth date', () => { + const result = personalDataSchema.safeParse({ ...validValues, birthDate: '31/02/2020' }) + expect(result.success).toBe(false) + }) +}) + +describe('addressSchema', () => { + it('rejects a malformed CEP', () => { + const result = addressSchema.safeParse({ ...validValues, cep: '01310100' }) + expect(result.success).toBe(false) + }) + + it('rejects an unknown UF', () => { + const result = addressSchema.safeParse({ ...validValues, estado: 'XX' }) + expect(result.success).toBe(false) + }) + + it('rejects an empty bairro', () => { + const result = addressSchema.safeParse({ ...validValues, bairro: '' }) + expect(result.success).toBe(false) + }) +}) + +describe('professionalSchema', () => { + it('rejects a zero salary', () => { + const result = professionalSchema.safeParse({ ...validValues, salario: 'R$ 0,00' }) + expect(result.success).toBe(false) + }) + + it('rejects a missing profession', () => { + const result = professionalSchema.safeParse({ ...validValues, profissao: '' }) + expect(result.success).toBe(false) + }) + + it('rejects a missing tempoDeEmpresa', () => { + const result = professionalSchema.safeParse({ ...validValues, tempoDeEmpresa: '' }) + expect(result.success).toBe(false) + }) +}) diff --git a/frontend/src/schemas/formSchema.ts b/frontend/src/schemas/formSchema.ts new file mode 100644 index 0000000..b49f098 --- /dev/null +++ b/frontend/src/schemas/formSchema.ts @@ -0,0 +1,81 @@ +import { z } from 'zod' +import { isValidCPF } from '../lib/cpf' +import { isValidBirthDate, parseCurrencyToNumber } from '../lib/masks' +import { UF_OPTIONS } from '../constants/options' + +const PHONE_REGEX = /^\(\d{2}\) \d{4,5}-\d{4}$/ +const CEP_REGEX = /^\d{5}-\d{3}$/ +const UF_CODES = UF_OPTIONS.map((option) => option.value) + +export const personalDataSchema = z.object({ + fullName: z + .string() + .trim() + .min(1, 'Informe o nome completo') + .refine((value) => value.trim().split(/\s+/).length >= 2, { + message: 'Informe nome e sobrenome', + }), + birthDate: z + .string() + .min(1, 'Informe a data de nascimento') + .refine(isValidBirthDate, { message: 'Data de nascimento inválida' }), + cpf: z + .string() + .min(1, 'Informe o CPF') + .refine(isValidCPF, { message: 'CPF inválido' }), + phone: z + .string() + .min(1, 'Informe o telefone') + .regex(PHONE_REGEX, 'Telefone inválido'), + email: z.string().min(1, 'Informe o e-mail').email('E-mail inválido'), +}) + +export const addressSchema = z.object({ + cep: z.string().min(1, 'Informe o CEP').regex(CEP_REGEX, 'CEP inválido'), + endereco: z.string().min(1, 'Informe o endereço'), + bairro: z.string().min(1, 'Informe o bairro'), + cidade: z.string().min(1, 'Informe a cidade'), + estado: z + .string() + .min(1, 'Informe o estado') + .refine((value) => UF_CODES.includes(value.toUpperCase()), { + message: 'Estado inválido', + }), +}) + +export const professionalSchema = z.object({ + empresa: z.string().min(1, 'Informe a empresa'), + profissao: z.string().min(1, 'Selecione uma profissão'), + salario: z + .string() + .min(1, 'Informe o salário') + .refine((value) => parseCurrencyToNumber(value) > 0, { + message: 'Informe um salário válido', + }), + tempoDeEmpresa: z.string().min(1, 'Selecione o tempo de empresa'), +}) + +export const formSchema = personalDataSchema.merge(addressSchema).merge(professionalSchema) + +export type FormValues = z.infer + +export const STEP1_FIELDS = Object.keys(personalDataSchema.shape) as (keyof FormValues)[] +export const STEP2_FIELDS = Object.keys(addressSchema.shape) as (keyof FormValues)[] +export const STEP3_FIELDS = Object.keys(professionalSchema.shape) as (keyof FormValues)[] + +export const defaultFormValues: FormValues = { + fullName: '', + birthDate: '', + cpf: '', + phone: '', + email: '', + cep: '', + endereco: '', + bairro: '', + cidade: '', + estado: '', + empresa: '', + profissao: '', + salario: '', + tempoDeEmpresa: '', +} diff --git a/frontend/src/test/renderWithForm.tsx b/frontend/src/test/renderWithForm.tsx new file mode 100644 index 0000000..aa64c30 --- /dev/null +++ b/frontend/src/test/renderWithForm.tsx @@ -0,0 +1,36 @@ +import type { ReactElement, ReactNode } from 'react' +import { render } from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' +import { FormProvider, useForm } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' +import { defaultFormValues, formSchema, type FormValues } from '../schemas/formSchema' + +function FormWrapper({ + children, + initialValues, +}: { + children: ReactNode + initialValues?: Partial +}) { + const methods = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { ...defaultFormValues, ...initialValues }, + }) + + return {children} +} + +interface RenderWithFormOptions { + initialValues?: Partial + initialPath?: string +} + +export function renderWithForm(ui: ReactElement, options: RenderWithFormOptions = {}) { + const { initialValues, initialPath = '/' } = options + + return render( + + {ui} + , + ) +} diff --git a/frontend/src/test/setup.ts b/frontend/src/test/setup.ts new file mode 100644 index 0000000..1555cbe --- /dev/null +++ b/frontend/src/test/setup.ts @@ -0,0 +1,8 @@ +import '@testing-library/jest-dom' +import { cleanup } from '@testing-library/react' +import { afterEach } from 'vitest' + +afterEach(() => { + cleanup() + window.localStorage.clear() +}) diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..6830b6f --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "allowArbitraryExtensions": true, + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..8455dcb --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "module": "nodenext", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..5f23a90 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,31 @@ +/// +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + globals: true, + setupFiles: ['./src/test/setup.ts'], + css: true, + coverage: { + provider: 'v8', + reporter: ['text', 'html', 'lcov'], + include: ['src/**/*.{ts,tsx}'], + exclude: [ + 'src/main.tsx', + 'src/vite-env.d.ts', + 'src/test/**', + 'src/**/*.d.ts', + ], + thresholds: { + lines: 80, + statements: 80, + functions: 80, + branches: 80, + }, + }, + }, +}) From 4b28318df1e9ea9e4a1854d9d458c048bd0a057b Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 12 Jul 2026 23:56:47 -0300 Subject: [PATCH 2/4] feat: add favicon --- frontend/public/favicon.svg | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg index 6893eb1..0e2eae1 100644 --- a/frontend/public/favicon.svg +++ b/frontend/public/favicon.svg @@ -1 +1,6 @@ - \ No newline at end of file + + + + + + From 99ab7ec04b93c767b7e38b389efac82ba52f6055 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 13 Jul 2026 00:09:46 -0300 Subject: [PATCH 3/4] feat: update css --- frontend/coverage/index.html | 2 +- frontend/coverage/lcov-report/index.html | 2 +- .../coverage/lcov-report/src/App.tsx.html | 2 +- .../components/form/CurrencyField.tsx.html | 13 +-- .../components/form/MaskedTextField.tsx.html | 2 +- .../src/components/form/SelectField.tsx.html | 2 +- .../src/components/form/TextField.tsx.html | 2 +- .../src/components/form/index.html | 2 +- .../src/components/layout/Layout.tsx.html | 2 +- .../src/components/layout/Logo.tsx.html | 2 +- .../src/components/layout/index.html | 2 +- .../components/wizard/StepIndicator.tsx.html | 2 +- .../components/wizard/SummarySection.tsx.html | 2 +- .../components/wizard/WizardActions.tsx.html | 2 +- .../wizard/WizardStepShell.tsx.html | 2 +- .../src/components/wizard/index.html | 2 +- .../lcov-report/src/constants/index.html | 2 +- .../lcov-report/src/constants/masks.ts.html | 2 +- .../lcov-report/src/constants/options.ts.html | 2 +- .../src/context/WizardFormProvider.tsx.html | 2 +- .../lcov-report/src/context/index.html | 2 +- .../coverage/lcov-report/src/hooks/index.html | 2 +- .../src/hooks/useRequireStepData.ts.html | 19 +---- .../src/hooks/useStepNavigation.ts.html | 2 +- frontend/coverage/lcov-report/src/index.html | 2 +- .../coverage/lcov-report/src/lib/api.ts.html | 16 +--- .../coverage/lcov-report/src/lib/cpf.ts.html | 2 +- .../coverage/lcov-report/src/lib/index.html | 2 +- .../lcov-report/src/lib/masks.ts.html | 2 +- .../coverage/lcov-report/src/lib/pdf.ts.html | 2 +- .../lcov-report/src/lib/storage.ts.html | 12 +-- .../src/pages/Step1PersonalData.tsx.html | 2 +- .../src/pages/Step2Address.tsx.html | 2 +- .../src/pages/Step3Professional.tsx.html | 2 +- .../src/pages/SummaryPage.tsx.html | 2 +- .../coverage/lcov-report/src/pages/index.html | 2 +- .../src/schemas/formSchema.ts.html | 2 +- .../lcov-report/src/schemas/index.html | 2 +- frontend/coverage/lcov.info | 78 +++++++++--------- frontend/coverage/src/App.tsx.html | 2 +- .../components/form/CurrencyField.tsx.html | 13 +-- .../components/form/MaskedTextField.tsx.html | 2 +- .../src/components/form/SelectField.tsx.html | 2 +- .../src/components/form/TextField.tsx.html | 2 +- .../coverage/src/components/form/index.html | 2 +- .../src/components/layout/Layout.tsx.html | 2 +- .../src/components/layout/Logo.tsx.html | 2 +- .../coverage/src/components/layout/index.html | 2 +- .../components/wizard/StepIndicator.tsx.html | 2 +- .../components/wizard/SummarySection.tsx.html | 2 +- .../components/wizard/WizardActions.tsx.html | 2 +- .../wizard/WizardStepShell.tsx.html | 2 +- .../coverage/src/components/wizard/index.html | 2 +- frontend/coverage/src/constants/index.html | 2 +- frontend/coverage/src/constants/masks.ts.html | 2 +- .../coverage/src/constants/options.ts.html | 2 +- .../src/context/WizardFormProvider.tsx.html | 2 +- frontend/coverage/src/context/index.html | 2 +- frontend/coverage/src/hooks/index.html | 2 +- .../src/hooks/useRequireStepData.ts.html | 19 +---- .../src/hooks/useStepNavigation.ts.html | 2 +- frontend/coverage/src/index.html | 2 +- frontend/coverage/src/lib/api.ts.html | 16 +--- frontend/coverage/src/lib/cpf.ts.html | 2 +- frontend/coverage/src/lib/index.html | 2 +- frontend/coverage/src/lib/masks.ts.html | 2 +- frontend/coverage/src/lib/pdf.ts.html | 2 +- frontend/coverage/src/lib/storage.ts.html | 12 +-- .../src/pages/Step1PersonalData.tsx.html | 2 +- .../coverage/src/pages/Step2Address.tsx.html | 2 +- .../src/pages/Step3Professional.tsx.html | 2 +- .../coverage/src/pages/SummaryPage.tsx.html | 2 +- frontend/coverage/src/pages/index.html | 2 +- .../coverage/src/schemas/formSchema.ts.html | 2 +- frontend/coverage/src/schemas/index.html | 2 +- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 41 ++++++++++ frontend/postcss.config.js | 5 ++ .../src/components/form/CurrencyField.tsx | 3 - frontend/src/constants/breakpoints.css | 2 + frontend/src/constants/colors.css | 18 +++++ frontend/src/constants/spacing.css | 14 ++++ frontend/src/hooks/useRequireStepData.ts | 5 -- frontend/src/index.css | 79 ++++++++----------- frontend/src/lib/api.ts | 4 - frontend/src/lib/storage.ts | 4 +- frontend/src/pages/SummaryPage.test.tsx | 1 - 87 files changed, 236 insertions(+), 271 deletions(-) create mode 100644 frontend/postcss.config.js create mode 100644 frontend/src/constants/breakpoints.css create mode 100644 frontend/src/constants/colors.css create mode 100644 frontend/src/constants/spacing.css diff --git a/frontend/coverage/index.html b/frontend/coverage/index.html index 895b5ed..73baa26 100644 --- a/frontend/coverage/index.html +++ b/frontend/coverage/index.html @@ -236,7 +236,7 @@

All files

- - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/base.css b/frontend/coverage/lcov-report/base.css deleted file mode 100644 index f418035..0000000 --- a/frontend/coverage/lcov-report/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/frontend/coverage/lcov-report/block-navigation.js b/frontend/coverage/lcov-report/block-navigation.js deleted file mode 100644 index 530d1ed..0000000 --- a/frontend/coverage/lcov-report/block-navigation.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; - - // Elements to highlight in the file listing view - var fileListingElements = ['td.pct.low']; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` - - // Selector that finds elements on the page to which we can jump - var selector = - fileListingElements.join(', ') + - ', ' + - notSelector + - missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements - .item(currentIndex) - .classList.remove('highlighted'); - missingCoverageElements.item(index).classList.add('highlighted'); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById('fileSearch') === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener('keydown', jumpToCode); diff --git a/frontend/coverage/lcov-report/favicon.png b/frontend/coverage/lcov-report/favicon.png deleted file mode 100644 index c1525b811a167671e9de1fa78aab9f5c0b61cef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> - - - - Code coverage report for All files - - - - - - - - - -
-
-

All files

-
- -
- 97.07% - Statements - 232/239 -
- - -
- 84.76% - Branches - 89/105 -
- - -
- 94.52% - Functions - 69/73 -
- - -
- 98.64% - Lines - 219/222 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
src -
-
100%1/1100%0/0100%1/1100%1/1
src/components/form -
-
100%21/2178.94%30/38100%10/10100%21/21
src/components/layout -
-
100%2/2100%0/0100%2/2100%2/2
src/components/wizard -
-
100%11/11100%14/14100%7/7100%10/10
src/constants -
-
100%8/8100%2/2100%2/2100%7/7
src/context -
-
100%7/7100%0/0100%4/4100%6/6
src/hooks -
-
100%12/12100%4/4100%5/5100%12/12
src/lib -
-
100%99/9993.54%29/31100%15/15100%93/93
src/pages -
-
88.88%56/6362.5%10/1682.6%19/2394.64%53/56
src/schemas -
-
100%15/15100%0/0100%4/4100%14/14
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/prettify.css b/frontend/coverage/lcov-report/prettify.css deleted file mode 100644 index b317a7c..0000000 --- a/frontend/coverage/lcov-report/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/frontend/coverage/lcov-report/prettify.js b/frontend/coverage/lcov-report/prettify.js deleted file mode 100644 index b322523..0000000 --- a/frontend/coverage/lcov-report/prettify.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/frontend/coverage/lcov-report/sort-arrow-sprite.png b/frontend/coverage/lcov-report/sort-arrow-sprite.png deleted file mode 100644 index 6ed68316eb3f65dec9063332d2f69bf3093bbfab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc diff --git a/frontend/coverage/lcov-report/sorter.js b/frontend/coverage/lcov-report/sorter.js deleted file mode 100644 index 4ed70ae..0000000 --- a/frontend/coverage/lcov-report/sorter.js +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { - return document.querySelector('.coverage-summary'); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector('thead tr'); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector('tbody'); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll('th')[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById('fileSearch').value; - const rows = document.getElementsByTagName('tbody')[0].children; - - // Try to create a RegExp from the searchValue. If it fails (invalid regex), - // it will be treated as a plain text search - let searchRegex; - try { - searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive - } catch (error) { - searchRegex = null; - } - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let isMatch = false; - - if (searchRegex) { - // If a valid regex was created, use it for matching - isMatch = searchRegex.test(row.textContent); - } else { - // Otherwise, fall back to the original plain text search - isMatch = row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()); - } - - row.style.display = isMatch ? '' : 'none'; - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById('filterTemplate'); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById('fileSearch').oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = - colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function(a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function(a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function() { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function() { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); diff --git a/frontend/coverage/lcov-report/src/App.tsx.html b/frontend/coverage/lcov-report/src/App.tsx.html deleted file mode 100644 index e95a1e7..0000000 --- a/frontend/coverage/lcov-report/src/App.tsx.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - Code coverage report for src/App.tsx - - - - - - - - - -
-
-

All files / src App.tsx

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'
-import { Layout } from './components/layout/Layout'
-import { WizardFormProvider } from './context/WizardFormProvider'
-import { Step1PersonalData } from './pages/Step1PersonalData'
-import { Step2Address } from './pages/Step2Address'
-import { Step3Professional } from './pages/Step3Professional'
-import { SummaryPage } from './pages/SummaryPage'
- 
-function App() {
-  return (
-    <BrowserRouter>
-      <WizardFormProvider>
-        <Layout>
-          <Routes>
-            <Route path="/" element={<Navigate to="/etapa-1" replace />} />
-            <Route path="/etapa-1" element={<Step1PersonalData />} />
-            <Route path="/etapa-2" element={<Step2Address />} />
-            <Route path="/etapa-3" element={<Step3Professional />} />
-            <Route path="/resumo" element={<SummaryPage />} />
-            <Route path="*" element={<Navigate to="/etapa-1" replace />} />
-          </Routes>
-        </Layout>
-      </WizardFormProvider>
-    </BrowserRouter>
-  )
-}
- 
-export default App
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/form/CurrencyField.tsx.html b/frontend/coverage/lcov-report/src/components/form/CurrencyField.tsx.html deleted file mode 100644 index d08505e..0000000 --- a/frontend/coverage/lcov-report/src/components/form/CurrencyField.tsx.html +++ /dev/null @@ -1,253 +0,0 @@ - - - - - - Code coverage report for src/components/form/CurrencyField.tsx - - - - - - - - - -
-
-

All files / src/components/form CurrencyField.tsx

-
- -
- 100% - Statements - 9/9 -
- - -
- 58.33% - Branches - 7/12 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 9/9 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57  -  -  -  -3x -  -  -30x -30x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -19x -  -19x -  -  -  -  -  -  -50x -  -  -  -  -  -  -  -31x -31x -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { Controller, useFormContext } from 'react-hook-form'
-import type { FormValues } from '../../schemas/formSchema'
-import { onlyDigits } from '../../lib/masks'
- 
-const NON_BREAKING_SPACE = ' '
- 
-function formatBRLFromDigits(digits: string): string {
-  const numeric = Number(digits || '0') / 100
-  return numeric
-    .toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })
-    .replaceAll(NON_BREAKING_SPACE, ' ')
-}
- 
-interface CurrencyFieldProps {
-  name: keyof FormValues
-  label: string
-  fullWidth?: boolean
-}
- 
-export function CurrencyField({ name, label, fullWidth }: Readonly<CurrencyFieldProps>) {
-  const {
-    control,
-    formState: { errors },
-  } = useFormContext<FormValues>()
-  const error = errors[name]?.message as string | undefined
- 
-  return (
-    <div className={`field${fullWidth ? ' field-full' : ''}`}>
-      <label htmlFor={name}>{label}</label>
-      <Controller
-        name={name}
-        control={control}
-        render={({ field }) => (
-          <input
-            id={name}
-            inputMode="decimal"
-            placeholder="R$ 0,00"
-            aria-invalid={!!error}
-            aria-describedby={error ? `${name}-error` : undefined}
-            value={field.value ?? ''}
-            onChange={(event) => {
-              const digits = onlyDigits(event.target.value).replace(/^0+/, '').slice(0, 12)
-              field.onChange(digits ? formatBRLFromDigits(digits) : '')
-            }}
-            onBlur={field.onBlur}
-          />
-        )}
-      />
-      {error && (
-        <span className="field-error" id={`${name}-error`} role="alert">
-          {error}
-        </span>
-      )}
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/form/MaskedTextField.tsx.html b/frontend/coverage/lcov-report/src/components/form/MaskedTextField.tsx.html deleted file mode 100644 index a40cc49..0000000 --- a/frontend/coverage/lcov-report/src/components/form/MaskedTextField.tsx.html +++ /dev/null @@ -1,265 +0,0 @@ - - - - - - Code coverage report for src/components/form/MaskedTextField.tsx - - - - - - - - - -
-
-

All files / src/components/form MaskedTextField.tsx

-
- -
- 100% - Statements - 5/5 -
- - -
- 90.9% - Branches - 10/11 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 5/5 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -88x -88x -  -88x -  -  -  -  -  -  -170x -  -  -  -  -  -  -  -  -  -114x -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { Controller, useFormContext } from 'react-hook-form'
-import { IMaskInput } from 'react-imask'
-import type { FormValues } from '../../schemas/formSchema'
- 
-interface MaskedTextFieldProps {
-  name: keyof FormValues
-  label: string
-  maskOptions: Record<string, unknown>
-  placeholder?: string
-  inputMode?: 'text' | 'numeric' | 'decimal'
-  hint?: string
-  fullWidth?: boolean
-}
- 
-export function MaskedTextField({
-  name,
-  label,
-  maskOptions,
-  placeholder,
-  inputMode,
-  hint,
-  fullWidth,
-}: Readonly<MaskedTextFieldProps>) {
-  const {
-    control,
-    formState: { errors },
-  } = useFormContext<FormValues>()
-  const error = errors[name]?.message as string | undefined
- 
-  return (
-    <div className={`field${fullWidth ? ' field-full' : ''}`}>
-      <label htmlFor={name}>{label}</label>
-      <Controller
-        name={name}
-        control={control}
-        render={({ field }) => (
-          <IMaskInput
-            {...maskOptions}
-            id={name}
-            name={field.name}
-            value={field.value ?? ''}
-            unmask={false}
-            inputMode={inputMode}
-            placeholder={placeholder}
-            aria-invalid={!!error}
-            aria-describedby={error ? `${name}-error` : undefined}
-            onAccept={(value: string) => field.onChange(value)}
-            onBlur={field.onBlur}
-          />
-        )}
-      />
-      {error && (
-        <span className="field-error" id={`${name}-error`} role="alert">
-          {error}
-        </span>
-      )}
-      {!error && hint && <span className="field-hint">{hint}</span>}
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/form/SelectField.tsx.html b/frontend/coverage/lcov-report/src/components/form/SelectField.tsx.html deleted file mode 100644 index 872c3b8..0000000 --- a/frontend/coverage/lcov-report/src/components/form/SelectField.tsx.html +++ /dev/null @@ -1,229 +0,0 @@ - - - - - - Code coverage report for src/components/form/SelectField.tsx - - - - - - - - - -
-
-

All files / src/components/form SelectField.tsx

-
- -
- 100% - Statements - 4/4 -
- - -
- 75% - Branches - 6/8 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 4/4 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -83x -83x -  -83x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1456x -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useFormContext } from 'react-hook-form'
-import type { FormValues } from '../../schemas/formSchema'
-import type { SelectOption } from '../../constants/options'
- 
-interface SelectFieldProps {
-  name: keyof FormValues
-  label: string
-  options: SelectOption[]
-  placeholder?: string
-  disabled?: boolean
-  fullWidth?: boolean
-}
- 
-export function SelectField({ name, label, options, placeholder, disabled, fullWidth }: Readonly<SelectFieldProps>) {
-  const {
-    register,
-    formState: { errors },
-  } = useFormContext<FormValues>()
-  const error = errors[name]?.message as string | undefined
- 
-  return (
-    <div className={`field${fullWidth ? ' field-full' : ''}`}>
-      <label htmlFor={name}>{label}</label>
-      <select
-        id={name}
-        disabled={disabled}
-        aria-invalid={!!error}
-        aria-describedby={error ? `${name}-error` : undefined}
-        defaultValue=""
-        {...register(name)}
-      >
-        <option value="" disabled>
-          {placeholder ?? 'Selecione...'}
-        </option>
-        {options.map((option) => (
-          <option key={option.value} value={option.value}>
-            {option.label}
-          </option>
-        ))}
-      </select>
-      {error && (
-        <span className="field-error" id={`${name}-error`} role="alert">
-          {error}
-        </span>
-      )}
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/form/TextField.tsx.html b/frontend/coverage/lcov-report/src/components/form/TextField.tsx.html deleted file mode 100644 index 1d57bc4..0000000 --- a/frontend/coverage/lcov-report/src/components/form/TextField.tsx.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - Code coverage report for src/components/form/TextField.tsx - - - - - - - - - -
-
-

All files / src/components/form TextField.tsx

-
- -
- 100% - Statements - 3/3 -
- - -
- 100% - Branches - 7/7 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -200x -200x -  -200x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useFormContext } from 'react-hook-form'
-import type { FormValues } from '../../schemas/formSchema'
- 
-interface TextFieldProps {
-  name: keyof FormValues
-  label: string
-  placeholder?: string
-  type?: string
-  autoComplete?: string
-  fullWidth?: boolean
-  readOnly?: boolean
-}
- 
-export function TextField({
-  name,
-  label,
-  placeholder,
-  type = 'text',
-  autoComplete,
-  fullWidth,
-  readOnly,
-}: Readonly<TextFieldProps>) {
-  const {
-    register,
-    formState: { errors },
-  } = useFormContext<FormValues>()
-  const error = errors[name]?.message as string | undefined
- 
-  return (
-    <div className={`field${fullWidth ? ' field-full' : ''}`}>
-      <label htmlFor={name}>{label}</label>
-      <input
-        id={name}
-        type={type}
-        placeholder={placeholder}
-        autoComplete={autoComplete}
-        readOnly={readOnly}
-        aria-invalid={!!error}
-        aria-describedby={error ? `${name}-error` : undefined}
-        {...register(name)}
-      />
-      {error && (
-        <span className="field-error" id={`${name}-error`} role="alert">
-          {error}
-        </span>
-      )}
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/form/index.html b/frontend/coverage/lcov-report/src/components/form/index.html deleted file mode 100644 index b3b7ba8..0000000 --- a/frontend/coverage/lcov-report/src/components/form/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for src/components/form - - - - - - - - - -
-
-

All files src/components/form

-
- -
- 100% - Statements - 21/21 -
- - -
- 78.94% - Branches - 30/38 -
- - -
- 100% - Functions - 10/10 -
- - -
- 100% - Lines - 21/21 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
CurrencyField.tsx -
-
100%9/958.33%7/12100%4/4100%9/9
MaskedTextField.tsx -
-
100%5/590.9%10/11100%3/3100%5/5
SelectField.tsx -
-
100%4/475%6/8100%2/2100%4/4
TextField.tsx -
-
100%3/3100%7/7100%1/1100%3/3
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/layout/Layout.tsx.html b/frontend/coverage/lcov-report/src/components/layout/Layout.tsx.html deleted file mode 100644 index a9a228c..0000000 --- a/frontend/coverage/lcov-report/src/components/layout/Layout.tsx.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - Code coverage report for src/components/layout/Layout.tsx - - - - - - - - - -
-
-

All files / src/components/layout Layout.tsx

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21  -  -  -  -  -3x -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import type { ReactNode } from 'react'
-import { Logo } from './Logo'
-import { StepIndicator } from '../wizard/StepIndicator'
- 
-export function Layout({ children }: { children: ReactNode }) {
-  return (
-    <div className="app-shell">
-      <header className="app-header">
-        <Logo />
-      </header>
-      <StepIndicator />
-      <main className="app-main">
-        <div className="card">{children}</div>
-      </main>
-      <footer className="app-footer">
-        <p>&copy; {new Date().getFullYear()} Verity. Todos os direitos reservados.</p>
-      </footer>
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/layout/Logo.tsx.html b/frontend/coverage/lcov-report/src/components/layout/Logo.tsx.html deleted file mode 100644 index 01803e8..0000000 --- a/frontend/coverage/lcov-report/src/components/layout/Logo.tsx.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - Code coverage report for src/components/layout/Logo.tsx - - - - - - - - - -
-
-

All files / src/components/layout Logo.tsx

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8  -4x -  -  -  -  -  - 
export function Logo() {
-  return (
-    <div className="logo-mark">
-      <img src="/logo.avif" alt="Verity" className="logo-image" />
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/layout/index.html b/frontend/coverage/lcov-report/src/components/layout/index.html deleted file mode 100644 index d66029b..0000000 --- a/frontend/coverage/lcov-report/src/components/layout/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for src/components/layout - - - - - - - - - -
-
-

All files src/components/layout

-
- -
- 100% - Statements - 2/2 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 2/2 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
Layout.tsx -
-
100%1/1100%0/0100%1/1100%1/1
Logo.tsx -
-
100%1/1100%0/0100%1/1100%1/1
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/wizard/StepIndicator.tsx.html b/frontend/coverage/lcov-report/src/components/wizard/StepIndicator.tsx.html deleted file mode 100644 index 18a0c0a..0000000 --- a/frontend/coverage/lcov-report/src/components/wizard/StepIndicator.tsx.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - Code coverage report for src/components/wizard/StepIndicator.tsx - - - - - - - - - -
-
-

All files / src/components/wizard StepIndicator.tsx

-
- -
- 100% - Statements - 7/7 -
- - -
- 100% - Branches - 6/6 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 6/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28  -  -3x -  -  -  -  -  -  -  -10x -26x -  -10x -  -  -40x -40x -  -  -  -  -  -  -  -  -  - 
import { useLocation } from 'react-router-dom'
- 
-const STEPS = [
-  { path: '/etapa-1', label: 'Dados Pessoais' },
-  { path: '/etapa-2', label: 'Endereço' },
-  { path: '/etapa-3', label: 'Profissional' },
-  { path: '/resumo', label: 'Resumo' },
-]
- 
-export function StepIndicator() {
-  const { pathname } = useLocation()
-  const currentIndex = STEPS.findIndex((step) => step.path === pathname)
- 
-  return (
-    <ol className="step-indicator">
-      {STEPS.map((step, index) => {
-        const status = index < currentIndex ? 'done' : index === currentIndex ? 'active' : 'pending'
-        return (
-          <li key={step.path} className={`step step-${status}`}>
-            <span className="step-index">{status === 'done' ? '✓' : index + 1}</span>
-            <span className="step-label">{step.label}</span>
-          </li>
-        )
-      })}
-    </ol>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/wizard/SummarySection.tsx.html b/frontend/coverage/lcov-report/src/components/wizard/SummarySection.tsx.html deleted file mode 100644 index aac3b8c..0000000 --- a/frontend/coverage/lcov-report/src/components/wizard/SummarySection.tsx.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - Code coverage report for src/components/wizard/SummarySection.tsx - - - - - - - - - -
-
-

All files / src/components/wizard SummarySection.tsx

-
- -
- 100% - Statements - 2/2 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 2/2 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32  -  -  -  -  -  -  -  -  -  -  -  -32x -  -  -  -  -  -  -  -  -  -142x -  -  -  -  -  -  -  -  - 
interface SummaryItem {
-  label: string
-  value: string
-}
- 
-interface SummarySectionProps {
-  title: string
-  items: SummaryItem[]
-  onEdit: () => void
-}
- 
-export function SummarySection({ title, items, onEdit }: Readonly<SummarySectionProps>) {
-  return (
-    <section className="summary-section">
-      <div className="summary-section-header">
-        <h3>{title}</h3>
-        <button type="button" className="btn-link" onClick={onEdit}>
-          Editar
-        </button>
-      </div>
-      <dl className="summary-list">
-        {items.map((item) => (
-          <div className="summary-row" key={item.label}>
-            <dt>{item.label}</dt>
-            <dd>{item.value || '—'}</dd>
-          </div>
-        ))}
-      </dl>
-    </section>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/wizard/WizardActions.tsx.html b/frontend/coverage/lcov-report/src/components/wizard/WizardActions.tsx.html deleted file mode 100644 index 585c08d..0000000 --- a/frontend/coverage/lcov-report/src/components/wizard/WizardActions.tsx.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - Code coverage report for src/components/wizard/WizardActions.tsx - - - - - - - - - -
-
-

All files / src/components/wizard WizardActions.tsx

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 4/4 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22  -  -  -  -  -  -  -  -77x -  -  -  -  -  -  -  -  -  -  -  -  - 
interface WizardActionsProps {
-  onBack?: () => void
-  onNext: () => void
-  nextLabel?: string
-  backLabel?: string
-}
- 
-export function WizardActions({ onBack, onNext, nextLabel = 'Próximo', backLabel = 'Voltar' }: Readonly<WizardActionsProps>) {
-  return (
-    <div className="wizard-actions">
-      {onBack && (
-        <button type="button" className="btn btn-secondary" onClick={onBack}>
-          {backLabel}
-        </button>
-      )}
-      <button type="button" className="btn btn-primary" onClick={onNext}>
-        {nextLabel}
-      </button>
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/wizard/WizardStepShell.tsx.html b/frontend/coverage/lcov-report/src/components/wizard/WizardStepShell.tsx.html deleted file mode 100644 index 3227422..0000000 --- a/frontend/coverage/lcov-report/src/components/wizard/WizardStepShell.tsx.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - Code coverage report for src/components/wizard/WizardStepShell.tsx - - - - - - - - - -
-
-

All files / src/components/wizard WizardStepShell.tsx

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18  -  -  -  -  -  -  -  -  -87x -  -  -  -  -  -  -  - 
import type { ReactNode } from 'react'
- 
-interface WizardStepShellProps {
-  title: string
-  description?: string
-  children: ReactNode
-}
- 
-export function WizardStepShell({ title, description, children }: Readonly<WizardStepShellProps>) {
-  return (
-    <section>
-      <h1 className="step-title">{title}</h1>
-      {description && <p className="step-description">{description}</p>}
-      {children}
-    </section>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/components/wizard/index.html b/frontend/coverage/lcov-report/src/components/wizard/index.html deleted file mode 100644 index 2322655..0000000 --- a/frontend/coverage/lcov-report/src/components/wizard/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for src/components/wizard - - - - - - - - - -
-
-

All files src/components/wizard

-
- -
- 100% - Statements - 11/11 -
- - -
- 100% - Branches - 14/14 -
- - -
- 100% - Functions - 7/7 -
- - -
- 100% - Lines - 10/10 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
StepIndicator.tsx -
-
100%7/7100%6/6100%3/3100%6/6
SummarySection.tsx -
-
100%2/2100%2/2100%2/2100%2/2
WizardActions.tsx -
-
100%1/1100%4/4100%1/1100%1/1
WizardStepShell.tsx -
-
100%1/1100%2/2100%1/1100%1/1
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/constants/index.html b/frontend/coverage/lcov-report/src/constants/index.html deleted file mode 100644 index b1b88c6..0000000 --- a/frontend/coverage/lcov-report/src/constants/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for src/constants - - - - - - - - - -
-
-

All files src/constants

-
- -
- 100% - Statements - 8/8 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 7/7 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
masks.ts -
-
100%4/4100%0/0100%0/0100%4/4
options.ts -
-
100%4/4100%2/2100%2/2100%3/3
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/constants/masks.ts.html b/frontend/coverage/lcov-report/src/constants/masks.ts.html deleted file mode 100644 index 539abc4..0000000 --- a/frontend/coverage/lcov-report/src/constants/masks.ts.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - Code coverage report for src/constants/masks.ts - - - - - - - - - -
-
-

All files / src/constants masks.ts

-
- -
- 100% - Statements - 4/4 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 0/0 -
- - -
- 100% - Lines - 4/4 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -104x -  -4x -  -4x -  -4x -  -  - 
export const CPF_MASK = { mask: '000.000.000-00' }
- 
-export const DATE_MASK = { mask: '00/00/0000' }
- 
-export const CEP_MASK = { mask: '00000-000' }
- 
-export const PHONE_MASK = {
-  mask: [{ mask: '(00) 0000-0000' }, { mask: '(00) 00000-0000' }],
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/constants/options.ts.html b/frontend/coverage/lcov-report/src/constants/options.ts.html deleted file mode 100644 index b6c375b..0000000 --- a/frontend/coverage/lcov-report/src/constants/options.ts.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - - - Code coverage report for src/constants/options.ts - - - - - - - - - -
-
-

All files / src/constants options.ts

-
- -
- 100% - Statements - 4/4 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47  -  -  -  -  -15x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -15x -  -  -  -  -  -  -  -  -407x -  - 
export interface SelectOption {
-  value: string
-  label: string
-}
- 
-export const UF_OPTIONS: SelectOption[] = [
-  { value: 'AC', label: 'Acre' },
-  { value: 'AL', label: 'Alagoas' },
-  { value: 'AP', label: 'Amapá' },
-  { value: 'AM', label: 'Amazonas' },
-  { value: 'BA', label: 'Bahia' },
-  { value: 'CE', label: 'Ceará' },
-  { value: 'DF', label: 'Distrito Federal' },
-  { value: 'ES', label: 'Espírito Santo' },
-  { value: 'GO', label: 'Goiás' },
-  { value: 'MA', label: 'Maranhão' },
-  { value: 'MT', label: 'Mato Grosso' },
-  { value: 'MS', label: 'Mato Grosso do Sul' },
-  { value: 'MG', label: 'Minas Gerais' },
-  { value: 'PA', label: 'Pará' },
-  { value: 'PB', label: 'Paraíba' },
-  { value: 'PR', label: 'Paraná' },
-  { value: 'PE', label: 'Pernambuco' },
-  { value: 'PI', label: 'Piauí' },
-  { value: 'RJ', label: 'Rio de Janeiro' },
-  { value: 'RN', label: 'Rio Grande do Norte' },
-  { value: 'RS', label: 'Rio Grande do Sul' },
-  { value: 'RO', label: 'Rondônia' },
-  { value: 'RR', label: 'Roraima' },
-  { value: 'SC', label: 'Santa Catarina' },
-  { value: 'SP', label: 'São Paulo' },
-  { value: 'SE', label: 'Sergipe' },
-  { value: 'TO', label: 'Tocantins' },
-]
- 
-export const TEMPO_EMPRESA_OPTIONS: SelectOption[] = [
-  { value: 'menos-6-meses', label: 'Menos de 6 meses' },
-  { value: '6-meses-1-ano', label: 'De 6 meses a 1 ano' },
-  { value: '1-2-anos', label: 'De 1 a 2 anos' },
-  { value: '2-5-anos', label: 'De 2 a 5 anos' },
-  { value: 'mais-5-anos', label: 'Mais de 5 anos' },
-]
- 
-export function getOptionLabel(options: SelectOption[], value: string): string {
-  return options.find((option) => option.value === value)?.label ?? value
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/context/WizardFormProvider.tsx.html b/frontend/coverage/lcov-report/src/context/WizardFormProvider.tsx.html deleted file mode 100644 index 4fd5b58..0000000 --- a/frontend/coverage/lcov-report/src/context/WizardFormProvider.tsx.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - Code coverage report for src/context/WizardFormProvider.tsx - - - - - - - - - -
-
-

All files / src/context WizardFormProvider.tsx

-
- -
- 100% - Statements - 7/7 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 6/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27  -  -  -  -  -  -  -17x -  -  -  -  -  -  -  -  -  -17x -5x -98x -  -5x -  -  -17x -  - 
import { useEffect, type ReactNode } from 'react'
-import { FormProvider, useForm } from 'react-hook-form'
-import { zodResolver } from '@hookform/resolvers/zod'
-import { defaultFormValues, formSchema, type FormValues } from '../schemas/formSchema'
-import { loadStoredFormValues, persistFormValues } from '../lib/storage'
- 
-export function WizardFormProvider({ children }: { children: ReactNode }) {
-  const methods = useForm<FormValues>({
-    resolver: zodResolver(formSchema),
-    mode: 'onSubmit',
-    reValidateMode: 'onChange',
-    defaultValues: {
-      ...defaultFormValues,
-      ...loadStoredFormValues<FormValues>(),
-    },
-  })
- 
-  useEffect(() => {
-    const subscription = methods.watch((values) => {
-      persistFormValues(values)
-    })
-    return () => subscription.unsubscribe()
-  }, [methods])
- 
-  return <FormProvider {...methods}>{children}</FormProvider>
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/context/index.html b/frontend/coverage/lcov-report/src/context/index.html deleted file mode 100644 index 28184ed..0000000 --- a/frontend/coverage/lcov-report/src/context/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for src/context - - - - - - - - - -
-
-

All files src/context

-
- -
- 100% - Statements - 7/7 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 6/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
WizardFormProvider.tsx -
-
100%7/7100%0/0100%4/4100%6/6
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/hooks/index.html b/frontend/coverage/lcov-report/src/hooks/index.html deleted file mode 100644 index 8e4fd4a..0000000 --- a/frontend/coverage/lcov-report/src/hooks/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for src/hooks - - - - - - - - - -
-
-

All files src/hooks

-
- -
- 100% - Statements - 12/12 -
- - -
- 100% - Branches - 4/4 -
- - -
- 100% - Functions - 5/5 -
- - -
- 100% - Lines - 12/12 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
useRequireStepData.ts -
-
100%5/5100%2/2100%2/2100%5/5
useStepNavigation.ts -
-
100%7/7100%2/2100%3/3100%7/7
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/hooks/useRequireStepData.ts.html b/frontend/coverage/lcov-report/src/hooks/useRequireStepData.ts.html deleted file mode 100644 index b5e97c3..0000000 --- a/frontend/coverage/lcov-report/src/hooks/useRequireStepData.ts.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - Code coverage report for src/hooks/useRequireStepData.ts - - - - - - - - - -
-
-

All files / src/hooks useRequireStepData.ts

-
- -
- 100% - Statements - 5/5 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 5/5 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16  -  -  -  -  -  -110x -110x -  -110x -16x -1x -  -  -  - 
import { useEffect } from 'react'
-import { useNavigate } from 'react-router-dom'
-import { useFormContext } from 'react-hook-form'
-import type { FormValues } from '../schemas/formSchema'
- 
-export function useRequireStepData(requiredField: keyof FormValues, redirectTo: string) {
-  const { getValues } = useFormContext<FormValues>()
-  const navigate = useNavigate()
- 
-  useEffect(() => {
-    if (!getValues(requiredField)) {
-      navigate(redirectTo, { replace: true })
-    }
-  }, [])
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/hooks/useStepNavigation.ts.html b/frontend/coverage/lcov-report/src/hooks/useStepNavigation.ts.html deleted file mode 100644 index d717c89..0000000 --- a/frontend/coverage/lcov-report/src/hooks/useStepNavigation.ts.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - Code coverage report for src/hooks/useStepNavigation.ts - - - - - - - - - -
-
-

All files / src/hooks useStepNavigation.ts

-
- -
- 100% - Statements - 7/7 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 7/7 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22  -  -  -  -  -115x -115x -  -  -9x -9x -7x -  -  -  -  -2x -  -  -115x -  - 
import { useNavigate } from 'react-router-dom'
-import { useFormContext } from 'react-hook-form'
-import type { FormValues } from '../schemas/formSchema'
- 
-export function useStepNavigation(fields: (keyof FormValues)[]) {
-  const { trigger } = useFormContext<FormValues>()
-  const navigate = useNavigate()
- 
-  async function goNext(path: string) {
-    const isValid = await trigger(fields)
-    if (isValid) {
-      navigate(path)
-    }
-  }
- 
-  function goBack(path: string) {
-    navigate(path)
-  }
- 
-  return { goNext, goBack }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/index.html b/frontend/coverage/lcov-report/src/index.html deleted file mode 100644 index 3d6d8ad..0000000 --- a/frontend/coverage/lcov-report/src/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for src - - - - - - - - - -
-
-

All files src

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
App.tsx -
-
100%1/1100%0/0100%1/1100%1/1
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/api.ts.html b/frontend/coverage/lcov-report/src/lib/api.ts.html deleted file mode 100644 index 50d484f..0000000 --- a/frontend/coverage/lcov-report/src/lib/api.ts.html +++ /dev/null @@ -1,265 +0,0 @@ - - - - - - Code coverage report for src/lib/api.ts - - - - - - - - - -
-
-

All files / src/lib api.ts

-
- -
- 100% - Statements - 17/17 -
- - -
- 83.33% - Branches - 5/6 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 15/15 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -4x -4x -1x -  -3x -  -  -  -  -3x -3x -2x -1x -  -  -  -  -  -  -1x -  -  -  -  -4x -4x -3x -  -  -  -  -  -  -  -  -1x -1x -  - 
import axios from 'axios'
- 
-export const mockApi = axios.create({
-  baseURL: import.meta.env.VITE_MOCK_API_URL ?? 'http://localhost:3001',
-})
- 
-export interface CepResult {
-  endereco: string
-  bairro: string
-  cidade: string
-  estado: string
-}
- 
-interface ViaCepResponse {
-  logradouro: string
-  bairro: string
-  localidade: string
-  uf: string
-  erro?: boolean
-}
- 
-async function fetchCepFromMock(cep: string): Promise<CepResult | null> {
-  try {
-    const { data } = await mockApi.get<CepResult>(`/ceps/${cep}`)
-    return data
-  } catch {
-    return null
-  }
-}
- 
-async function fetchCepFromViaCep(cep: string): Promise<CepResult | null> {
-  try {
-    const { data } = await axios.get<ViaCepResponse>(`https://viacep.com.br/ws/${cep}/json/`)
-    if (data.erro) return null
-    return {
-      endereco: data.logradouro,
-      bairro: data.bairro,
-      cidade: data.localidade,
-      estado: data.uf,
-    }
-  } catch {
-    return null
-  }
-}
- 
-export async function fetchAddressByCep(cep: string): Promise<CepResult | null> {
-  const mockResult = await fetchCepFromMock(cep)
-  if (mockResult) return mockResult
-  return fetchCepFromViaCep(cep)
-}
- 
-export interface Profession {
-  id: number | string
-  name: string
-}
- 
-export async function fetchProfessions(): Promise<Profession[]> {
-  const { data } = await mockApi.get<Profession[]>('/professions')
-  return data
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/cpf.ts.html b/frontend/coverage/lcov-report/src/lib/cpf.ts.html deleted file mode 100644 index dc861af..0000000 --- a/frontend/coverage/lcov-report/src/lib/cpf.ts.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - Code coverage report for src/lib/cpf.ts - - - - - - - - - -
-
-

All files / src/lib cpf.ts

-
- -
- 100% - Statements - 14/14 -
- - -
- 87.5% - Branches - 7/8 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 13/13 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27  -39x -  -  -  -34x -34x -323x -  -34x -34x -  -  -  -37x -  -37x -20x -  -  -17x -17x -17x -  -17x -  - 
export function onlyDigits(value: string): string {
-  return value.replace(/\D/g, '')
-}
- 
-function calcCheckDigit(digits: number[], length: number): number {
-  let sum = 0
-  for (let i = 0; i < length; i++) {
-    sum += digits[i] * (length + 1 - i)
-  }
-  const remainder = (sum * 10) % 11
-  return remainder === 10 ? 0 : remainder
-}
- 
-export function isValidCPF(rawValue: string): boolean {
-  const cpf = onlyDigits(rawValue)
- 
-  if (cpf.length !== 11 || /^(\d)\1{10}$/.test(cpf)) {
-    return false
-  }
- 
-  const digits = cpf.split('').map(Number)
-  const firstCheckDigit = calcCheckDigit(digits, 9)
-  const secondCheckDigit = calcCheckDigit(digits, 10)
- 
-  return firstCheckDigit === digits[9] && secondCheckDigit === digits[10]
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/index.html b/frontend/coverage/lcov-report/src/lib/index.html deleted file mode 100644 index 753e46e..0000000 --- a/frontend/coverage/lcov-report/src/lib/index.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - Code coverage report for src/lib - - - - - - - - - -
-
-

All files src/lib

-
- -
- 100% - Statements - 99/99 -
- - -
- 93.54% - Branches - 29/31 -
- - -
- 100% - Functions - 15/15 -
- - -
- 100% - Lines - 93/93 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
api.ts -
-
100%17/1783.33%5/6100%4/4100%15/15
cpf.ts -
-
100%14/1487.5%7/8100%3/3100%13/13
masks.ts -
-
100%17/17100%11/11100%3/3100%15/15
pdf.ts -
-
100%41/41100%4/4100%2/2100%41/41
storage.ts -
-
100%10/10100%2/2100%3/3100%9/9
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/masks.ts.html b/frontend/coverage/lcov-report/src/lib/masks.ts.html deleted file mode 100644 index fe4a685..0000000 --- a/frontend/coverage/lcov-report/src/lib/masks.ts.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - Code coverage report for src/lib/masks.ts - - - - - - - - - -
-
-

All files / src/lib masks.ts

-
- -
- 100% - Statements - 17/17 -
- - -
- 100% - Branches - 11/11 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 15/15 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32  -69x -  -  -  -37x -37x -  -21x -21x -21x -21x -  -  -21x -  -  -  -37x -  -17x -17x -  -17x -  -  -  -31x -31x -31x -  - 
export function onlyDigits(value: string): string {
-  return value.replace(/\D/g, '')
-}
- 
-export function isValidBirthDate(value: string): boolean {
-  const match = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value)
-  if (!match) return false
- 
-  const day = Number(match[1])
-  const month = Number(match[2])
-  const year = Number(match[3])
-  const date = new Date(year, month - 1, day)
- 
-  const isRealCalendarDate =
-    date.getFullYear() === year &&
-    date.getMonth() === month - 1 &&
-    date.getDate() === day
- 
-  if (!isRealCalendarDate) return false
- 
-  const today = new Date()
-  today.setHours(0, 0, 0, 0)
- 
-  return date <= today && year >= 1900
-}
- 
-export function parseCurrencyToNumber(value: string): number {
-  const normalized = value.replace(/[^\d,-]/g, '').replace(',', '.')
-  const parsed = Number.parseFloat(normalized)
-  return Number.isNaN(parsed) ? 0 : parsed
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/pdf.ts.html b/frontend/coverage/lcov-report/src/lib/pdf.ts.html deleted file mode 100644 index 1fd3fad..0000000 --- a/frontend/coverage/lcov-report/src/lib/pdf.ts.html +++ /dev/null @@ -1,370 +0,0 @@ - - - - - - Code coverage report for src/lib/pdf.ts - - - - - - - - - -
-
-

All files / src/lib pdf.ts

-
- -
- 100% - Statements - 41/41 -
- - -
- 100% - Branches - 4/4 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 41/41 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96  -  -  -  -1x -1x -1x -1x -  -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -2x -  -2x -2x -2x -2x -2x -2x -2x -2x -2x -  -2x -  -2x -6x -6x -6x -6x -6x -6x -6x -6x -  -6x -6x -28x -28x -28x -28x -28x -28x -  -  -6x -  -  -2x -2x -2x -2x -  -2x -2x -  - 
import { jsPDF } from 'jspdf'
-import type { FormValues } from '../schemas/formSchema'
-import { getOptionLabel, TEMPO_EMPRESA_OPTIONS, UF_OPTIONS } from '../constants/options'
- 
-const VERITY_BLUE: [number, number, number] = [13, 71, 161]
-const VERITY_BLUE_DARK: [number, number, number] = [8, 43, 99]
-const TEXT_GRAY: [number, number, number] = [60, 68, 82]
-const MARGIN_X = 16
- 
-interface Section {
-  title: string
-  rows: [string, string][]
-}
- 
-function buildSections(data: FormValues): Section[] {
-  return [
-    {
-      title: 'Dados Pessoais',
-      rows: [
-        ['Nome completo', data.fullName],
-        ['Data de nascimento', data.birthDate],
-        ['CPF', data.cpf],
-        ['Telefone', data.phone],
-        ['E-mail', data.email],
-      ],
-    },
-    {
-      title: 'Informações Residenciais',
-      rows: [
-        ['CEP', data.cep],
-        ['Endereço', data.endereco],
-        ['Bairro', data.bairro],
-        ['Cidade', data.cidade],
-        ['Estado', getOptionLabel(UF_OPTIONS, data.estado)],
-      ],
-    },
-    {
-      title: 'Informações Profissionais',
-      rows: [
-        ['Empresa', data.empresa],
-        ['Profissão', data.profissao],
-        ['Salário', data.salario],
-        ['Tempo de empresa', getOptionLabel(TEMPO_EMPRESA_OPTIONS, data.tempoDeEmpresa)],
-      ],
-    },
-  ]
-}
- 
-export function generateSummaryPdf(data: FormValues): void {
-  const doc = new jsPDF()
-  const pageWidth = doc.internal.pageSize.getWidth()
- 
-  doc.setFillColor(...VERITY_BLUE)
-  doc.rect(0, 0, pageWidth, 30, 'F')
-  doc.setTextColor(255, 255, 255)
-  doc.setFont('helvetica', 'bold')
-  doc.setFontSize(18)
-  doc.text('verity', MARGIN_X, 19)
-  doc.setFontSize(11)
-  doc.setFont('helvetica', 'normal')
-  doc.text('Resumo do Cadastro', pageWidth - MARGIN_X, 19, { align: 'right' })
- 
-  let cursorY = 44
- 
-  for (const section of buildSections(data)) {
-    doc.setTextColor(...VERITY_BLUE_DARK)
-    doc.setFont('helvetica', 'bold')
-    doc.setFontSize(13)
-    doc.text(section.title, MARGIN_X, cursorY)
-    cursorY += 3
-    doc.setDrawColor(...VERITY_BLUE)
-    doc.line(MARGIN_X, cursorY, pageWidth - MARGIN_X, cursorY)
-    cursorY += 8
- 
-    doc.setFontSize(11)
-    for (const [label, value] of section.rows) {
-      doc.setFont('helvetica', 'bold')
-      doc.setTextColor(...TEXT_GRAY)
-      doc.text(`${label}:`, MARGIN_X, cursorY)
-      doc.setFont('helvetica', 'normal')
-      doc.text(value || '—', MARGIN_X + 55, cursorY)
-      cursorY += 7
-    }
- 
-    cursorY += 6
-  }
- 
-  const generatedAt = new Date().toLocaleString('pt-BR')
-  doc.setFontSize(9)
-  doc.setTextColor(...TEXT_GRAY)
-  doc.text(`Gerado em ${generatedAt}`, MARGIN_X, doc.internal.pageSize.getHeight() - 10)
- 
-  const fileSuffix = data.fullName.trim().toLowerCase().replace(/\s+/g, '-') || 'cadastro'
-  doc.save(`verity-cadastro-${fileSuffix}.pdf`)
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/lib/storage.ts.html b/frontend/coverage/lcov-report/src/lib/storage.ts.html deleted file mode 100644 index 11517c2..0000000 --- a/frontend/coverage/lcov-report/src/lib/storage.ts.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - Code coverage report for src/lib/storage.ts - - - - - - - - - -
-
-

All files / src/lib storage.ts

-
- -
- 100% - Statements - 10/10 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 9/9 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -223x -  -  -21x -21x -21x -11x -  -1x -  -  -  -  -102x -102x -  -  -  -  -1x -  - 
const STORAGE_KEY = 'verity:cadastro-wizard'
- 
-export function loadStoredFormValues<T>(): Partial<T> | undefined {
-  try {
-    const raw = window.localStorage.getItem(STORAGE_KEY)
-    if (!raw) return undefined
-    return JSON.parse(raw) as Partial<T>
-  } catch {
-    return undefined
-  }
-}
- 
-export function persistFormValues(values: unknown): void {
-  try {
-    window.localStorage.setItem(STORAGE_KEY, JSON.stringify(values))
-  } catch {}
-}
- 
-export function clearStoredFormValues(): void {
-  window.localStorage.removeItem(STORAGE_KEY)
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/pages/Step1PersonalData.tsx.html b/frontend/coverage/lcov-report/src/pages/Step1PersonalData.tsx.html deleted file mode 100644 index 8c27412..0000000 --- a/frontend/coverage/lcov-report/src/pages/Step1PersonalData.tsx.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - Code coverage report for src/pages/Step1PersonalData.tsx - - - - - - - - - -
-
-

All files / src/pages Step1PersonalData.tsx

-
- -
- 100% - Statements - 3/3 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37  -  -  -  -  -  -  -  -  -11x -  -11x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -3x -  -  -  - 
import { TextField } from '../components/form/TextField'
-import { MaskedTextField } from '../components/form/MaskedTextField'
-import { WizardActions } from '../components/wizard/WizardActions'
-import { WizardStepShell } from '../components/wizard/WizardStepShell'
-import { useStepNavigation } from '../hooks/useStepNavigation'
-import { STEP1_FIELDS } from '../schemas/formSchema'
-import { CPF_MASK, DATE_MASK, PHONE_MASK } from '../constants/masks'
- 
-export function Step1PersonalData() {
-  const { goNext } = useStepNavigation(STEP1_FIELDS)
- 
-  return (
-    <WizardStepShell title="Dados Pessoais" description="Preencha suas informações pessoais para começar.">
-      <div className="field-grid">
-        <TextField name="fullName" label="Nome completo" autoComplete="name" fullWidth />
-        <MaskedTextField
-          name="birthDate"
-          label="Data de nascimento"
-          maskOptions={DATE_MASK}
-          placeholder="dd/mm/aaaa"
-          inputMode="numeric"
-        />
-        <MaskedTextField name="cpf" label="CPF" maskOptions={CPF_MASK} placeholder="000.000.000-00" inputMode="numeric" />
-        <MaskedTextField
-          name="phone"
-          label="Telefone"
-          maskOptions={PHONE_MASK}
-          placeholder="(00) 00000-0000"
-          inputMode="numeric"
-        />
-        <TextField name="email" label="E-mail" type="email" autoComplete="email" />
-      </div>
-      <WizardActions onNext={() => goNext('/etapa-2')} />
-    </WizardStepShell>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/pages/Step2Address.tsx.html b/frontend/coverage/lcov-report/src/pages/Step2Address.tsx.html deleted file mode 100644 index ea309f0..0000000 --- a/frontend/coverage/lcov-report/src/pages/Step2Address.tsx.html +++ /dev/null @@ -1,328 +0,0 @@ - - - - - - Code coverage report for src/pages/Step2Address.tsx - - - - - - - - - -
-
-

All files / src/pages Step2Address.tsx

-
- -
- 96.42% - Statements - 27/28 -
- - -
- 75% - Branches - 6/8 -
- - -
- 100% - Functions - 6/6 -
- - -
- 100% - Lines - 26/26 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -83x -83x -83x -83x -83x -  -83x -37x -37x -33x -33x -  -  -4x -4x -  -4x -4x -4x -3x -3x -3x -3x -3x -  -1x -  -  -  -4x -4x -  -  -  -83x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  - 
import { useEffect, useState } from 'react'
-import { useFormContext, useWatch } from 'react-hook-form'
-import { TextField } from '../components/form/TextField'
-import { SelectField } from '../components/form/SelectField'
-import { MaskedTextField } from '../components/form/MaskedTextField'
-import { WizardActions } from '../components/wizard/WizardActions'
-import { WizardStepShell } from '../components/wizard/WizardStepShell'
-import { useStepNavigation } from '../hooks/useStepNavigation'
-import { useRequireStepData } from '../hooks/useRequireStepData'
-import { STEP2_FIELDS, type FormValues } from '../schemas/formSchema'
-import { CEP_MASK } from '../constants/masks'
-import { UF_OPTIONS } from '../constants/options'
-import { fetchAddressByCep } from '../lib/api'
-import { onlyDigits } from '../lib/masks'
- 
-type CepStatus = 'idle' | 'loading' | 'success' | 'not-found'
- 
-const CEP_STATUS_HINTS: Partial<Record<CepStatus, string>> = {
-  loading: 'Buscando endereço...',
-  'not-found': 'CEP não encontrado, preencha manualmente.',
-}
- 
-export function Step2Address() {
-  useRequireStepData('fullName', '/etapa-1')
-  const { control, setValue } = useFormContext<FormValues>()
-  const { goNext, goBack } = useStepNavigation(STEP2_FIELDS)
-  const [cepStatus, setCepStatus] = useState<CepStatus>('idle')
-  const cep = useWatch({ control, name: 'cep' })
- 
-  useEffect(() => {
-    const digits = onlyDigits(cep ?? '')
-    if (digits.length !== 8) {
-      setCepStatus('idle')
-      return
-    }
- 
-    let cancelled = false
-    setCepStatus('loading')
- 
-    fetchAddressByCep(digits).then((result) => {
-      Iif (cancelled) return
-      if (result) {
-        setValue('endereco', result.endereco, { shouldValidate: true })
-        setValue('bairro', result.bairro, { shouldValidate: true })
-        setValue('cidade', result.cidade, { shouldValidate: true })
-        setValue('estado', result.estado.toUpperCase(), { shouldValidate: true })
-        setCepStatus('success')
-      } else {
-        setCepStatus('not-found')
-      }
-    })
- 
-    return () => {
-      cancelled = true
-    }
-  }, [cep, setValue])
- 
-  return (
-    <WizardStepShell
-      title="Informações Residenciais"
-      description="Informe o CEP para preenchermos o endereço automaticamente."
-    >
-      <div className="field-grid">
-        <MaskedTextField
-          name="cep"
-          label="CEP"
-          maskOptions={CEP_MASK}
-          placeholder="00000-000"
-          inputMode="numeric"
-          fullWidth
-          hint={CEP_STATUS_HINTS[cepStatus]}
-        />
-        <TextField name="endereco" label="Endereço" fullWidth />
-        <TextField name="bairro" label="Bairro" />
-        <TextField name="cidade" label="Cidade" />
-        <SelectField name="estado" label="Estado" options={UF_OPTIONS} placeholder="UF" />
-      </div>
-      <WizardActions onBack={() => goBack('/etapa-1')} onNext={() => goNext('/etapa-3')} />
-    </WizardStepShell>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/pages/Step3Professional.tsx.html b/frontend/coverage/lcov-report/src/pages/Step3Professional.tsx.html deleted file mode 100644 index 7434118..0000000 --- a/frontend/coverage/lcov-report/src/pages/Step3Professional.tsx.html +++ /dev/null @@ -1,253 +0,0 @@ - - - - - - Code coverage report for src/pages/Step3Professional.tsx - - - - - - - - - -
-
-

All files / src/pages Step3Professional.tsx

-
- -
- 80% - Statements - 16/20 -
- - -
- 50% - Branches - 4/8 -
- - -
- 77.77% - Functions - 7/9 -
- - -
- 93.33% - Lines - 14/15 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57  -  -  -  -  -  -  -  -  -  -  -  -  -13x -13x -13x -13x -  -13x -4x -  -4x -  -4x -4x -  -  -  -  -  -4x -  -  -4x -4x -  -  -  -13x -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  - 
import { useEffect, useState } from 'react'
-import { TextField } from '../components/form/TextField'
-import { SelectField } from '../components/form/SelectField'
-import { CurrencyField } from '../components/form/CurrencyField'
-import { WizardActions } from '../components/wizard/WizardActions'
-import { WizardStepShell } from '../components/wizard/WizardStepShell'
-import { useStepNavigation } from '../hooks/useStepNavigation'
-import { useRequireStepData } from '../hooks/useRequireStepData'
-import { STEP3_FIELDS } from '../schemas/formSchema'
-import { TEMPO_EMPRESA_OPTIONS, type SelectOption } from '../constants/options'
-import { fetchProfessions } from '../lib/api'
- 
-export function Step3Professional() {
-  useRequireStepData('endereco', '/etapa-2')
-  const { goNext, goBack } = useStepNavigation(STEP3_FIELDS)
-  const [professionOptions, setProfessionOptions] = useState<SelectOption[]>([])
-  const [loadingProfessions, setLoadingProfessions] = useState(true)
- 
-  useEffect(() => {
-    let cancelled = false
- 
-    fetchProfessions()
-      .then((professions) => {
-        Iif (cancelled) return
-        setProfessionOptions(professions.map((p) => ({ value: p.name, label: p.name })))
-      })
-      .catch(() => {
-        if (!cancelled) setProfessionOptions([])
-      })
-      .finally(() => {
-        Eif (!cancelled) setLoadingProfessions(false)
-      })
- 
-    return () => {
-      cancelled = true
-    }
-  }, [])
- 
-  return (
-    <WizardStepShell title="Informações Profissionais" description="Conte-nos um pouco sobre sua vida profissional.">
-      <div className="field-grid">
-        <TextField name="empresa" label="Empresa" fullWidth />
-        <SelectField
-          name="profissao"
-          label="Profissão"
-          options={professionOptions}
-          placeholder={loadingProfessions ? 'Carregando profissões...' : 'Selecione...'}
-          disabled={loadingProfessions}
-        />
-        <CurrencyField name="salario" label="Salário" />
-        <SelectField name="tempoDeEmpresa" label="Tempo de empresa" options={TEMPO_EMPRESA_OPTIONS} fullWidth />
-      </div>
-      <WizardActions onBack={() => goBack('/etapa-2')} onNext={() => goNext('/resumo')} nextLabel="Ver resumo" />
-    </WizardStepShell>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/pages/SummaryPage.tsx.html b/frontend/coverage/lcov-report/src/pages/SummaryPage.tsx.html deleted file mode 100644 index d182655..0000000 --- a/frontend/coverage/lcov-report/src/pages/SummaryPage.tsx.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - Code coverage report for src/pages/SummaryPage.tsx - - - - - - - - - -
-
-

All files / src/pages SummaryPage.tsx

-
- -
- 83.33% - Statements - 10/12 -
- - -
- 100% - Branches - 0/0 -
- - -
- 66.66% - Functions - 4/6 -
- - -
- 83.33% - Lines - 10/12 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72  -  -  -  -  -  -  -  -  -  -  -10x -10x -10x -10x -  -  -2x -  -  -  -1x -1x -1x -  -  -10x -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useNavigate } from 'react-router-dom'
-import { useFormContext } from 'react-hook-form'
-import { WizardStepShell } from '../components/wizard/WizardStepShell'
-import { SummarySection } from '../components/wizard/SummarySection'
-import { useRequireStepData } from '../hooks/useRequireStepData'
-import { defaultFormValues, type FormValues } from '../schemas/formSchema'
-import { getOptionLabel, TEMPO_EMPRESA_OPTIONS, UF_OPTIONS } from '../constants/options'
-import { generateSummaryPdf } from '../lib/pdf'
-import { clearStoredFormValues } from '../lib/storage'
- 
-export function SummaryPage() {
-  useRequireStepData('profissao', '/etapa-3')
-  const { getValues, reset } = useFormContext<FormValues>()
-  const navigate = useNavigate()
-  const values = getValues()
- 
-  function handleExport() {
-    generateSummaryPdf(values)
-  }
- 
-  function handleNewRegistration() {
-    clearStoredFormValues()
-    reset(defaultFormValues)
-    navigate('/etapa-1')
-  }
- 
-  return (
-    <WizardStepShell title="Resumo do Cadastro" description="Confira seus dados antes de exportar o PDF.">
-      <SummarySection
-        title="Dados Pessoais"
-        onEdit={() => navigate('/etapa-1')}
-        items={[
-          { label: 'Nome completo', value: values.fullName },
-          { label: 'Data de nascimento', value: values.birthDate },
-          { label: 'CPF', value: values.cpf },
-          { label: 'Telefone', value: values.phone },
-          { label: 'E-mail', value: values.email },
-        ]}
-      />
-      <SummarySection
-        title="Informações Residenciais"
-        onEdit={() => navigate('/etapa-2')}
-        items={[
-          { label: 'CEP', value: values.cep },
-          { label: 'Endereço', value: values.endereco },
-          { label: 'Bairro', value: values.bairro },
-          { label: 'Cidade', value: values.cidade },
-          { label: 'Estado', value: getOptionLabel(UF_OPTIONS, values.estado) },
-        ]}
-      />
-      <SummarySection
-        title="Informações Profissionais"
-        onEdit={() => navigate('/etapa-3')}
-        items={[
-          { label: 'Empresa', value: values.empresa },
-          { label: 'Profissão', value: values.profissao },
-          { label: 'Salário', value: values.salario },
-          { label: 'Tempo de empresa', value: getOptionLabel(TEMPO_EMPRESA_OPTIONS, values.tempoDeEmpresa) },
-        ]}
-      />
-      <div className="wizard-actions">
-        <button type="button" className="btn btn-secondary" onClick={handleNewRegistration}>
-          Novo cadastro
-        </button>
-        <button type="button" className="btn btn-primary" onClick={handleExport}>
-          Exportar PDF
-        </button>
-      </div>
-    </WizardStepShell>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/pages/index.html b/frontend/coverage/lcov-report/src/pages/index.html deleted file mode 100644 index 5aeefe3..0000000 --- a/frontend/coverage/lcov-report/src/pages/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for src/pages - - - - - - - - - -
-
-

All files src/pages

-
- -
- 88.88% - Statements - 56/63 -
- - -
- 62.5% - Branches - 10/16 -
- - -
- 82.6% - Functions - 19/23 -
- - -
- 94.64% - Lines - 53/56 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
Step1PersonalData.tsx -
-
100%3/3100%0/0100%2/2100%3/3
Step2Address.tsx -
-
96.42%27/2875%6/8100%6/6100%26/26
Step3Professional.tsx -
-
80%16/2050%4/877.77%7/993.33%14/15
SummaryPage.tsx -
-
83.33%10/12100%0/066.66%4/683.33%10/12
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/schemas/formSchema.ts.html b/frontend/coverage/lcov-report/src/schemas/formSchema.ts.html deleted file mode 100644 index 22080a6..0000000 --- a/frontend/coverage/lcov-report/src/schemas/formSchema.ts.html +++ /dev/null @@ -1,328 +0,0 @@ - - - - - - Code coverage report for src/schemas/formSchema.ts - - - - - - - - - -
-
-

All files / src/schemas formSchema.ts

-
- -
- 100% - Statements - 15/15 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 14/14 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82  -  -  -  -  -13x -13x -351x -  -13x -  -  -  -  -29x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -13x -  -  -  -  -  -  -  -26x -  -  -  -  -13x -  -  -  -  -  -26x -  -  -  -  -  -13x -  -  -  -13x -13x -13x -  -13x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { z } from 'zod'
-import { isValidCPF } from '../lib/cpf'
-import { isValidBirthDate, parseCurrencyToNumber } from '../lib/masks'
-import { UF_OPTIONS } from '../constants/options'
- 
-const PHONE_REGEX = /^\(\d{2}\) \d{4,5}-\d{4}$/
-const CEP_REGEX = /^\d{5}-\d{3}$/
-const UF_CODES = UF_OPTIONS.map((option) => option.value)
- 
-export const personalDataSchema = z.object({
-  fullName: z
-    .string()
-    .trim()
-    .min(1, 'Informe o nome completo')
-    .refine((value) => value.trim().split(/\s+/).length >= 2, {
-      message: 'Informe nome e sobrenome',
-    }),
-  birthDate: z
-    .string()
-    .min(1, 'Informe a data de nascimento')
-    .refine(isValidBirthDate, { message: 'Data de nascimento inválida' }),
-  cpf: z
-    .string()
-    .min(1, 'Informe o CPF')
-    .refine(isValidCPF, { message: 'CPF inválido' }),
-  phone: z
-    .string()
-    .min(1, 'Informe o telefone')
-    .regex(PHONE_REGEX, 'Telefone inválido'),
-  email: z.string().min(1, 'Informe o e-mail').email('E-mail inválido'),
-})
- 
-export const addressSchema = z.object({
-  cep: z.string().min(1, 'Informe o CEP').regex(CEP_REGEX, 'CEP inválido'),
-  endereco: z.string().min(1, 'Informe o endereço'),
-  bairro: z.string().min(1, 'Informe o bairro'),
-  cidade: z.string().min(1, 'Informe a cidade'),
-  estado: z
-    .string()
-    .min(1, 'Informe o estado')
-    .refine((value) => UF_CODES.includes(value.toUpperCase()), {
-      message: 'Estado inválido',
-    }),
-})
- 
-export const professionalSchema = z.object({
-  empresa: z.string().min(1, 'Informe a empresa'),
-  profissao: z.string().min(1, 'Selecione uma profissão'),
-  salario: z
-    .string()
-    .min(1, 'Informe o salário')
-    .refine((value) => parseCurrencyToNumber(value) > 0, {
-      message: 'Informe um salário válido',
-    }),
-  tempoDeEmpresa: z.string().min(1, 'Selecione o tempo de empresa'),
-})
- 
-export const formSchema = personalDataSchema.merge(addressSchema).merge(professionalSchema)
- 
-export type FormValues = z.infer<typeof formSchema>
- 
-export const STEP1_FIELDS = Object.keys(personalDataSchema.shape) as (keyof FormValues)[]
-export const STEP2_FIELDS = Object.keys(addressSchema.shape) as (keyof FormValues)[]
-export const STEP3_FIELDS = Object.keys(professionalSchema.shape) as (keyof FormValues)[]
- 
-export const defaultFormValues: FormValues = {
-  fullName: '',
-  birthDate: '',
-  cpf: '',
-  phone: '',
-  email: '',
-  cep: '',
-  endereco: '',
-  bairro: '',
-  cidade: '',
-  estado: '',
-  empresa: '',
-  profissao: '',
-  salario: '',
-  tempoDeEmpresa: '',
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov-report/src/schemas/index.html b/frontend/coverage/lcov-report/src/schemas/index.html deleted file mode 100644 index d5757c9..0000000 --- a/frontend/coverage/lcov-report/src/schemas/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for src/schemas - - - - - - - - - -
-
-

All files src/schemas

-
- -
- 100% - Statements - 15/15 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 14/14 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
formSchema.ts -
-
100%15/15100%0/0100%4/4100%14/14
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/lcov.info b/frontend/coverage/lcov.info deleted file mode 100644 index 0930e54..0000000 --- a/frontend/coverage/lcov.info +++ /dev/null @@ -1,707 +0,0 @@ -TN: -SF:src/App.tsx -FN:9,App -FNF:1 -FNH:1 -FNDA:2,App -DA:10,2 -LF:1 -LH:1 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/components/form/CurrencyField.tsx -FN:7,formatBRLFromDigits -FN:20,CurrencyField -FN:33,(anonymous_2) -FN:41,(anonymous_3) -FNF:4 -FNH:4 -FNDA:30,formatBRLFromDigits -FNDA:19,CurrencyField -FNDA:50,(anonymous_2) -FNDA:31,(anonymous_3) -DA:5,3 -DA:8,30 -DA:9,30 -DA:24,19 -DA:25,19 -DA:27,19 -DA:34,50 -DA:42,31 -DA:43,31 -LF:9 -LH:9 -BRDA:8,0,0,30 -BRDA:8,0,1,0 -BRDA:28,1,0,0 -BRDA:28,1,1,19 -BRDA:39,2,0,0 -BRDA:39,2,1,50 -BRDA:40,3,0,50 -BRDA:40,3,1,0 -BRDA:43,4,0,30 -BRDA:43,4,1,1 -BRDA:49,5,0,19 -BRDA:49,5,1,0 -BRF:12 -BRH:7 -end_of_record -TN: -SF:src/components/form/MaskedTextField.tsx -FN:15,MaskedTextField -FN:36,(anonymous_1) -FN:47,(anonymous_2) -FNF:3 -FNH:3 -FNDA:88,MaskedTextField -FNDA:170,(anonymous_1) -FNDA:114,(anonymous_2) -DA:27,88 -DA:28,88 -DA:30,88 -DA:37,170 -DA:47,114 -LF:5 -LH:5 -BRDA:31,0,0,51 -BRDA:31,0,1,37 -BRDA:41,1,0,170 -BRDA:41,1,1,0 -BRDA:46,2,0,3 -BRDA:46,2,1,167 -BRDA:52,3,0,88 -BRDA:52,3,1,3 -BRDA:57,4,0,88 -BRDA:57,4,1,85 -BRDA:57,4,2,5 -BRF:11 -BRH:10 -end_of_record -TN: -SF:src/components/form/SelectField.tsx -FN:14,SelectField -FN:35,(anonymous_1) -FNF:2 -FNH:2 -FNDA:83,SelectField -FNDA:1456,(anonymous_1) -DA:18,83 -DA:19,83 -DA:21,83 -DA:36,1456 -LF:4 -LH:4 -BRDA:22,0,0,13 -BRDA:22,0,1,70 -BRDA:28,1,0,0 -BRDA:28,1,1,83 -BRDA:33,2,0,83 -BRDA:33,2,1,17 -BRDA:41,3,0,83 -BRDA:41,3,1,0 -BRF:8 -BRH:6 -end_of_record -TN: -SF:src/components/form/TextField.tsx -FN:14,TextField -FNF:1 -FNH:1 -FNDA:200,TextField -DA:26,200 -DA:27,200 -DA:29,200 -LF:3 -LH:3 -BRDA:18,0,0,200 -BRDA:30,1,0,75 -BRDA:30,1,1,125 -BRDA:39,2,0,3 -BRDA:39,2,1,197 -BRDA:42,3,0,200 -BRDA:42,3,1,3 -BRF:7 -BRH:7 -end_of_record -TN: -SF:src/components/layout/Layout.tsx -FN:5,Layout -FNF:1 -FNH:1 -FNDA:3,Layout -DA:6,3 -LF:1 -LH:1 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/components/layout/Logo.tsx -FN:1,Logo -FNF:1 -FNH:1 -FNDA:4,Logo -DA:2,4 -LF:1 -LH:1 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/components/wizard/StepIndicator.tsx -FN:10,StepIndicator -FN:12,(anonymous_1) -FN:16,(anonymous_2) -FNF:3 -FNH:3 -FNDA:10,StepIndicator -FNDA:26,(anonymous_1) -FNDA:40,(anonymous_2) -DA:3,3 -DA:11,10 -DA:12,26 -DA:14,10 -DA:17,40 -DA:18,40 -LF:6 -LH:6 -BRDA:17,0,0,10 -BRDA:17,0,1,30 -BRDA:17,1,0,8 -BRDA:17,1,1,22 -BRDA:20,2,0,10 -BRDA:20,2,1,30 -BRF:6 -BRH:6 -end_of_record -TN: -SF:src/components/wizard/SummarySection.tsx -FN:12,SummarySection -FN:22,(anonymous_1) -FNF:2 -FNH:2 -FNDA:32,SummarySection -FNDA:142,(anonymous_1) -DA:13,32 -DA:23,142 -LF:2 -LH:2 -BRDA:25,0,0,142 -BRDA:25,0,1,15 -BRF:2 -BRH:2 -end_of_record -TN: -SF:src/components/wizard/WizardActions.tsx -FN:8,WizardActions -FNF:1 -FNH:1 -FNDA:77,WizardActions -DA:9,77 -LF:1 -LH:1 -BRDA:8,0,0,77 -BRDA:8,1,0,77 -BRDA:11,2,0,77 -BRDA:11,2,1,65 -BRF:4 -BRH:4 -end_of_record -TN: -SF:src/components/wizard/WizardStepShell.tsx -FN:9,WizardStepShell -FNF:1 -FNH:1 -FNDA:87,WizardStepShell -DA:10,87 -LF:1 -LH:1 -BRDA:13,0,0,87 -BRDA:13,0,1,86 -BRF:2 -BRH:2 -end_of_record -TN: -SF:src/constants/masks.ts -FNF:0 -FNH:0 -DA:1,4 -DA:3,4 -DA:5,4 -DA:7,4 -LF:4 -LH:4 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/constants/options.ts -FN:44,getOptionLabel -FN:45,(anonymous_1) -FNF:2 -FNH:2 -FNDA:27,getOptionLabel -FNDA:407,(anonymous_1) -DA:6,15 -DA:36,15 -DA:45,407 -LF:3 -LH:3 -BRDA:45,0,0,27 -BRDA:45,0,1,3 -BRF:2 -BRH:2 -end_of_record -TN: -SF:src/context/WizardFormProvider.tsx -FN:7,WizardFormProvider -FN:18,(anonymous_1) -FN:19,(anonymous_2) -FN:22,(anonymous_3) -FNF:4 -FNH:4 -FNDA:17,WizardFormProvider -FNDA:5,(anonymous_1) -FNDA:98,(anonymous_2) -FNDA:5,(anonymous_3) -DA:8,17 -DA:18,17 -DA:19,5 -DA:20,98 -DA:22,5 -DA:25,17 -LF:6 -LH:6 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/hooks/useRequireStepData.ts -FN:6,useRequireStepData -FN:10,(anonymous_1) -FNF:2 -FNH:2 -FNDA:110,useRequireStepData -FNDA:16,(anonymous_1) -DA:7,110 -DA:8,110 -DA:10,110 -DA:11,16 -DA:12,1 -LF:5 -LH:5 -BRDA:11,0,0,1 -BRDA:11,0,1,15 -BRF:2 -BRH:2 -end_of_record -TN: -SF:src/hooks/useStepNavigation.ts -FN:5,useStepNavigation -FN:9,goNext -FN:16,goBack -FNF:3 -FNH:3 -FNDA:115,useStepNavigation -FNDA:9,goNext -FNDA:2,goBack -DA:6,115 -DA:7,115 -DA:10,9 -DA:11,9 -DA:12,7 -DA:17,2 -DA:20,115 -LF:7 -LH:7 -BRDA:11,0,0,7 -BRDA:11,0,1,2 -BRF:2 -BRH:2 -end_of_record -TN: -SF:src/lib/api.ts -FN:22,fetchCepFromMock -FN:31,fetchCepFromViaCep -FN:46,fetchAddressByCep -FN:57,fetchProfessions -FNF:4 -FNH:4 -FNDA:4,fetchCepFromMock -FNDA:3,fetchCepFromViaCep -FNDA:4,fetchAddressByCep -FNDA:1,fetchProfessions -DA:3,1 -DA:23,4 -DA:24,4 -DA:25,1 -DA:27,3 -DA:32,3 -DA:33,3 -DA:34,2 -DA:35,1 -DA:42,1 -DA:47,4 -DA:48,4 -DA:49,3 -DA:58,1 -DA:59,1 -LF:15 -LH:15 -BRDA:4,0,0,1 -BRDA:4,0,1,0 -BRDA:34,1,0,1 -BRDA:34,1,1,1 -BRDA:48,2,0,1 -BRDA:48,2,1,3 -BRF:6 -BRH:5 -end_of_record -TN: -SF:src/lib/cpf.ts -FN:1,onlyDigits -FN:5,calcCheckDigit -FN:14,isValidCPF -FNF:3 -FNH:3 -FNDA:39,onlyDigits -FNDA:34,calcCheckDigit -FNDA:37,isValidCPF -DA:2,39 -DA:6,34 -DA:7,34 -DA:8,323 -DA:10,34 -DA:11,34 -DA:15,37 -DA:17,37 -DA:18,20 -DA:21,17 -DA:22,17 -DA:23,17 -DA:25,17 -LF:13 -LH:13 -BRDA:11,0,0,0 -BRDA:11,0,1,34 -BRDA:17,1,0,20 -BRDA:17,1,1,17 -BRDA:17,2,0,37 -BRDA:17,2,1,20 -BRDA:25,3,0,17 -BRDA:25,3,1,17 -BRF:8 -BRH:7 -end_of_record -TN: -SF:src/lib/masks.ts -FN:1,onlyDigits -FN:5,isValidBirthDate -FN:27,parseCurrencyToNumber -FNF:3 -FNH:3 -FNDA:69,onlyDigits -FNDA:37,isValidBirthDate -FNDA:31,parseCurrencyToNumber -DA:2,69 -DA:6,37 -DA:7,37 -DA:9,21 -DA:10,21 -DA:11,21 -DA:12,21 -DA:15,21 -DA:19,37 -DA:21,17 -DA:22,17 -DA:24,17 -DA:28,31 -DA:29,31 -DA:30,31 -LF:15 -LH:15 -BRDA:7,0,0,16 -BRDA:7,0,1,21 -BRDA:15,1,0,21 -BRDA:15,1,1,20 -BRDA:15,1,2,17 -BRDA:19,2,0,4 -BRDA:19,2,1,33 -BRDA:24,3,0,17 -BRDA:24,3,1,16 -BRDA:30,4,0,22 -BRDA:30,4,1,9 -BRF:11 -BRH:11 -end_of_record -TN: -SF:src/lib/pdf.ts -FN:15,buildSections -FN:49,generateSummaryPdf -FNF:2 -FNH:2 -FNDA:2,buildSections -FNDA:2,generateSummaryPdf -DA:5,1 -DA:6,1 -DA:7,1 -DA:8,1 -DA:16,2 -DA:50,2 -DA:51,2 -DA:53,2 -DA:54,2 -DA:55,2 -DA:56,2 -DA:57,2 -DA:58,2 -DA:59,2 -DA:60,2 -DA:61,2 -DA:63,2 -DA:65,2 -DA:66,6 -DA:67,6 -DA:68,6 -DA:69,6 -DA:70,6 -DA:71,6 -DA:72,6 -DA:73,6 -DA:75,6 -DA:76,6 -DA:77,28 -DA:78,28 -DA:79,28 -DA:80,28 -DA:81,28 -DA:82,28 -DA:85,6 -DA:88,2 -DA:89,2 -DA:90,2 -DA:91,2 -DA:93,2 -DA:94,2 -LF:41 -LH:41 -BRDA:81,0,0,28 -BRDA:81,0,1,1 -BRDA:93,1,0,2 -BRDA:93,1,1,1 -BRF:4 -BRH:4 -end_of_record -TN: -SF:src/lib/storage.ts -FN:3,loadStoredFormValues -FN:13,persistFormValues -FN:19,clearStoredFormValues -FNF:3 -FNH:3 -FNDA:21,loadStoredFormValues -FNDA:102,persistFormValues -FNDA:1,clearStoredFormValues -DA:1,3 -DA:4,21 -DA:5,21 -DA:6,21 -DA:7,11 -DA:9,1 -DA:14,102 -DA:15,102 -DA:20,1 -LF:9 -LH:9 -BRDA:6,0,0,10 -BRDA:6,0,1,11 -BRF:2 -BRH:2 -end_of_record -TN: -SF:src/pages/Step1PersonalData.tsx -FN:9,Step1PersonalData -FN:33,(anonymous_1) -FNF:2 -FNH:2 -FNDA:11,Step1PersonalData -FNDA:3,(anonymous_1) -DA:10,11 -DA:12,11 -DA:33,3 -LF:3 -LH:3 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/pages/Step2Address.tsx -FN:23,Step2Address -FN:30,(anonymous_1) -FN:40,(anonymous_2) -FN:53,(anonymous_3) -FN:78,(anonymous_4) -FN:78,(anonymous_5) -FNF:6 -FNH:6 -FNDA:83,Step2Address -FNDA:37,(anonymous_1) -FNDA:4,(anonymous_2) -FNDA:4,(anonymous_3) -FNDA:1,(anonymous_4) -FNDA:2,(anonymous_5) -DA:18,2 -DA:24,83 -DA:25,83 -DA:26,83 -DA:27,83 -DA:28,83 -DA:30,83 -DA:31,37 -DA:32,37 -DA:33,33 -DA:34,33 -DA:37,4 -DA:38,4 -DA:40,4 -DA:41,4 -DA:42,4 -DA:43,3 -DA:44,3 -DA:45,3 -DA:46,3 -DA:47,3 -DA:49,1 -DA:53,4 -DA:54,4 -DA:58,83 -DA:78,2 -LF:26 -LH:26 -BRDA:31,0,0,37 -BRDA:31,0,1,0 -BRDA:32,1,0,33 -BRDA:32,1,1,4 -BRDA:41,2,0,0 -BRDA:41,2,1,4 -BRDA:42,3,0,3 -BRDA:42,3,1,1 -BRF:8 -BRH:6 -end_of_record -TN: -SF:src/pages/Step3Professional.tsx -FN:13,Step3Professional -FN:19,(anonymous_1) -FN:23,(anonymous_2) -FN:25,(anonymous_3) -FN:27,(anonymous_4) -FN:30,(anonymous_5) -FN:34,(anonymous_6) -FN:53,(anonymous_7) -FN:53,(anonymous_8) -FNF:9 -FNH:7 -FNDA:13,Step3Professional -FNDA:4,(anonymous_1) -FNDA:4,(anonymous_2) -FNDA:4,(anonymous_3) -FNDA:0,(anonymous_4) -FNDA:4,(anonymous_5) -FNDA:4,(anonymous_6) -FNDA:0,(anonymous_7) -FNDA:2,(anonymous_8) -DA:14,13 -DA:15,13 -DA:16,13 -DA:17,13 -DA:19,13 -DA:20,4 -DA:22,4 -DA:24,4 -DA:25,4 -DA:28,0 -DA:31,4 -DA:34,4 -DA:35,4 -DA:39,13 -DA:53,2 -LF:15 -LH:14 -BRDA:24,0,0,0 -BRDA:24,0,1,4 -BRDA:28,1,0,0 -BRDA:28,1,1,0 -BRDA:31,2,0,4 -BRDA:31,2,1,0 -BRDA:47,3,0,7 -BRDA:47,3,1,6 -BRF:8 -BRH:4 -end_of_record -TN: -SF:src/pages/SummaryPage.tsx -FN:11,SummaryPage -FN:17,handleExport -FN:21,handleNewRegistration -FN:31,(anonymous_3) -FN:42,(anonymous_4) -FN:53,(anonymous_5) -FNF:6 -FNH:4 -FNDA:10,SummaryPage -FNDA:2,handleExport -FNDA:1,handleNewRegistration -FNDA:1,(anonymous_3) -FNDA:0,(anonymous_4) -FNDA:0,(anonymous_5) -DA:12,10 -DA:13,10 -DA:14,10 -DA:15,10 -DA:18,2 -DA:22,1 -DA:23,1 -DA:24,1 -DA:27,10 -DA:31,1 -DA:42,0 -DA:53,0 -LF:12 -LH:10 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/schemas/formSchema.ts -FN:8,(anonymous_0) -FN:15,(anonymous_1) -FN:41,(anonymous_2) -FN:52,(anonymous_3) -FNF:4 -FNH:4 -FNDA:351,(anonymous_0) -FNDA:29,(anonymous_1) -FNDA:26,(anonymous_2) -FNDA:26,(anonymous_3) -DA:6,13 -DA:7,13 -DA:8,351 -DA:10,13 -DA:15,29 -DA:33,13 -DA:41,26 -DA:46,13 -DA:52,26 -DA:58,13 -DA:62,13 -DA:63,13 -DA:64,13 -DA:66,13 -LF:14 -LH:14 -BRF:0 -BRH:0 -end_of_record diff --git a/frontend/coverage/prettify.css b/frontend/coverage/prettify.css deleted file mode 100644 index b317a7c..0000000 --- a/frontend/coverage/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/frontend/coverage/prettify.js b/frontend/coverage/prettify.js deleted file mode 100644 index b322523..0000000 --- a/frontend/coverage/prettify.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/frontend/coverage/sort-arrow-sprite.png b/frontend/coverage/sort-arrow-sprite.png deleted file mode 100644 index 6ed68316eb3f65dec9063332d2f69bf3093bbfab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc diff --git a/frontend/coverage/sorter.js b/frontend/coverage/sorter.js deleted file mode 100644 index 4ed70ae..0000000 --- a/frontend/coverage/sorter.js +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { - return document.querySelector('.coverage-summary'); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector('thead tr'); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector('tbody'); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll('th')[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById('fileSearch').value; - const rows = document.getElementsByTagName('tbody')[0].children; - - // Try to create a RegExp from the searchValue. If it fails (invalid regex), - // it will be treated as a plain text search - let searchRegex; - try { - searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive - } catch (error) { - searchRegex = null; - } - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let isMatch = false; - - if (searchRegex) { - // If a valid regex was created, use it for matching - isMatch = searchRegex.test(row.textContent); - } else { - // Otherwise, fall back to the original plain text search - isMatch = row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()); - } - - row.style.display = isMatch ? '' : 'none'; - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById('filterTemplate'); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById('fileSearch').oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = - colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function(a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function(a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function() { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function() { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); diff --git a/frontend/coverage/src/App.tsx.html b/frontend/coverage/src/App.tsx.html deleted file mode 100644 index fc76c52..0000000 --- a/frontend/coverage/src/App.tsx.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - Code coverage report for src/App.tsx - - - - - - - - - -
-
-

All files / src App.tsx

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'
-import { Layout } from './components/layout/Layout'
-import { WizardFormProvider } from './context/WizardFormProvider'
-import { Step1PersonalData } from './pages/Step1PersonalData'
-import { Step2Address } from './pages/Step2Address'
-import { Step3Professional } from './pages/Step3Professional'
-import { SummaryPage } from './pages/SummaryPage'
- 
-function App() {
-  return (
-    <BrowserRouter>
-      <WizardFormProvider>
-        <Layout>
-          <Routes>
-            <Route path="/" element={<Navigate to="/etapa-1" replace />} />
-            <Route path="/etapa-1" element={<Step1PersonalData />} />
-            <Route path="/etapa-2" element={<Step2Address />} />
-            <Route path="/etapa-3" element={<Step3Professional />} />
-            <Route path="/resumo" element={<SummaryPage />} />
-            <Route path="*" element={<Navigate to="/etapa-1" replace />} />
-          </Routes>
-        </Layout>
-      </WizardFormProvider>
-    </BrowserRouter>
-  )
-}
- 
-export default App
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/form/CurrencyField.tsx.html b/frontend/coverage/src/components/form/CurrencyField.tsx.html deleted file mode 100644 index b6064a2..0000000 --- a/frontend/coverage/src/components/form/CurrencyField.tsx.html +++ /dev/null @@ -1,253 +0,0 @@ - - - - - - Code coverage report for src/components/form/CurrencyField.tsx - - - - - - - - - -
-
-

All files / src/components/form CurrencyField.tsx

-
- -
- 100% - Statements - 9/9 -
- - -
- 58.33% - Branches - 7/12 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 9/9 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57  -  -  -  -3x -  -  -30x -30x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -19x -19x -  -19x -  -  -  -  -  -  -50x -  -  -  -  -  -  -  -31x -31x -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { Controller, useFormContext } from 'react-hook-form'
-import type { FormValues } from '../../schemas/formSchema'
-import { onlyDigits } from '../../lib/masks'
- 
-const NON_BREAKING_SPACE = ' '
- 
-function formatBRLFromDigits(digits: string): string {
-  const numeric = Number(digits || '0') / 100
-  return numeric
-    .toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })
-    .replaceAll(NON_BREAKING_SPACE, ' ')
-}
- 
-interface CurrencyFieldProps {
-  name: keyof FormValues
-  label: string
-  fullWidth?: boolean
-}
- 
-export function CurrencyField({ name, label, fullWidth }: Readonly<CurrencyFieldProps>) {
-  const {
-    control,
-    formState: { errors },
-  } = useFormContext<FormValues>()
-  const error = errors[name]?.message as string | undefined
- 
-  return (
-    <div className={`field${fullWidth ? ' field-full' : ''}`}>
-      <label htmlFor={name}>{label}</label>
-      <Controller
-        name={name}
-        control={control}
-        render={({ field }) => (
-          <input
-            id={name}
-            inputMode="decimal"
-            placeholder="R$ 0,00"
-            aria-invalid={!!error}
-            aria-describedby={error ? `${name}-error` : undefined}
-            value={field.value ?? ''}
-            onChange={(event) => {
-              const digits = onlyDigits(event.target.value).replace(/^0+/, '').slice(0, 12)
-              field.onChange(digits ? formatBRLFromDigits(digits) : '')
-            }}
-            onBlur={field.onBlur}
-          />
-        )}
-      />
-      {error && (
-        <span className="field-error" id={`${name}-error`} role="alert">
-          {error}
-        </span>
-      )}
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/form/MaskedTextField.tsx.html b/frontend/coverage/src/components/form/MaskedTextField.tsx.html deleted file mode 100644 index 36eaa43..0000000 --- a/frontend/coverage/src/components/form/MaskedTextField.tsx.html +++ /dev/null @@ -1,265 +0,0 @@ - - - - - - Code coverage report for src/components/form/MaskedTextField.tsx - - - - - - - - - -
-
-

All files / src/components/form MaskedTextField.tsx

-
- -
- 100% - Statements - 5/5 -
- - -
- 90.9% - Branches - 10/11 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 5/5 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -88x -88x -  -88x -  -  -  -  -  -  -170x -  -  -  -  -  -  -  -  -  -114x -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { Controller, useFormContext } from 'react-hook-form'
-import { IMaskInput } from 'react-imask'
-import type { FormValues } from '../../schemas/formSchema'
- 
-interface MaskedTextFieldProps {
-  name: keyof FormValues
-  label: string
-  maskOptions: Record<string, unknown>
-  placeholder?: string
-  inputMode?: 'text' | 'numeric' | 'decimal'
-  hint?: string
-  fullWidth?: boolean
-}
- 
-export function MaskedTextField({
-  name,
-  label,
-  maskOptions,
-  placeholder,
-  inputMode,
-  hint,
-  fullWidth,
-}: Readonly<MaskedTextFieldProps>) {
-  const {
-    control,
-    formState: { errors },
-  } = useFormContext<FormValues>()
-  const error = errors[name]?.message as string | undefined
- 
-  return (
-    <div className={`field${fullWidth ? ' field-full' : ''}`}>
-      <label htmlFor={name}>{label}</label>
-      <Controller
-        name={name}
-        control={control}
-        render={({ field }) => (
-          <IMaskInput
-            {...maskOptions}
-            id={name}
-            name={field.name}
-            value={field.value ?? ''}
-            unmask={false}
-            inputMode={inputMode}
-            placeholder={placeholder}
-            aria-invalid={!!error}
-            aria-describedby={error ? `${name}-error` : undefined}
-            onAccept={(value: string) => field.onChange(value)}
-            onBlur={field.onBlur}
-          />
-        )}
-      />
-      {error && (
-        <span className="field-error" id={`${name}-error`} role="alert">
-          {error}
-        </span>
-      )}
-      {!error && hint && <span className="field-hint">{hint}</span>}
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/form/SelectField.tsx.html b/frontend/coverage/src/components/form/SelectField.tsx.html deleted file mode 100644 index 65a561a..0000000 --- a/frontend/coverage/src/components/form/SelectField.tsx.html +++ /dev/null @@ -1,229 +0,0 @@ - - - - - - Code coverage report for src/components/form/SelectField.tsx - - - - - - - - - -
-
-

All files / src/components/form SelectField.tsx

-
- -
- 100% - Statements - 4/4 -
- - -
- 75% - Branches - 6/8 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 4/4 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -83x -83x -  -83x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1456x -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useFormContext } from 'react-hook-form'
-import type { FormValues } from '../../schemas/formSchema'
-import type { SelectOption } from '../../constants/options'
- 
-interface SelectFieldProps {
-  name: keyof FormValues
-  label: string
-  options: SelectOption[]
-  placeholder?: string
-  disabled?: boolean
-  fullWidth?: boolean
-}
- 
-export function SelectField({ name, label, options, placeholder, disabled, fullWidth }: Readonly<SelectFieldProps>) {
-  const {
-    register,
-    formState: { errors },
-  } = useFormContext<FormValues>()
-  const error = errors[name]?.message as string | undefined
- 
-  return (
-    <div className={`field${fullWidth ? ' field-full' : ''}`}>
-      <label htmlFor={name}>{label}</label>
-      <select
-        id={name}
-        disabled={disabled}
-        aria-invalid={!!error}
-        aria-describedby={error ? `${name}-error` : undefined}
-        defaultValue=""
-        {...register(name)}
-      >
-        <option value="" disabled>
-          {placeholder ?? 'Selecione...'}
-        </option>
-        {options.map((option) => (
-          <option key={option.value} value={option.value}>
-            {option.label}
-          </option>
-        ))}
-      </select>
-      {error && (
-        <span className="field-error" id={`${name}-error`} role="alert">
-          {error}
-        </span>
-      )}
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/form/TextField.tsx.html b/frontend/coverage/src/components/form/TextField.tsx.html deleted file mode 100644 index 2a0fc00..0000000 --- a/frontend/coverage/src/components/form/TextField.tsx.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - Code coverage report for src/components/form/TextField.tsx - - - - - - - - - -
-
-

All files / src/components/form TextField.tsx

-
- -
- 100% - Statements - 3/3 -
- - -
- 100% - Branches - 7/7 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -200x -200x -  -200x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useFormContext } from 'react-hook-form'
-import type { FormValues } from '../../schemas/formSchema'
- 
-interface TextFieldProps {
-  name: keyof FormValues
-  label: string
-  placeholder?: string
-  type?: string
-  autoComplete?: string
-  fullWidth?: boolean
-  readOnly?: boolean
-}
- 
-export function TextField({
-  name,
-  label,
-  placeholder,
-  type = 'text',
-  autoComplete,
-  fullWidth,
-  readOnly,
-}: Readonly<TextFieldProps>) {
-  const {
-    register,
-    formState: { errors },
-  } = useFormContext<FormValues>()
-  const error = errors[name]?.message as string | undefined
- 
-  return (
-    <div className={`field${fullWidth ? ' field-full' : ''}`}>
-      <label htmlFor={name}>{label}</label>
-      <input
-        id={name}
-        type={type}
-        placeholder={placeholder}
-        autoComplete={autoComplete}
-        readOnly={readOnly}
-        aria-invalid={!!error}
-        aria-describedby={error ? `${name}-error` : undefined}
-        {...register(name)}
-      />
-      {error && (
-        <span className="field-error" id={`${name}-error`} role="alert">
-          {error}
-        </span>
-      )}
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/form/index.html b/frontend/coverage/src/components/form/index.html deleted file mode 100644 index 4b880f0..0000000 --- a/frontend/coverage/src/components/form/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for src/components/form - - - - - - - - - -
-
-

All files src/components/form

-
- -
- 100% - Statements - 21/21 -
- - -
- 78.94% - Branches - 30/38 -
- - -
- 100% - Functions - 10/10 -
- - -
- 100% - Lines - 21/21 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
CurrencyField.tsx -
-
100%9/958.33%7/12100%4/4100%9/9
MaskedTextField.tsx -
-
100%5/590.9%10/11100%3/3100%5/5
SelectField.tsx -
-
100%4/475%6/8100%2/2100%4/4
TextField.tsx -
-
100%3/3100%7/7100%1/1100%3/3
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/layout/Layout.tsx.html b/frontend/coverage/src/components/layout/Layout.tsx.html deleted file mode 100644 index 987a880..0000000 --- a/frontend/coverage/src/components/layout/Layout.tsx.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - Code coverage report for src/components/layout/Layout.tsx - - - - - - - - - -
-
-

All files / src/components/layout Layout.tsx

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21  -  -  -  -  -3x -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import type { ReactNode } from 'react'
-import { Logo } from './Logo'
-import { StepIndicator } from '../wizard/StepIndicator'
- 
-export function Layout({ children }: { children: ReactNode }) {
-  return (
-    <div className="app-shell">
-      <header className="app-header">
-        <Logo />
-      </header>
-      <StepIndicator />
-      <main className="app-main">
-        <div className="card">{children}</div>
-      </main>
-      <footer className="app-footer">
-        <p>&copy; {new Date().getFullYear()} Verity. Todos os direitos reservados.</p>
-      </footer>
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/layout/Logo.tsx.html b/frontend/coverage/src/components/layout/Logo.tsx.html deleted file mode 100644 index e747970..0000000 --- a/frontend/coverage/src/components/layout/Logo.tsx.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - Code coverage report for src/components/layout/Logo.tsx - - - - - - - - - -
-
-

All files / src/components/layout Logo.tsx

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8  -4x -  -  -  -  -  - 
export function Logo() {
-  return (
-    <div className="logo-mark">
-      <img src="/logo.avif" alt="Verity" className="logo-image" />
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/layout/index.html b/frontend/coverage/src/components/layout/index.html deleted file mode 100644 index 25dc5bd..0000000 --- a/frontend/coverage/src/components/layout/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for src/components/layout - - - - - - - - - -
-
-

All files src/components/layout

-
- -
- 100% - Statements - 2/2 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 2/2 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
Layout.tsx -
-
100%1/1100%0/0100%1/1100%1/1
Logo.tsx -
-
100%1/1100%0/0100%1/1100%1/1
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/wizard/StepIndicator.tsx.html b/frontend/coverage/src/components/wizard/StepIndicator.tsx.html deleted file mode 100644 index cad6beb..0000000 --- a/frontend/coverage/src/components/wizard/StepIndicator.tsx.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - Code coverage report for src/components/wizard/StepIndicator.tsx - - - - - - - - - -
-
-

All files / src/components/wizard StepIndicator.tsx

-
- -
- 100% - Statements - 7/7 -
- - -
- 100% - Branches - 6/6 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 6/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28  -  -3x -  -  -  -  -  -  -  -10x -26x -  -10x -  -  -40x -40x -  -  -  -  -  -  -  -  -  - 
import { useLocation } from 'react-router-dom'
- 
-const STEPS = [
-  { path: '/etapa-1', label: 'Dados Pessoais' },
-  { path: '/etapa-2', label: 'Endereço' },
-  { path: '/etapa-3', label: 'Profissional' },
-  { path: '/resumo', label: 'Resumo' },
-]
- 
-export function StepIndicator() {
-  const { pathname } = useLocation()
-  const currentIndex = STEPS.findIndex((step) => step.path === pathname)
- 
-  return (
-    <ol className="step-indicator">
-      {STEPS.map((step, index) => {
-        const status = index < currentIndex ? 'done' : index === currentIndex ? 'active' : 'pending'
-        return (
-          <li key={step.path} className={`step step-${status}`}>
-            <span className="step-index">{status === 'done' ? '✓' : index + 1}</span>
-            <span className="step-label">{step.label}</span>
-          </li>
-        )
-      })}
-    </ol>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/wizard/SummarySection.tsx.html b/frontend/coverage/src/components/wizard/SummarySection.tsx.html deleted file mode 100644 index 90ad33f..0000000 --- a/frontend/coverage/src/components/wizard/SummarySection.tsx.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - Code coverage report for src/components/wizard/SummarySection.tsx - - - - - - - - - -
-
-

All files / src/components/wizard SummarySection.tsx

-
- -
- 100% - Statements - 2/2 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 2/2 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32  -  -  -  -  -  -  -  -  -  -  -  -32x -  -  -  -  -  -  -  -  -  -142x -  -  -  -  -  -  -  -  - 
interface SummaryItem {
-  label: string
-  value: string
-}
- 
-interface SummarySectionProps {
-  title: string
-  items: SummaryItem[]
-  onEdit: () => void
-}
- 
-export function SummarySection({ title, items, onEdit }: Readonly<SummarySectionProps>) {
-  return (
-    <section className="summary-section">
-      <div className="summary-section-header">
-        <h3>{title}</h3>
-        <button type="button" className="btn-link" onClick={onEdit}>
-          Editar
-        </button>
-      </div>
-      <dl className="summary-list">
-        {items.map((item) => (
-          <div className="summary-row" key={item.label}>
-            <dt>{item.label}</dt>
-            <dd>{item.value || '—'}</dd>
-          </div>
-        ))}
-      </dl>
-    </section>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/wizard/WizardActions.tsx.html b/frontend/coverage/src/components/wizard/WizardActions.tsx.html deleted file mode 100644 index 69190c3..0000000 --- a/frontend/coverage/src/components/wizard/WizardActions.tsx.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - Code coverage report for src/components/wizard/WizardActions.tsx - - - - - - - - - -
-
-

All files / src/components/wizard WizardActions.tsx

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 4/4 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22  -  -  -  -  -  -  -  -77x -  -  -  -  -  -  -  -  -  -  -  -  - 
interface WizardActionsProps {
-  onBack?: () => void
-  onNext: () => void
-  nextLabel?: string
-  backLabel?: string
-}
- 
-export function WizardActions({ onBack, onNext, nextLabel = 'Próximo', backLabel = 'Voltar' }: Readonly<WizardActionsProps>) {
-  return (
-    <div className="wizard-actions">
-      {onBack && (
-        <button type="button" className="btn btn-secondary" onClick={onBack}>
-          {backLabel}
-        </button>
-      )}
-      <button type="button" className="btn btn-primary" onClick={onNext}>
-        {nextLabel}
-      </button>
-    </div>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/wizard/WizardStepShell.tsx.html b/frontend/coverage/src/components/wizard/WizardStepShell.tsx.html deleted file mode 100644 index e09603c..0000000 --- a/frontend/coverage/src/components/wizard/WizardStepShell.tsx.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - Code coverage report for src/components/wizard/WizardStepShell.tsx - - - - - - - - - -
-
-

All files / src/components/wizard WizardStepShell.tsx

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18  -  -  -  -  -  -  -  -  -87x -  -  -  -  -  -  -  - 
import type { ReactNode } from 'react'
- 
-interface WizardStepShellProps {
-  title: string
-  description?: string
-  children: ReactNode
-}
- 
-export function WizardStepShell({ title, description, children }: Readonly<WizardStepShellProps>) {
-  return (
-    <section>
-      <h1 className="step-title">{title}</h1>
-      {description && <p className="step-description">{description}</p>}
-      {children}
-    </section>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/components/wizard/index.html b/frontend/coverage/src/components/wizard/index.html deleted file mode 100644 index f76c3b2..0000000 --- a/frontend/coverage/src/components/wizard/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for src/components/wizard - - - - - - - - - -
-
-

All files src/components/wizard

-
- -
- 100% - Statements - 11/11 -
- - -
- 100% - Branches - 14/14 -
- - -
- 100% - Functions - 7/7 -
- - -
- 100% - Lines - 10/10 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
StepIndicator.tsx -
-
100%7/7100%6/6100%3/3100%6/6
SummarySection.tsx -
-
100%2/2100%2/2100%2/2100%2/2
WizardActions.tsx -
-
100%1/1100%4/4100%1/1100%1/1
WizardStepShell.tsx -
-
100%1/1100%2/2100%1/1100%1/1
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/constants/index.html b/frontend/coverage/src/constants/index.html deleted file mode 100644 index 0a2077b..0000000 --- a/frontend/coverage/src/constants/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for src/constants - - - - - - - - - -
-
-

All files src/constants

-
- -
- 100% - Statements - 8/8 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 7/7 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
masks.ts -
-
100%4/4100%0/0100%0/0100%4/4
options.ts -
-
100%4/4100%2/2100%2/2100%3/3
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/constants/masks.ts.html b/frontend/coverage/src/constants/masks.ts.html deleted file mode 100644 index 5e4a991..0000000 --- a/frontend/coverage/src/constants/masks.ts.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - Code coverage report for src/constants/masks.ts - - - - - - - - - -
-
-

All files / src/constants masks.ts

-
- -
- 100% - Statements - 4/4 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 0/0 -
- - -
- 100% - Lines - 4/4 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -104x -  -4x -  -4x -  -4x -  -  - 
export const CPF_MASK = { mask: '000.000.000-00' }
- 
-export const DATE_MASK = { mask: '00/00/0000' }
- 
-export const CEP_MASK = { mask: '00000-000' }
- 
-export const PHONE_MASK = {
-  mask: [{ mask: '(00) 0000-0000' }, { mask: '(00) 00000-0000' }],
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/constants/options.ts.html b/frontend/coverage/src/constants/options.ts.html deleted file mode 100644 index f843448..0000000 --- a/frontend/coverage/src/constants/options.ts.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - - - Code coverage report for src/constants/options.ts - - - - - - - - - -
-
-

All files / src/constants options.ts

-
- -
- 100% - Statements - 4/4 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47  -  -  -  -  -15x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -15x -  -  -  -  -  -  -  -  -407x -  - 
export interface SelectOption {
-  value: string
-  label: string
-}
- 
-export const UF_OPTIONS: SelectOption[] = [
-  { value: 'AC', label: 'Acre' },
-  { value: 'AL', label: 'Alagoas' },
-  { value: 'AP', label: 'Amapá' },
-  { value: 'AM', label: 'Amazonas' },
-  { value: 'BA', label: 'Bahia' },
-  { value: 'CE', label: 'Ceará' },
-  { value: 'DF', label: 'Distrito Federal' },
-  { value: 'ES', label: 'Espírito Santo' },
-  { value: 'GO', label: 'Goiás' },
-  { value: 'MA', label: 'Maranhão' },
-  { value: 'MT', label: 'Mato Grosso' },
-  { value: 'MS', label: 'Mato Grosso do Sul' },
-  { value: 'MG', label: 'Minas Gerais' },
-  { value: 'PA', label: 'Pará' },
-  { value: 'PB', label: 'Paraíba' },
-  { value: 'PR', label: 'Paraná' },
-  { value: 'PE', label: 'Pernambuco' },
-  { value: 'PI', label: 'Piauí' },
-  { value: 'RJ', label: 'Rio de Janeiro' },
-  { value: 'RN', label: 'Rio Grande do Norte' },
-  { value: 'RS', label: 'Rio Grande do Sul' },
-  { value: 'RO', label: 'Rondônia' },
-  { value: 'RR', label: 'Roraima' },
-  { value: 'SC', label: 'Santa Catarina' },
-  { value: 'SP', label: 'São Paulo' },
-  { value: 'SE', label: 'Sergipe' },
-  { value: 'TO', label: 'Tocantins' },
-]
- 
-export const TEMPO_EMPRESA_OPTIONS: SelectOption[] = [
-  { value: 'menos-6-meses', label: 'Menos de 6 meses' },
-  { value: '6-meses-1-ano', label: 'De 6 meses a 1 ano' },
-  { value: '1-2-anos', label: 'De 1 a 2 anos' },
-  { value: '2-5-anos', label: 'De 2 a 5 anos' },
-  { value: 'mais-5-anos', label: 'Mais de 5 anos' },
-]
- 
-export function getOptionLabel(options: SelectOption[], value: string): string {
-  return options.find((option) => option.value === value)?.label ?? value
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/context/WizardFormProvider.tsx.html b/frontend/coverage/src/context/WizardFormProvider.tsx.html deleted file mode 100644 index 3239d0e..0000000 --- a/frontend/coverage/src/context/WizardFormProvider.tsx.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - Code coverage report for src/context/WizardFormProvider.tsx - - - - - - - - - -
-
-

All files / src/context WizardFormProvider.tsx

-
- -
- 100% - Statements - 7/7 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 6/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27  -  -  -  -  -  -  -17x -  -  -  -  -  -  -  -  -  -17x -5x -98x -  -5x -  -  -17x -  - 
import { useEffect, type ReactNode } from 'react'
-import { FormProvider, useForm } from 'react-hook-form'
-import { zodResolver } from '@hookform/resolvers/zod'
-import { defaultFormValues, formSchema, type FormValues } from '../schemas/formSchema'
-import { loadStoredFormValues, persistFormValues } from '../lib/storage'
- 
-export function WizardFormProvider({ children }: { children: ReactNode }) {
-  const methods = useForm<FormValues>({
-    resolver: zodResolver(formSchema),
-    mode: 'onSubmit',
-    reValidateMode: 'onChange',
-    defaultValues: {
-      ...defaultFormValues,
-      ...loadStoredFormValues<FormValues>(),
-    },
-  })
- 
-  useEffect(() => {
-    const subscription = methods.watch((values) => {
-      persistFormValues(values)
-    })
-    return () => subscription.unsubscribe()
-  }, [methods])
- 
-  return <FormProvider {...methods}>{children}</FormProvider>
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/context/index.html b/frontend/coverage/src/context/index.html deleted file mode 100644 index 1e99a4e..0000000 --- a/frontend/coverage/src/context/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for src/context - - - - - - - - - -
-
-

All files src/context

-
- -
- 100% - Statements - 7/7 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 6/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
WizardFormProvider.tsx -
-
100%7/7100%0/0100%4/4100%6/6
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/hooks/index.html b/frontend/coverage/src/hooks/index.html deleted file mode 100644 index b2d2e27..0000000 --- a/frontend/coverage/src/hooks/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for src/hooks - - - - - - - - - -
-
-

All files src/hooks

-
- -
- 100% - Statements - 12/12 -
- - -
- 100% - Branches - 4/4 -
- - -
- 100% - Functions - 5/5 -
- - -
- 100% - Lines - 12/12 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
useRequireStepData.ts -
-
100%5/5100%2/2100%2/2100%5/5
useStepNavigation.ts -
-
100%7/7100%2/2100%3/3100%7/7
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/hooks/useRequireStepData.ts.html b/frontend/coverage/src/hooks/useRequireStepData.ts.html deleted file mode 100644 index 20c6681..0000000 --- a/frontend/coverage/src/hooks/useRequireStepData.ts.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - Code coverage report for src/hooks/useRequireStepData.ts - - - - - - - - - -
-
-

All files / src/hooks useRequireStepData.ts

-
- -
- 100% - Statements - 5/5 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 5/5 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16  -  -  -  -  -  -110x -110x -  -110x -16x -1x -  -  -  - 
import { useEffect } from 'react'
-import { useNavigate } from 'react-router-dom'
-import { useFormContext } from 'react-hook-form'
-import type { FormValues } from '../schemas/formSchema'
- 
-export function useRequireStepData(requiredField: keyof FormValues, redirectTo: string) {
-  const { getValues } = useFormContext<FormValues>()
-  const navigate = useNavigate()
- 
-  useEffect(() => {
-    if (!getValues(requiredField)) {
-      navigate(redirectTo, { replace: true })
-    }
-  }, [])
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/hooks/useStepNavigation.ts.html b/frontend/coverage/src/hooks/useStepNavigation.ts.html deleted file mode 100644 index 8f23628..0000000 --- a/frontend/coverage/src/hooks/useStepNavigation.ts.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - Code coverage report for src/hooks/useStepNavigation.ts - - - - - - - - - -
-
-

All files / src/hooks useStepNavigation.ts

-
- -
- 100% - Statements - 7/7 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 7/7 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22  -  -  -  -  -115x -115x -  -  -9x -9x -7x -  -  -  -  -2x -  -  -115x -  - 
import { useNavigate } from 'react-router-dom'
-import { useFormContext } from 'react-hook-form'
-import type { FormValues } from '../schemas/formSchema'
- 
-export function useStepNavigation(fields: (keyof FormValues)[]) {
-  const { trigger } = useFormContext<FormValues>()
-  const navigate = useNavigate()
- 
-  async function goNext(path: string) {
-    const isValid = await trigger(fields)
-    if (isValid) {
-      navigate(path)
-    }
-  }
- 
-  function goBack(path: string) {
-    navigate(path)
-  }
- 
-  return { goNext, goBack }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/index.html b/frontend/coverage/src/index.html deleted file mode 100644 index 2130957..0000000 --- a/frontend/coverage/src/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for src - - - - - - - - - -
-
-

All files src

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
App.tsx -
-
100%1/1100%0/0100%1/1100%1/1
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/lib/api.ts.html b/frontend/coverage/src/lib/api.ts.html deleted file mode 100644 index 9292d4a..0000000 --- a/frontend/coverage/src/lib/api.ts.html +++ /dev/null @@ -1,265 +0,0 @@ - - - - - - Code coverage report for src/lib/api.ts - - - - - - - - - -
-
-

All files / src/lib api.ts

-
- -
- 100% - Statements - 17/17 -
- - -
- 83.33% - Branches - 5/6 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 15/15 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -4x -4x -1x -  -3x -  -  -  -  -3x -3x -2x -1x -  -  -  -  -  -  -1x -  -  -  -  -4x -4x -3x -  -  -  -  -  -  -  -  -1x -1x -  - 
import axios from 'axios'
- 
-export const mockApi = axios.create({
-  baseURL: import.meta.env.VITE_MOCK_API_URL ?? 'http://localhost:3001',
-})
- 
-export interface CepResult {
-  endereco: string
-  bairro: string
-  cidade: string
-  estado: string
-}
- 
-interface ViaCepResponse {
-  logradouro: string
-  bairro: string
-  localidade: string
-  uf: string
-  erro?: boolean
-}
- 
-async function fetchCepFromMock(cep: string): Promise<CepResult | null> {
-  try {
-    const { data } = await mockApi.get<CepResult>(`/ceps/${cep}`)
-    return data
-  } catch {
-    return null
-  }
-}
- 
-async function fetchCepFromViaCep(cep: string): Promise<CepResult | null> {
-  try {
-    const { data } = await axios.get<ViaCepResponse>(`https://viacep.com.br/ws/${cep}/json/`)
-    if (data.erro) return null
-    return {
-      endereco: data.logradouro,
-      bairro: data.bairro,
-      cidade: data.localidade,
-      estado: data.uf,
-    }
-  } catch {
-    return null
-  }
-}
- 
-export async function fetchAddressByCep(cep: string): Promise<CepResult | null> {
-  const mockResult = await fetchCepFromMock(cep)
-  if (mockResult) return mockResult
-  return fetchCepFromViaCep(cep)
-}
- 
-export interface Profession {
-  id: number | string
-  name: string
-}
- 
-export async function fetchProfessions(): Promise<Profession[]> {
-  const { data } = await mockApi.get<Profession[]>('/professions')
-  return data
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/lib/cpf.ts.html b/frontend/coverage/src/lib/cpf.ts.html deleted file mode 100644 index c22333e..0000000 --- a/frontend/coverage/src/lib/cpf.ts.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - Code coverage report for src/lib/cpf.ts - - - - - - - - - -
-
-

All files / src/lib cpf.ts

-
- -
- 100% - Statements - 14/14 -
- - -
- 87.5% - Branches - 7/8 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 13/13 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27  -39x -  -  -  -34x -34x -323x -  -34x -34x -  -  -  -37x -  -37x -20x -  -  -17x -17x -17x -  -17x -  - 
export function onlyDigits(value: string): string {
-  return value.replace(/\D/g, '')
-}
- 
-function calcCheckDigit(digits: number[], length: number): number {
-  let sum = 0
-  for (let i = 0; i < length; i++) {
-    sum += digits[i] * (length + 1 - i)
-  }
-  const remainder = (sum * 10) % 11
-  return remainder === 10 ? 0 : remainder
-}
- 
-export function isValidCPF(rawValue: string): boolean {
-  const cpf = onlyDigits(rawValue)
- 
-  if (cpf.length !== 11 || /^(\d)\1{10}$/.test(cpf)) {
-    return false
-  }
- 
-  const digits = cpf.split('').map(Number)
-  const firstCheckDigit = calcCheckDigit(digits, 9)
-  const secondCheckDigit = calcCheckDigit(digits, 10)
- 
-  return firstCheckDigit === digits[9] && secondCheckDigit === digits[10]
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/lib/index.html b/frontend/coverage/src/lib/index.html deleted file mode 100644 index 9f27f62..0000000 --- a/frontend/coverage/src/lib/index.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - Code coverage report for src/lib - - - - - - - - - -
-
-

All files src/lib

-
- -
- 100% - Statements - 99/99 -
- - -
- 93.54% - Branches - 29/31 -
- - -
- 100% - Functions - 15/15 -
- - -
- 100% - Lines - 93/93 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
api.ts -
-
100%17/1783.33%5/6100%4/4100%15/15
cpf.ts -
-
100%14/1487.5%7/8100%3/3100%13/13
masks.ts -
-
100%17/17100%11/11100%3/3100%15/15
pdf.ts -
-
100%41/41100%4/4100%2/2100%41/41
storage.ts -
-
100%10/10100%2/2100%3/3100%9/9
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/lib/masks.ts.html b/frontend/coverage/src/lib/masks.ts.html deleted file mode 100644 index 2037cce..0000000 --- a/frontend/coverage/src/lib/masks.ts.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - Code coverage report for src/lib/masks.ts - - - - - - - - - -
-
-

All files / src/lib masks.ts

-
- -
- 100% - Statements - 17/17 -
- - -
- 100% - Branches - 11/11 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 15/15 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32  -69x -  -  -  -37x -37x -  -21x -21x -21x -21x -  -  -21x -  -  -  -37x -  -17x -17x -  -17x -  -  -  -31x -31x -31x -  - 
export function onlyDigits(value: string): string {
-  return value.replace(/\D/g, '')
-}
- 
-export function isValidBirthDate(value: string): boolean {
-  const match = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value)
-  if (!match) return false
- 
-  const day = Number(match[1])
-  const month = Number(match[2])
-  const year = Number(match[3])
-  const date = new Date(year, month - 1, day)
- 
-  const isRealCalendarDate =
-    date.getFullYear() === year &&
-    date.getMonth() === month - 1 &&
-    date.getDate() === day
- 
-  if (!isRealCalendarDate) return false
- 
-  const today = new Date()
-  today.setHours(0, 0, 0, 0)
- 
-  return date <= today && year >= 1900
-}
- 
-export function parseCurrencyToNumber(value: string): number {
-  const normalized = value.replace(/[^\d,-]/g, '').replace(',', '.')
-  const parsed = Number.parseFloat(normalized)
-  return Number.isNaN(parsed) ? 0 : parsed
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/lib/pdf.ts.html b/frontend/coverage/src/lib/pdf.ts.html deleted file mode 100644 index d660c59..0000000 --- a/frontend/coverage/src/lib/pdf.ts.html +++ /dev/null @@ -1,370 +0,0 @@ - - - - - - Code coverage report for src/lib/pdf.ts - - - - - - - - - -
-
-

All files / src/lib pdf.ts

-
- -
- 100% - Statements - 41/41 -
- - -
- 100% - Branches - 4/4 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 41/41 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96  -  -  -  -1x -1x -1x -1x -  -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -2x -  -2x -2x -2x -2x -2x -2x -2x -2x -2x -  -2x -  -2x -6x -6x -6x -6x -6x -6x -6x -6x -  -6x -6x -28x -28x -28x -28x -28x -28x -  -  -6x -  -  -2x -2x -2x -2x -  -2x -2x -  - 
import { jsPDF } from 'jspdf'
-import type { FormValues } from '../schemas/formSchema'
-import { getOptionLabel, TEMPO_EMPRESA_OPTIONS, UF_OPTIONS } from '../constants/options'
- 
-const VERITY_BLUE: [number, number, number] = [13, 71, 161]
-const VERITY_BLUE_DARK: [number, number, number] = [8, 43, 99]
-const TEXT_GRAY: [number, number, number] = [60, 68, 82]
-const MARGIN_X = 16
- 
-interface Section {
-  title: string
-  rows: [string, string][]
-}
- 
-function buildSections(data: FormValues): Section[] {
-  return [
-    {
-      title: 'Dados Pessoais',
-      rows: [
-        ['Nome completo', data.fullName],
-        ['Data de nascimento', data.birthDate],
-        ['CPF', data.cpf],
-        ['Telefone', data.phone],
-        ['E-mail', data.email],
-      ],
-    },
-    {
-      title: 'Informações Residenciais',
-      rows: [
-        ['CEP', data.cep],
-        ['Endereço', data.endereco],
-        ['Bairro', data.bairro],
-        ['Cidade', data.cidade],
-        ['Estado', getOptionLabel(UF_OPTIONS, data.estado)],
-      ],
-    },
-    {
-      title: 'Informações Profissionais',
-      rows: [
-        ['Empresa', data.empresa],
-        ['Profissão', data.profissao],
-        ['Salário', data.salario],
-        ['Tempo de empresa', getOptionLabel(TEMPO_EMPRESA_OPTIONS, data.tempoDeEmpresa)],
-      ],
-    },
-  ]
-}
- 
-export function generateSummaryPdf(data: FormValues): void {
-  const doc = new jsPDF()
-  const pageWidth = doc.internal.pageSize.getWidth()
- 
-  doc.setFillColor(...VERITY_BLUE)
-  doc.rect(0, 0, pageWidth, 30, 'F')
-  doc.setTextColor(255, 255, 255)
-  doc.setFont('helvetica', 'bold')
-  doc.setFontSize(18)
-  doc.text('verity', MARGIN_X, 19)
-  doc.setFontSize(11)
-  doc.setFont('helvetica', 'normal')
-  doc.text('Resumo do Cadastro', pageWidth - MARGIN_X, 19, { align: 'right' })
- 
-  let cursorY = 44
- 
-  for (const section of buildSections(data)) {
-    doc.setTextColor(...VERITY_BLUE_DARK)
-    doc.setFont('helvetica', 'bold')
-    doc.setFontSize(13)
-    doc.text(section.title, MARGIN_X, cursorY)
-    cursorY += 3
-    doc.setDrawColor(...VERITY_BLUE)
-    doc.line(MARGIN_X, cursorY, pageWidth - MARGIN_X, cursorY)
-    cursorY += 8
- 
-    doc.setFontSize(11)
-    for (const [label, value] of section.rows) {
-      doc.setFont('helvetica', 'bold')
-      doc.setTextColor(...TEXT_GRAY)
-      doc.text(`${label}:`, MARGIN_X, cursorY)
-      doc.setFont('helvetica', 'normal')
-      doc.text(value || '—', MARGIN_X + 55, cursorY)
-      cursorY += 7
-    }
- 
-    cursorY += 6
-  }
- 
-  const generatedAt = new Date().toLocaleString('pt-BR')
-  doc.setFontSize(9)
-  doc.setTextColor(...TEXT_GRAY)
-  doc.text(`Gerado em ${generatedAt}`, MARGIN_X, doc.internal.pageSize.getHeight() - 10)
- 
-  const fileSuffix = data.fullName.trim().toLowerCase().replace(/\s+/g, '-') || 'cadastro'
-  doc.save(`verity-cadastro-${fileSuffix}.pdf`)
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/lib/storage.ts.html b/frontend/coverage/src/lib/storage.ts.html deleted file mode 100644 index aac3ce3..0000000 --- a/frontend/coverage/src/lib/storage.ts.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - Code coverage report for src/lib/storage.ts - - - - - - - - - -
-
-

All files / src/lib storage.ts

-
- -
- 100% - Statements - 10/10 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 9/9 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -223x -  -  -21x -21x -21x -11x -  -1x -  -  -  -  -102x -102x -  -  -  -  -1x -  - 
const STORAGE_KEY = 'verity:cadastro-wizard'
- 
-export function loadStoredFormValues<T>(): Partial<T> | undefined {
-  try {
-    const raw = window.localStorage.getItem(STORAGE_KEY)
-    if (!raw) return undefined
-    return JSON.parse(raw) as Partial<T>
-  } catch {
-    return undefined
-  }
-}
- 
-export function persistFormValues(values: unknown): void {
-  try {
-    window.localStorage.setItem(STORAGE_KEY, JSON.stringify(values))
-  } catch {}
-}
- 
-export function clearStoredFormValues(): void {
-  window.localStorage.removeItem(STORAGE_KEY)
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/pages/Step1PersonalData.tsx.html b/frontend/coverage/src/pages/Step1PersonalData.tsx.html deleted file mode 100644 index 8c076f1..0000000 --- a/frontend/coverage/src/pages/Step1PersonalData.tsx.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - Code coverage report for src/pages/Step1PersonalData.tsx - - - - - - - - - -
-
-

All files / src/pages Step1PersonalData.tsx

-
- -
- 100% - Statements - 3/3 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37  -  -  -  -  -  -  -  -  -11x -  -11x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -3x -  -  -  - 
import { TextField } from '../components/form/TextField'
-import { MaskedTextField } from '../components/form/MaskedTextField'
-import { WizardActions } from '../components/wizard/WizardActions'
-import { WizardStepShell } from '../components/wizard/WizardStepShell'
-import { useStepNavigation } from '../hooks/useStepNavigation'
-import { STEP1_FIELDS } from '../schemas/formSchema'
-import { CPF_MASK, DATE_MASK, PHONE_MASK } from '../constants/masks'
- 
-export function Step1PersonalData() {
-  const { goNext } = useStepNavigation(STEP1_FIELDS)
- 
-  return (
-    <WizardStepShell title="Dados Pessoais" description="Preencha suas informações pessoais para começar.">
-      <div className="field-grid">
-        <TextField name="fullName" label="Nome completo" autoComplete="name" fullWidth />
-        <MaskedTextField
-          name="birthDate"
-          label="Data de nascimento"
-          maskOptions={DATE_MASK}
-          placeholder="dd/mm/aaaa"
-          inputMode="numeric"
-        />
-        <MaskedTextField name="cpf" label="CPF" maskOptions={CPF_MASK} placeholder="000.000.000-00" inputMode="numeric" />
-        <MaskedTextField
-          name="phone"
-          label="Telefone"
-          maskOptions={PHONE_MASK}
-          placeholder="(00) 00000-0000"
-          inputMode="numeric"
-        />
-        <TextField name="email" label="E-mail" type="email" autoComplete="email" />
-      </div>
-      <WizardActions onNext={() => goNext('/etapa-2')} />
-    </WizardStepShell>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/pages/Step2Address.tsx.html b/frontend/coverage/src/pages/Step2Address.tsx.html deleted file mode 100644 index b8d3b08..0000000 --- a/frontend/coverage/src/pages/Step2Address.tsx.html +++ /dev/null @@ -1,328 +0,0 @@ - - - - - - Code coverage report for src/pages/Step2Address.tsx - - - - - - - - - -
-
-

All files / src/pages Step2Address.tsx

-
- -
- 96.42% - Statements - 27/28 -
- - -
- 75% - Branches - 6/8 -
- - -
- 100% - Functions - 6/6 -
- - -
- 100% - Lines - 26/26 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -83x -83x -83x -83x -83x -  -83x -37x -37x -33x -33x -  -  -4x -4x -  -4x -4x -4x -3x -3x -3x -3x -3x -  -1x -  -  -  -4x -4x -  -  -  -83x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  - 
import { useEffect, useState } from 'react'
-import { useFormContext, useWatch } from 'react-hook-form'
-import { TextField } from '../components/form/TextField'
-import { SelectField } from '../components/form/SelectField'
-import { MaskedTextField } from '../components/form/MaskedTextField'
-import { WizardActions } from '../components/wizard/WizardActions'
-import { WizardStepShell } from '../components/wizard/WizardStepShell'
-import { useStepNavigation } from '../hooks/useStepNavigation'
-import { useRequireStepData } from '../hooks/useRequireStepData'
-import { STEP2_FIELDS, type FormValues } from '../schemas/formSchema'
-import { CEP_MASK } from '../constants/masks'
-import { UF_OPTIONS } from '../constants/options'
-import { fetchAddressByCep } from '../lib/api'
-import { onlyDigits } from '../lib/masks'
- 
-type CepStatus = 'idle' | 'loading' | 'success' | 'not-found'
- 
-const CEP_STATUS_HINTS: Partial<Record<CepStatus, string>> = {
-  loading: 'Buscando endereço...',
-  'not-found': 'CEP não encontrado, preencha manualmente.',
-}
- 
-export function Step2Address() {
-  useRequireStepData('fullName', '/etapa-1')
-  const { control, setValue } = useFormContext<FormValues>()
-  const { goNext, goBack } = useStepNavigation(STEP2_FIELDS)
-  const [cepStatus, setCepStatus] = useState<CepStatus>('idle')
-  const cep = useWatch({ control, name: 'cep' })
- 
-  useEffect(() => {
-    const digits = onlyDigits(cep ?? '')
-    if (digits.length !== 8) {
-      setCepStatus('idle')
-      return
-    }
- 
-    let cancelled = false
-    setCepStatus('loading')
- 
-    fetchAddressByCep(digits).then((result) => {
-      Iif (cancelled) return
-      if (result) {
-        setValue('endereco', result.endereco, { shouldValidate: true })
-        setValue('bairro', result.bairro, { shouldValidate: true })
-        setValue('cidade', result.cidade, { shouldValidate: true })
-        setValue('estado', result.estado.toUpperCase(), { shouldValidate: true })
-        setCepStatus('success')
-      } else {
-        setCepStatus('not-found')
-      }
-    })
- 
-    return () => {
-      cancelled = true
-    }
-  }, [cep, setValue])
- 
-  return (
-    <WizardStepShell
-      title="Informações Residenciais"
-      description="Informe o CEP para preenchermos o endereço automaticamente."
-    >
-      <div className="field-grid">
-        <MaskedTextField
-          name="cep"
-          label="CEP"
-          maskOptions={CEP_MASK}
-          placeholder="00000-000"
-          inputMode="numeric"
-          fullWidth
-          hint={CEP_STATUS_HINTS[cepStatus]}
-        />
-        <TextField name="endereco" label="Endereço" fullWidth />
-        <TextField name="bairro" label="Bairro" />
-        <TextField name="cidade" label="Cidade" />
-        <SelectField name="estado" label="Estado" options={UF_OPTIONS} placeholder="UF" />
-      </div>
-      <WizardActions onBack={() => goBack('/etapa-1')} onNext={() => goNext('/etapa-3')} />
-    </WizardStepShell>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/pages/Step3Professional.tsx.html b/frontend/coverage/src/pages/Step3Professional.tsx.html deleted file mode 100644 index 347f93e..0000000 --- a/frontend/coverage/src/pages/Step3Professional.tsx.html +++ /dev/null @@ -1,253 +0,0 @@ - - - - - - Code coverage report for src/pages/Step3Professional.tsx - - - - - - - - - -
-
-

All files / src/pages Step3Professional.tsx

-
- -
- 80% - Statements - 16/20 -
- - -
- 50% - Branches - 4/8 -
- - -
- 77.77% - Functions - 7/9 -
- - -
- 93.33% - Lines - 14/15 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57  -  -  -  -  -  -  -  -  -  -  -  -  -13x -13x -13x -13x -  -13x -4x -  -4x -  -4x -4x -  -  -  -  -  -4x -  -  -4x -4x -  -  -  -13x -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  - 
import { useEffect, useState } from 'react'
-import { TextField } from '../components/form/TextField'
-import { SelectField } from '../components/form/SelectField'
-import { CurrencyField } from '../components/form/CurrencyField'
-import { WizardActions } from '../components/wizard/WizardActions'
-import { WizardStepShell } from '../components/wizard/WizardStepShell'
-import { useStepNavigation } from '../hooks/useStepNavigation'
-import { useRequireStepData } from '../hooks/useRequireStepData'
-import { STEP3_FIELDS } from '../schemas/formSchema'
-import { TEMPO_EMPRESA_OPTIONS, type SelectOption } from '../constants/options'
-import { fetchProfessions } from '../lib/api'
- 
-export function Step3Professional() {
-  useRequireStepData('endereco', '/etapa-2')
-  const { goNext, goBack } = useStepNavigation(STEP3_FIELDS)
-  const [professionOptions, setProfessionOptions] = useState<SelectOption[]>([])
-  const [loadingProfessions, setLoadingProfessions] = useState(true)
- 
-  useEffect(() => {
-    let cancelled = false
- 
-    fetchProfessions()
-      .then((professions) => {
-        Iif (cancelled) return
-        setProfessionOptions(professions.map((p) => ({ value: p.name, label: p.name })))
-      })
-      .catch(() => {
-        if (!cancelled) setProfessionOptions([])
-      })
-      .finally(() => {
-        Eif (!cancelled) setLoadingProfessions(false)
-      })
- 
-    return () => {
-      cancelled = true
-    }
-  }, [])
- 
-  return (
-    <WizardStepShell title="Informações Profissionais" description="Conte-nos um pouco sobre sua vida profissional.">
-      <div className="field-grid">
-        <TextField name="empresa" label="Empresa" fullWidth />
-        <SelectField
-          name="profissao"
-          label="Profissão"
-          options={professionOptions}
-          placeholder={loadingProfessions ? 'Carregando profissões...' : 'Selecione...'}
-          disabled={loadingProfessions}
-        />
-        <CurrencyField name="salario" label="Salário" />
-        <SelectField name="tempoDeEmpresa" label="Tempo de empresa" options={TEMPO_EMPRESA_OPTIONS} fullWidth />
-      </div>
-      <WizardActions onBack={() => goBack('/etapa-2')} onNext={() => goNext('/resumo')} nextLabel="Ver resumo" />
-    </WizardStepShell>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/pages/SummaryPage.tsx.html b/frontend/coverage/src/pages/SummaryPage.tsx.html deleted file mode 100644 index a69a443..0000000 --- a/frontend/coverage/src/pages/SummaryPage.tsx.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - Code coverage report for src/pages/SummaryPage.tsx - - - - - - - - - -
-
-

All files / src/pages SummaryPage.tsx

-
- -
- 83.33% - Statements - 10/12 -
- - -
- 100% - Branches - 0/0 -
- - -
- 66.66% - Functions - 4/6 -
- - -
- 83.33% - Lines - 10/12 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72  -  -  -  -  -  -  -  -  -  -  -10x -10x -10x -10x -  -  -2x -  -  -  -1x -1x -1x -  -  -10x -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useNavigate } from 'react-router-dom'
-import { useFormContext } from 'react-hook-form'
-import { WizardStepShell } from '../components/wizard/WizardStepShell'
-import { SummarySection } from '../components/wizard/SummarySection'
-import { useRequireStepData } from '../hooks/useRequireStepData'
-import { defaultFormValues, type FormValues } from '../schemas/formSchema'
-import { getOptionLabel, TEMPO_EMPRESA_OPTIONS, UF_OPTIONS } from '../constants/options'
-import { generateSummaryPdf } from '../lib/pdf'
-import { clearStoredFormValues } from '../lib/storage'
- 
-export function SummaryPage() {
-  useRequireStepData('profissao', '/etapa-3')
-  const { getValues, reset } = useFormContext<FormValues>()
-  const navigate = useNavigate()
-  const values = getValues()
- 
-  function handleExport() {
-    generateSummaryPdf(values)
-  }
- 
-  function handleNewRegistration() {
-    clearStoredFormValues()
-    reset(defaultFormValues)
-    navigate('/etapa-1')
-  }
- 
-  return (
-    <WizardStepShell title="Resumo do Cadastro" description="Confira seus dados antes de exportar o PDF.">
-      <SummarySection
-        title="Dados Pessoais"
-        onEdit={() => navigate('/etapa-1')}
-        items={[
-          { label: 'Nome completo', value: values.fullName },
-          { label: 'Data de nascimento', value: values.birthDate },
-          { label: 'CPF', value: values.cpf },
-          { label: 'Telefone', value: values.phone },
-          { label: 'E-mail', value: values.email },
-        ]}
-      />
-      <SummarySection
-        title="Informações Residenciais"
-        onEdit={() => navigate('/etapa-2')}
-        items={[
-          { label: 'CEP', value: values.cep },
-          { label: 'Endereço', value: values.endereco },
-          { label: 'Bairro', value: values.bairro },
-          { label: 'Cidade', value: values.cidade },
-          { label: 'Estado', value: getOptionLabel(UF_OPTIONS, values.estado) },
-        ]}
-      />
-      <SummarySection
-        title="Informações Profissionais"
-        onEdit={() => navigate('/etapa-3')}
-        items={[
-          { label: 'Empresa', value: values.empresa },
-          { label: 'Profissão', value: values.profissao },
-          { label: 'Salário', value: values.salario },
-          { label: 'Tempo de empresa', value: getOptionLabel(TEMPO_EMPRESA_OPTIONS, values.tempoDeEmpresa) },
-        ]}
-      />
-      <div className="wizard-actions">
-        <button type="button" className="btn btn-secondary" onClick={handleNewRegistration}>
-          Novo cadastro
-        </button>
-        <button type="button" className="btn btn-primary" onClick={handleExport}>
-          Exportar PDF
-        </button>
-      </div>
-    </WizardStepShell>
-  )
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/pages/index.html b/frontend/coverage/src/pages/index.html deleted file mode 100644 index b89b27d..0000000 --- a/frontend/coverage/src/pages/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for src/pages - - - - - - - - - -
-
-

All files src/pages

-
- -
- 88.88% - Statements - 56/63 -
- - -
- 62.5% - Branches - 10/16 -
- - -
- 82.6% - Functions - 19/23 -
- - -
- 94.64% - Lines - 53/56 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
Step1PersonalData.tsx -
-
100%3/3100%0/0100%2/2100%3/3
Step2Address.tsx -
-
96.42%27/2875%6/8100%6/6100%26/26
Step3Professional.tsx -
-
80%16/2050%4/877.77%7/993.33%14/15
SummaryPage.tsx -
-
83.33%10/12100%0/066.66%4/683.33%10/12
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/schemas/formSchema.ts.html b/frontend/coverage/src/schemas/formSchema.ts.html deleted file mode 100644 index b3f7c31..0000000 --- a/frontend/coverage/src/schemas/formSchema.ts.html +++ /dev/null @@ -1,328 +0,0 @@ - - - - - - Code coverage report for src/schemas/formSchema.ts - - - - - - - - - -
-
-

All files / src/schemas formSchema.ts

-
- -
- 100% - Statements - 15/15 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 14/14 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82  -  -  -  -  -13x -13x -351x -  -13x -  -  -  -  -29x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -13x -  -  -  -  -  -  -  -26x -  -  -  -  -13x -  -  -  -  -  -26x -  -  -  -  -  -13x -  -  -  -13x -13x -13x -  -13x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { z } from 'zod'
-import { isValidCPF } from '../lib/cpf'
-import { isValidBirthDate, parseCurrencyToNumber } from '../lib/masks'
-import { UF_OPTIONS } from '../constants/options'
- 
-const PHONE_REGEX = /^\(\d{2}\) \d{4,5}-\d{4}$/
-const CEP_REGEX = /^\d{5}-\d{3}$/
-const UF_CODES = UF_OPTIONS.map((option) => option.value)
- 
-export const personalDataSchema = z.object({
-  fullName: z
-    .string()
-    .trim()
-    .min(1, 'Informe o nome completo')
-    .refine((value) => value.trim().split(/\s+/).length >= 2, {
-      message: 'Informe nome e sobrenome',
-    }),
-  birthDate: z
-    .string()
-    .min(1, 'Informe a data de nascimento')
-    .refine(isValidBirthDate, { message: 'Data de nascimento inválida' }),
-  cpf: z
-    .string()
-    .min(1, 'Informe o CPF')
-    .refine(isValidCPF, { message: 'CPF inválido' }),
-  phone: z
-    .string()
-    .min(1, 'Informe o telefone')
-    .regex(PHONE_REGEX, 'Telefone inválido'),
-  email: z.string().min(1, 'Informe o e-mail').email('E-mail inválido'),
-})
- 
-export const addressSchema = z.object({
-  cep: z.string().min(1, 'Informe o CEP').regex(CEP_REGEX, 'CEP inválido'),
-  endereco: z.string().min(1, 'Informe o endereço'),
-  bairro: z.string().min(1, 'Informe o bairro'),
-  cidade: z.string().min(1, 'Informe a cidade'),
-  estado: z
-    .string()
-    .min(1, 'Informe o estado')
-    .refine((value) => UF_CODES.includes(value.toUpperCase()), {
-      message: 'Estado inválido',
-    }),
-})
- 
-export const professionalSchema = z.object({
-  empresa: z.string().min(1, 'Informe a empresa'),
-  profissao: z.string().min(1, 'Selecione uma profissão'),
-  salario: z
-    .string()
-    .min(1, 'Informe o salário')
-    .refine((value) => parseCurrencyToNumber(value) > 0, {
-      message: 'Informe um salário válido',
-    }),
-  tempoDeEmpresa: z.string().min(1, 'Selecione o tempo de empresa'),
-})
- 
-export const formSchema = personalDataSchema.merge(addressSchema).merge(professionalSchema)
- 
-export type FormValues = z.infer<typeof formSchema>
- 
-export const STEP1_FIELDS = Object.keys(personalDataSchema.shape) as (keyof FormValues)[]
-export const STEP2_FIELDS = Object.keys(addressSchema.shape) as (keyof FormValues)[]
-export const STEP3_FIELDS = Object.keys(professionalSchema.shape) as (keyof FormValues)[]
- 
-export const defaultFormValues: FormValues = {
-  fullName: '',
-  birthDate: '',
-  cpf: '',
-  phone: '',
-  email: '',
-  cep: '',
-  endereco: '',
-  bairro: '',
-  cidade: '',
-  estado: '',
-  empresa: '',
-  profissao: '',
-  salario: '',
-  tempoDeEmpresa: '',
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage/src/schemas/index.html b/frontend/coverage/src/schemas/index.html deleted file mode 100644 index 89e1ff7..0000000 --- a/frontend/coverage/src/schemas/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for src/schemas - - - - - - - - - -
-
-

All files src/schemas

-
- -
- 100% - Statements - 15/15 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 14/14 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
formSchema.ts -
-
100%15/15100%0/0100%4/4100%14/14
-
-
-
- - - - - - - - \ No newline at end of file