Skip to content

Feature/sprint 8b auth onboarding#136

Merged
frigini merged 63 commits intomasterfrom
feature/sprint-8b-auth-onboarding
Mar 5, 2026
Merged

Feature/sprint 8b auth onboarding#136
frigini merged 63 commits intomasterfrom
feature/sprint-8b-auth-onboarding

Conversation

@frigini
Copy link
Owner

@frigini frigini commented Feb 18, 2026

Summary by CodeRabbit

  • New Features

    • Customer & provider self-registration with multi-step onboarding, provider dashboard, status view, and visible tier badges.
    • Social login (Google, Facebook) plus credentials sign‑in; in-app registration/login UX improvements.
    • Provider document upload with file metadata, CEP address lookup, service management, and WhatsApp contact links.
    • Provider tiering (Standard/Silver/Gold/Platinum) and tier management.
  • Documentation

    • Roadmap updated with new MVP date, re-sequenced sprints, risks, and architectural notes; frontend testing plan added.
  • Localization

    • Validation and error messages localized to Portuguese.

Filipe Frigini added 2 commits February 18, 2026 17:19
…tion, profile management, document handling, and tier system.
…figuration, Docker Compose, and Aspire AppHost integration.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Warning

Rate limit exceeded

@frigini has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 10 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0151809d-c999-44b5-bd72-0f980fe54069

📥 Commits

Reviewing files that changed from the base of the PR and between ca2d9c1 and 4d41197.

📒 Files selected for processing (2)
  • docs/roadmap.md
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/perfil/page.tsx
📝 Walkthrough

Walkthrough

Roadmap and Keycloak infra updated; provider self-registration/onboarding added (tiers, documents, services); DTOs, commands, handlers, EF migrations and DB permissions updated; extensive frontend pages/hooks/components and API clients added; Testcontainers RabbitMQ integrated into test infra; multiple validators, rate limits, and auth flows introduced.

Changes

Cohort / File(s) Summary
Roadmap & Docs
docs/roadmap.md, docs/testing/frontend-testing-plan.md
Rescheduled MVP/sprints, added NX/architecture decisions and a comprehensive frontend testing plan.
Compose & Keycloak envs
infrastructure/compose/environments/.env.example, infrastructure/compose/environments/development.yml, infrastructure/compose/standalone/keycloak-only.yml
Keycloak moved to Postgres-backed config; added bootstrap admin vars and social login env vars; updated import paths and hosts.
Keycloak Realm
infrastructure/keycloak/realms/meajudaai-realm.dev.json
Added Google/Facebook identityProviders and enabled directAccessGrants for customer-app.
AppHost & Keycloak helpers
src/Aspire/MeAjudaAi.AppHost/Program.cs, .../Extensions/KeycloakExtensions.cs
Dev helper injects social provider envs; introduced KC_BOOTSTRAP_ADMIN_* and disabled inbound claim mapping.
Provider Domain & Persistence
src/Modules/Providers/Domain/..., .../Infrastructure/Persistence/..., .../Migrations/*
Added EProviderTier, Provider.Tier, PromoteTier/UpdateServices, ProviderTierUpdatedDomainEvent; Document.FileName/FileUrl; updated EF mappings and added migrations (tier, document file props, constraint normalization, snapshots).
Providers API & Handlers
src/Bootstrapper/.../ProviderRegistrationEndpoints.cs, src/Modules/Providers/API/..., src/Modules/Providers/Application/Commands/*, Handlers/*
New public endpoints: provider self-registration (public and authenticated), GetMyProviderStatus, UploadMyDocument; RegisterProviderCommand + handler; AddDocumentCommand extended with file metadata; validators added.
Providers Frontend
src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/..., components/..., hooks/..., lib/schemas/*, types/api/provider.ts
New onboarding pages (register, profile, address, documents), document upload UI/hooks (useDocumentUpload), status/profile hooks, schemas, enriched Provider DTO types and mappers.
Users / Registration & Keycloak
src/Modules/Users/API/..., Application/..., Infrastructure/...
RegisterCustomer endpoint/command/handler/validator; User.Create factory returning Result; IUserDomainService.DeactivateUserInKeycloak and Keycloak deactivation compensation on failures; admin-token flow improvements.
Frontend API & Utilities
src/Web/MeAjudaAi.Web.Customer/lib/api/*, lib/api/mappers.ts, lib/api/fetch-client.ts, lib/api/response-utils.ts
Introduced baseFetch/authenticatedFetch/publicFetch with ApiError, response normalization/unwrap helpers, richer client mappers and typed ApiResponse/ApiError.
Permissions, Roles & Rate Limits
src/Shared/Authorization/Core/EPermission.cs, src/Shared/Utilities/UserRoles.cs, src/Shared/Utilities/Constants/RateLimitPolicies.cs, src/Bootstrapper/.../SecurityExtensions.cs
Added self-registration and provider permissions, provider-tier role constants and helpers, new rate-limit policies for registration and provider-registration.
DB Init & Permissions
infrastructure/database/..., infrastructure/database/views/cross-module-views.sql
Removed cross-module views and some cross-module grants; made spatial_ref_sys grants existence-aware; adjusted module permission scripts.
Testing / CI / Testcontainers
Directory.Packages.props, tests/..., tests/*/packages.lock.json, tests/MeAjudaAi.Shared.Tests/.../SharedTestContainers.cs
Added Testcontainers.RabbitMq, integrated RabbitMQ and Azurite into shared test infra, added PostGIS pre-migration step, updated tests and CI triggers.
Frontend UI / UX & Helpers
src/Web/MeAjudaAi.Web.Customer/components/*, components/auth/*, lib/utils/*, app/(auth)/*, globals.css, next.config.ts
New Checkbox, Avatar/VerifiedBadge updates, auth forms/pages, Customer register flow, phone/whatsapp util, normalization helpers, image remotePatterns and theme tokens.
Migrations & Naming
src/Modules/Providers/Infrastructure/Persistence/ProvidersDbContextFactory.cs, .../Migrations/*
Enabled UseSnakeCaseNamingConvention and applied several EF migrations/designer snapshots with normalized constraint names.
Miscellaneous
scripts/dev.ps1, middlewares, security headers, removed EnsureDatabaseMigrations calls`
Dev .env loader, SecurityHeadersMiddleware refactor (OnStarting), migration ordering priority, removed automatic migrations from several modules.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Browser
    participant Frontend as Customer UI
    participant API as API Server
    participant Keycloak as Keycloak
    participant DB as Database
    participant Storage as Blob Storage

    Frontend->>API: POST /api/v1/providers/become (RegisterProviderRequest)
    activate API
    API->>Keycloak: Validate token / claims (optional)
    Keycloak-->>API: token/claims
    API->>DB: Check provider by userId
    DB-->>API: Not found / Existing
    API->>DB: Create Provider, Document metadata (FileName/FileUrl), emit events
    DB-->>API: OK
    API-->>Frontend: 201 Created {ProviderDto}
    deactivate API

    Frontend->>API: GET /api/v1/providers/me/documents/sas (request SAS)
    API->>Storage: Request SAS URL
    Storage-->>API: SAS URL
    API-->>Frontend: SAS URL
    Frontend->>Storage: PUT file to SAS URL
    Storage-->>Frontend: 200 OK
    Frontend->>API: POST /api/v1/providers/me/documents (metadata with FileUrl)
    API->>DB: Insert/Update Document (FileName/FileUrl)
    DB-->>API: OK
    API-->>Frontend: 200 {ProviderDto with Documents}
Loading
sequenceDiagram
    participant Browser as Client
    participant Keycloak as Keycloak
    participant API as API Server
    participant DB as Database

    Browser->>Keycloak: OAuth signin (idp_hint=google/facebook)
    Keycloak-->>Browser: Redirect + tokens
    Browser->>API: GET /api/v1/providers/me/status (Bearer token)
    API->>Keycloak: (optional) introspect/validate token
    API->>DB: Query provider by userId
    DB-->>API: ProviderStatusDto
    API-->>Browser: 200 {ProviderStatusDto}
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐇 I hopped through code with nimble feet,
Tiers and uploads—what a treat.
Keycloak greets the login cheer,
Providers bloom and tests run clear.
A carrot patch of commits complete. 🥕

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/sprint-8b-auth-onboarding

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/Modules/Providers/Tests/Unit/Application/Validators/AddDocumentRequestValidatorTests.cs (1)

46-48: ⚠️ Potential issue | 🟠 Major

Translate validation messages to Portuguese to align with language policy.

The AddDocumentRequestValidator uses English messages ("Document number is required", etc.), but the codebase language policy requires user-facing validation messages in Portuguese. All other validators in the codebase follow this pattern (e.g., "Rua é obrigatória", "Email é obrigatório"). Update the four .WithMessage() calls in the validator to Portuguese, and update the corresponding test assertion at line 47.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Providers/Tests/Unit/Application/Validators/AddDocumentRequestValidatorTests.cs`
around lines 46 - 48, The test assertion and validator messages are using
English; update them to Portuguese: change the assertion
result.ShouldHaveValidationErrorFor(x => x.Number).WithErrorMessage("Document
number is required") to use the Portuguese message, and update the four
.WithMessage(...) calls inside the AddDocumentRequestValidator (the rules that
reference Number and the other three fields) to their Portuguese equivalents
(e.g., "Número do documento é obrigatório" or the project's established phrasing
like "Número é obrigatório", and translate the other three messages consistently
with existing validators such as "Rua é obrigatória" and "Email é obrigatório").
Ensure the test string matches the new Portuguese message used in
AddDocumentRequestValidator so the test asserts the translated message.
infrastructure/compose/standalone/keycloak-only.yml (1)

20-26: ⚠️ Potential issue | 🟡 Minor

Remove duplicate KC_HOSTNAME_STRICT_HTTPS entry.

There are two identical KC_HOSTNAME_STRICT_HTTPS: false entries; keep only one to avoid confusion.

🧹 Suggested cleanup
-      KC_HOSTNAME_STRICT_HTTPS: false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/compose/standalone/keycloak-only.yml` around lines 20 - 26,
The environment block contains a duplicate KC_HOSTNAME_STRICT_HTTPS entry;
remove one of the two KC_HOSTNAME_STRICT_HTTPS: false lines so only a single
KC_HOSTNAME_STRICT_HTTPS variable remains alongside KC_HOSTNAME_STRICT,
KC_HTTP_ENABLED, KEYCLOAK_ADMIN and KEYCLOAK_ADMIN_PASSWORD to avoid redundancy
and confusion.
docs/roadmap.md (1)

10-45: ⚠️ Potential issue | 🟡 Minor

Resolve conflicting MVP dates.

The roadmap lists multiple MVP targets (e.g., 14 Março 2026 vs 28 Março 2026). Please pick a single MVP launch date and update all references to match.

Also applies to: 2132-2173

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap.md` around lines 10 - 45, Conflicting MVP dates exist (the "MVP
Target: 14/Março/2026" text and the "🎯 **28 Março 2026**: MVP Launch" entry);
pick one canonical launch date (e.g., 28 Março 2026) and update every occurrence
to match — specifically replace the "MVP Target: 14/Março/2026" token and any
other mentions (including the "🎯 MVP Launch" entry and the repeated entries
referenced by "Also applies to: 2132-2173") so the roadmap consistently shows
the single chosen date.
🧹 Nitpick comments (11)
src/Shared/Authorization/Core/EPermission.cs (1)

126-144: New permission entries are well-structured and follow existing conventions.

The Display(Name) values are consistent with the module:action pattern used throughout. XML documentation is in Portuguese per language policy. Appending to the end of the enum preserves existing ordinal values for backward compatibility.

One observation: UsersRegister and ProvidersRegister are documented as public/unauthenticated endpoints. If these permissions are never checked at runtime (since the endpoints are AllowAnonymous), consider adding a brief comment clarifying they exist for documentation/auditing purposes to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Authorization/Core/EPermission.cs` around lines 126 - 144, Add a
short clarifying comment near the UsersRegister and ProvidersRegister enum
members to state these permissions correspond to public AllowAnonymous endpoints
and are included only for documentation/auditing/consistency (they are not
enforced at runtime); update the comments adjacent to the UsersRegister and
ProvidersRegister members in the EPermission enum so reviewers and future
maintainers understand these entries are intentionally present despite not being
checked at authorization time.
src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs (1)

64-65: Internal error messages returned directly to the client.

userResult.Error.Message and providerResult.Error.Message are forwarded verbatim to the caller. If these contain internal details (stack traces, DB constraint names, Keycloak error details), this leaks implementation information. Consider returning a generic user-facing message in Portuguese and logging the details server-side.

Also applies to: 95-96

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs`
around lines 64 - 65, Replace returning internal error strings from
userResult.Error.Message and providerResult.Error.Message with fixed,
user-friendly Portuguese responses (e.g., "Ocorreu um erro ao registrar o
usuário." and "Ocorreu um erro ao registrar o provedor.") and return those with
Results.BadRequest; simultaneously log the full error details server-side (e.g.,
log userResult.Error and providerResult.Error with the existing ILogger or
logger instance) so the internal message/stack is not exposed to clients; apply
the same change for the checks around variables userResult and providerResult
(the failure branches currently returning their .Error.Message).
src/Modules/Providers/Application/Handlers/RegisterProviderCommandHandler.cs (1)

26-32: Silent idempotency on existing provider — consider logging or returning a distinct status.

When a provider already exists for the UserId, the handler silently returns the existing DTO (line 29-32) without any log or status distinction. The caller (and any monitoring) has no way to differentiate a new registration from a duplicate attempt. At minimum, add an info-level log.

Proposed improvement
         var existingProvider = await _providerRepository.GetByUserIdAsync(command.UserId, cancellationToken);
         if (existingProvider is not null)
         {
+            _logger.LogInformation("Provider already exists for user {UserId}, returning existing profile", command.UserId);
             return ProviderMapper.ToDto(existingProvider);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Providers/Application/Handlers/RegisterProviderCommandHandler.cs`
around lines 26 - 32, The handler's idempotent path in
Handle(RegisterProviderCommand, CancellationToken) returns
ProviderMapper.ToDto(existingProvider) when
_providerRepository.GetByUserIdAsync(...) finds an existing provider but emits
no log or status; update this branch to log an info-level message (e.g., using
the existing logger instance) indicating a duplicate registration attempt for
the given command.UserId and, optionally, return a distinct result status or
wrapper value (e.g., Result indicating AlreadyExists) so callers/monitoring can
distinguish new vs duplicate registrations instead of silently returning the
DTO.
src/Modules/Providers/Domain/Entities/Provider.cs (1)

505-533: Consider whether PromoteTier should gate on provider status.

The method allows tier changes on any non-deleted provider (including PendingBasicInfo, Rejected, Suspended). If a provider is rejected or suspended, should they still be able to upgrade their tier via a Stripe webhook? This may be intentional (the XML docs say "Não há restrição de progressão"), but accepting payment for a rejected/suspended provider could create a confusing billing state.

If this is deliberate, no action needed — just flagging for awareness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Providers/Domain/Entities/Provider.cs` around lines 505 - 533,
PromoteTier currently allows tier changes for any non-deleted provider; add a
guard that prevents tier updates for undesirable statuses (e.g.,
PendingBasicInfo, Rejected, Suspended) or explicitly document why it's allowed.
Locate the PromoteTier method and either (a) add a check against the provider
Status property (e.g., if Status == ProviderStatus.Rejected || Status ==
ProviderStatus.Suspended || Status == ProviderStatus.PendingBasicInfo) and throw
a ProviderDomainException, or (b) add a clear XML doc comment clarifying that
tier changes are allowed regardless of Status; ensure the
ProviderTierUpdatedDomainEvent and MarkAsUpdated() behavior remains consistent
after the change.
src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx (1)

71-100: Raw enum values displayed in Portuguese UI.

providerStatus.tier (e.g. "Standard") and providerStatus.status (e.g. "PendingBasicInfo") render as-is. In a Portuguese-language UI, these should be mapped to localized labels (e.g. "Padrão", "Pendente"). Consider a small display-name helper similar to ToDisplayName used in the Admin web (ProviderConstants.cs).

Also, while loading (providerStatus is undefined), the fallback "Quero trabalhar" link flashes briefly before resolving. You could use the isLoading state from the hook to suppress the section entirely during the initial fetch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx` around lines
71 - 100, The UI is rendering raw enum values (providerStatus.tier and
providerStatus.status) and flashing the fallback link while providerStatus is
undefined; map those enums to Portuguese display labels (use or add a small
helper like ToDisplayName/ProviderConstants mapping for tier and status) and
replace direct usages of providerStatus.tier and providerStatus.status inside
DropdownMenuLabel and the status span with the mapped display strings;
additionally, suppress rendering the entire provider block while the hook is
loading by checking the hook's isLoading before showing either the
providerStatus branch or the fallback DropdownMenuItem (i.e., use isLoading to
skip rendering until fetch completes).
src/Web/MeAjudaAi.Web.Customer/hooks/use-provider-status.ts (1)

8-13: ApiResponse<T> interface is duplicated across hooks.

This same shape exists in use-register-provider.ts (as ApiSuccessResponse<T>). Consider extracting a shared type to a common location (e.g., @/types/api.ts) to keep them in sync.

Based on learnings: prefer reuse of shared contracts to keep Web aligned with backend/shared code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-provider-status.ts` around lines 8 -
13, The ApiResponse<T> interface defined in use-provider-status.ts duplicates
the ApiSuccessResponse<T> type in use-register-provider.ts; extract a single
shared type (e.g., ApiResponse<T>) into a new shared file like `@/types/api.ts`
and update both hooks to import and use that shared type instead of their local
declarations (refer to ApiResponse<T>, ApiSuccessResponse<T>,
use-provider-status.ts and use-register-provider.ts to locate the duplicated
definitions).
src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx (1)

16-22: Document number validation allows non-digit characters.

The documentNumber field only checks length (11–14), but doesn't enforce digits-only. A user could submit "abcdefghijk" and pass client validation. Consider adding a .regex(/^\d+$/, "Apenas números são permitidos") constraint.

Proposed fix
-    documentNumber: z.string().min(11, "Documento inválido").max(14, "Documento inválido"),
+    documentNumber: z.string().regex(/^\d{11,14}$/, "Documento inválido (apenas números, 11 a 14 dígitos)"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx` around lines
16 - 22, The documentNumber validation in the formSchema allows non-digit
characters because it only enforces length; update the documentNumber validator
inside formSchema (in src/Web/.../page.tsx) to require digits-only by adding a
regex constraint (e.g., append .regex(/^\d+$/, "Apenas números são permitidos")
to the existing z.string().min(...).max(...)) so only numeric CPF/CNPJ values
pass client validation.
src/Modules/Providers/API/Endpoints/ProvidersModuleEndpoints.cs (1)

30-46: XML doc doesn't list the three new endpoints.

The <remarks> block enumerates every endpoint but omits POST /register, POST /me/documents, and GET /me/status. Keeping it in sync avoids confusion for future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Providers/API/Endpoints/ProvidersModuleEndpoints.cs` around lines
30 - 46, Update the XML <remarks> block in ProvidersModuleEndpoints.cs to
include the three new endpoints that are missing from the list: add entries for
"POST /register", "POST /me/documents", and "GET /me/status" alongside the
existing entries so the documentation enumerates every route exposed by the
Providers module; modify the <remarks> text near the existing endpoints list to
insert those three lines in the same style/format as the other bullet points.
src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts (2)

23-30: Request fires with "Bearer undefined" when session has no token.

If session is null or accessToken is missing, the header becomes Authorization: Bearer undefined. Since useMutation is user-triggered, the user is likely authenticated, but a defensive guard would surface a clearer error.

🛡️ Suggested guard
         mutationFn: async (data: RegisterProviderRequest): Promise<ProviderDto> => {
+            if (!session?.accessToken) {
+                throw new Error("User is not authenticated");
+            }
             const config = client.getConfig();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts` around lines
23 - 30, The fetch call in use-register-provider (inside the useRegisterProvider
hook) may send Authorization: "Bearer undefined" when session or
session.accessToken is missing; add a defensive guard before calling fetch:
verify session and session.accessToken (or token variable used) and if missing
either throw a clear Error (e.g., "Missing access token") or return a rejected
Promise so useMutation surfaces the error, and only include the Authorization
header when a valid token exists (or fail-fast instead of sending "Bearer
undefined").

8-12: Duplicate response wrapper — reuse ApiResponse<T> from @/types/api.ts.

A local ApiSuccessResponse<T> is defined here, but the codebase already has ApiResponse<T> in types/api.ts (used by use-provider-status.ts). Consolidate to avoid drift between the two definitions.

♻️ Proposed fix
-import { RegisterProviderRequest, ProviderDto } from "@/types/provider";
+import { RegisterProviderRequest, ProviderDto } from "@/types/provider";
+import { ApiResponse } from "@/types/api";
-
-interface ApiSuccessResponse<T> {
-    data: T;
-    isSuccess: boolean;
-    // ...
-}

Then at Line 37:

-            const json = await response.json() as ApiSuccessResponse<ProviderDto>;
+            const json = await response.json() as ApiResponse<ProviderDto>;

Based on learnings: "prefer reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts` around lines 8
- 12, Replace the local ApiSuccessResponse<T> declaration with the shared
ApiResponse<T> type: remove the local interface in use-register-provider.ts, add
an import for ApiResponse from "@/types/api" and update all references in this
file (e.g., any usages in functions/hooks) to use ApiResponse<T> instead of
ApiSuccessResponse<T> so the hook aligns with the existing shared contract used
by use-provider-status.ts.
src/Modules/Providers/API/Endpoints/Public/RegisterProviderEndpoint.cs (1)

16-24: Registration returns 200 OK unconditionally — consider 201 Created for new providers.

The bootstrapper-level endpoint (ProviderRegistrationEndpoints.cs) returns Results.Created(...) for new registrations, while this endpoint always returns 200 OK via Handle(result). If the handler returns the existing provider on duplicate registration (confirmed by RegisterProviderCommandHandler), returning 200 for idempotent re-registrations is fine, but a 201 on actual creation would give the client a clearer signal.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Providers/API/Endpoints/Public/RegisterProviderEndpoint.cs`
around lines 16 - 24, The endpoint always returns 200 OK via
RegisterProviderAsync/Handle(result) even when a new provider was created;
update the endpoint logic in Map/RegisterProviderAsync to detect when the
handler indicates creation (use the result from
RegisterProviderCommandHandler/RegisterProviderAsync) and return a 201 Created
response (e.g., Results.Created with the resource location or route name
"RegisterProvider" and the created ProviderDto) while still returning 200 OK for
idempotent/duplicate registrations; ensure Response<ProviderDto> produced
metadata matches both cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/roadmap.md`:
- Around line 41-43: Update the inconsistent Sprint 8B/8C entries so naming and
status match the schedule: change the line containing "🔄 **19 Fev - 4 Mar
2026**: Sprint 8B - Authentication & Onboarding Flow (EM ANDAMENTO)" to reflect
the correct name and status (either rename to "Mobile App" if that is the
canonical label or change status from "(EM ANDAMENTO)" to "(PLANEJADO)" to match
the PR date), and ensure the adjacent "⏳ **5-18 Mar 2026**: Sprint 8C - Mobile
App (React Native + Expo)" entry uses the complementary label; apply the same
alignment to the other occurrences referenced (sections around 1507-1556 and
2168-2170) so all sprint names and statuses are consistent across the document.

In `@infrastructure/compose/environments/development.yml`:
- Around line 57-58: Remove the duplicate KC_HOSTNAME_STRICT_HTTPS YAML key so
it's declared only once; locate the environment block where
KC_HOSTNAME_STRICT_HTTPS is defined (both entries currently set to false) and
delete the redundant line, leaving a single KC_HOSTNAME_STRICT_HTTPS: false
entry to avoid ambiguity.

In `@infrastructure/keycloak/realms/meajudaai-realm.dev.json`:
- Around line 96-141: The realm JSON includes an Instagram identity provider
(alias "instagram", providerId "instagram") but Keycloak 26+ disables that
broker by default; either remove the Instagram provider block from the realm
JSON or enable the broker by adding --features=instagram-broker to your Keycloak
startup args in all compose/startup manifests (keycloak-only.yml,
development.yml, testing.yml, base/keycloak.yml) so the provider becomes
functional; choose one approach and apply consistently across the deployment
manifests and the realm file.

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs`:
- Around line 47-49: The phone sanitization currently only strips spaces, dashes
and parentheses for request.PhoneNumber and can leave characters like '+' that
break usernames; update the sanitization in ProviderRegistrationEndpoints (where
phone and username are built) to use a regex-based cleanup (e.g., replace all
non-digit characters via Regex.Replace(request.PhoneNumber, @"\D", "")) so only
digits remain, ensure the System.Text.RegularExpressions namespace is imported,
and then build username as $"provider_{phone}" from the cleaned digits.
- Line 56: The temporary password generated as Guid.NewGuid().ToString("N") in
ProviderRegistrationEndpoints (the registration payload) fails
CreateUserCommandValidator's ValidationConstants.Patterns.Password; change the
registration logic in ProviderRegistrationEndpoints.cs to either omit the
temporary password and let Keycloak/social handle credentials, or generate a
compliant temporary password (e.g., include at least one uppercase, one
lowercase, one digit and length >=8) before sending to CreateUserCommand (ensure
the same change is applied where Password is set in the registration payload so
CreateUserCommand validation passes).
- Around line 61-96: After creating the user (CreateUserCommand via
commandDispatcher.SendAsync returning userResult), add a compensation step: if
the subsequent CreateProviderCommand (createProviderCommand / providerResult)
fails, explicitly remove the previously created Keycloak user to avoid orphaned
identities; call the existing Keycloak/delete user API or dispatch a
DeleteUserCommand (using the userResult.Value!.Id or userResult.Value.Username)
in the providerResult.IsFailure branch, await that call, log success/failure,
and surface a clear error to the client; ensure this cleanup is idempotent and
any cleanup failures are logged but do not mask the original provider creation
error.

In
`@src/Modules/Providers/API/Endpoints/Public/Me/GetMyProviderStatusEndpoint.cs`:
- Around line 22-30: The OpenAPI metadata for the endpoint mapped by
Map(...).MapGet("me/status", GetMyStatusAsync) is missing a 400 response; the
GetMyStatusAsync handler returns BadRequest(...) for invalid requests, so update
the endpoint fluent chain to include a 400 response by adding
.Produces(StatusCodes.Status400BadRequest) (or
.Produces<Response<YourErrorDto>>(StatusCodes.Status400BadRequest) if you want a
typed response) alongside the existing 200 and 404 declarations so Swagger
correctly documents BadRequest responses.

In `@src/Modules/Providers/API/Endpoints/Public/Me/UploadMyDocumentEndpoint.cs`:
- Around line 55-56: The current handler in UploadMyDocumentEndpoint.cs returns
raw providerResult.Error.Message on failure; change the BadRequest to return a
localized, user-facing message instead (either a hardcoded Portuguese string
matching the endpoint style or a ValidationMessages constant). Locate the
failure check around providerResult (the if (providerResult.IsFailure) branch)
and replace BadRequest(providerResult.Error.Message) with
BadRequest(ValidationMessages.YourAppropriateConstant) or BadRequest("Não foi
possível enviar o documento. Verifique os dados e tente novamente."), keeping
the original flow and HTTP status but ensuring consistent localization.
- Around line 28-34: The endpoint mapped by Map -> app.MapPost("me/documents",
UploadMyDocumentAsync) currently calls RequireAuthorization() with no
parameters, allowing any authenticated user; update this to enforce the
provider-* role by applying an authorization policy or role requirement (e.g.,
add a policy that checks roles starting with "provider-" similar to the existing
SelfOrAdmin policy and call RequireAuthorization("ProviderPolicy") or use
RequireAuthorization with the appropriate role/ policy name), or if the generic
authenticated behavior is intentional, update the endpoint documentation to
remove the "provider-*" requirement; change should reference
UploadMyDocumentAsync and the RequireAuthorization call.

In `@src/Modules/Providers/API/Endpoints/Public/RegisterProviderEndpoint.cs`:
- Around line 63-69: Rename the endpoint-local record RegisterProviderRequest to
a distinct name (e.g., RegisterProviderApiRequest) to avoid collision with
Application.DTOs.Requests.RegisterProviderRequest; update the record declaration
and all references in this file (the endpoint handler method signature,
parameter names, model binding usages, and any local variable references) to use
RegisterProviderApiRequest so the endpoint DTO clearly differs from the
application-layer DTO.

In `@src/Modules/Providers/Application/DTOs/Requests/RegisterProviderRequest.cs`:
- Around line 14-45: The RegisterProviderRequest DTO is missing the
DocumentNumber property so incoming JSON with documentNumber will be dropped;
add a public string DocumentNumber { get; init; } = string.Empty; to the
RegisterProviderRequest record so the deserialized payload provides the value
expected by RegisterProviderCommand and the Document entity; ensure the property
name matches casing/serialization conventions (DocumentNumber) and that any
mapping code (e.g., where RegisterProviderRequest is mapped to
RegisterProviderCommand) forwards this value.

In
`@src/Modules/Providers/Application/Handlers/RegisterProviderCommandHandler.cs`:
- Around line 72-75: In the catch block inside RegisterProviderCommandHandler
replace returning the exception message to consumers: stop concatenating
ex.Message into the Error returned by Result<ProviderDto>.Failure (the Error
with code "Provider.Registration") and instead return a fixed, generic
Portuguese user-facing message (e.g., "Erro inesperado ao registrar prestador.")
while logging the full exception server-side via your logger (e.g., ILogger or
processLogger) using LogError/LogCritical and include ex and contextual data;
ensure only the generic message is sent in the Error constructor and that the
exception details remain in the server logs for diagnostics.
- Around line 38-41: The current RegisterProviderCommandHandler creates an
Address with non-semantic placeholders via new Address("Pending", "0",
"Pending", "Pending", "Pending", "00000000") which will persist as valid but
meaningless data; change this by either (A) using clear sentinel values that
match expected formats (e.g., state="XX", zip="00000-000") before creating
Address so downstream filters/clients can detect placeholders, or (B) refactor
BusinessProfile to accept a nullable Address and update
RegisterProviderCommandHandler to pass null during initial registration and only
create/populate Address when the wizard completes; update any consumers of
BusinessProfile.Address to handle null/sentinel accordingly (look for Address
constructor usage and BusinessProfile.Address property).

In
`@src/Modules/Providers/Tests/Unit/API/Endpoints/UpdateMyProviderProfileEndpointTests.cs`:
- Around line 50-53: The test constructs a ProviderDto with Guid.NewGuid() which
doesn't match the locally defined providerId used later in the assertion; update
the ProviderDto instantiation in UpdateMyProviderProfileEndpointTests to use the
existing providerId variable instead of Guid.NewGuid() so the returned
ProviderDto.Id matches the providerId asserted in the Verify (check the
ProviderDto constructor call and the providerId variable referenced in the
Verify c.ProviderId == providerId).

In
`@src/Modules/Providers/Tests/Unit/API/Endpoints/UploadMyDocumentEndpointTests.cs`:
- Around line 121-152: The test currently asserts
result.Should().BeOfType<BadRequest<Result<ProviderDto>>> but the endpoint
pipeline uses BaseEndpoint.Handle which wraps failures in Response<object>, so
update the assertion to expect BadRequest<Response<object>>; locate the test
method UploadDocumentAsync_WhenCommandFails_ShouldReturnBadRequest (and the
helper UploadDocumentMethod()) and change the expected type from
BadRequest<Result<ProviderDto>> to BadRequest<Response<object>> (and adjust any
casts or variable names accordingly) so the unit test matches the Handle()
return contract.
- Around line 46-92: The test method
UploadDocumentAsync_WithValidRequest_ShouldUploadAndReturnCreated misstates the
expected response: the endpoint returns Ok<Result<ProviderDto>> (200) because
UploadMyDocumentEndpoint calls Handle(result) without a createdRoute; either
rename the test to UploadDocumentAsync_WithValidRequest_ShouldUploadAndReturnOk
(update the method symbol in the test) to match the current behavior, or change
the endpoint's Handle invocation in UploadMyDocumentEndpoint (pass a
createdRoute string like "UploadMyDocument" and appropriate routeValues such as
the new document/provider id) so Handle(result, "UploadMyDocument", routeValues)
produces a 201 Created; pick one and update corresponding assertions/route setup
accordingly.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/perfil/page.tsx`:
- Around line 43-48: Replace raw enum outputs with localized labels by creating
mapping objects (e.g., statusLabels: Record<EProviderStatus,string> and
tierLabels: Record<EProviderTier,string>) that map EProviderStatus and
EProviderTier values to Portuguese strings, then use
statusLabels[providerStatus.status] and tierLabels[providerStatus.tier] in the
JSX instead of {providerStatus.status} and {providerStatus.tier}; update the
span that currently uses providerStatus.status and any other places rendering
providerStatus.tier to reference these helpers so users see friendly Portuguese
labels.
- Around line 8-10: ProviderProfilePage currently only destructures data and
isLoading from useProviderStatus, so transient API failures render the "not a
provider" view; destructure error from useProviderStatus (e.g., const { data:
providerStatus, isLoading, error } = useProviderStatus()) and add an early error
branch before the existing !providerStatus check that renders an error message
("Erro ao carregar status") with a retry action (e.g., Button that calls
window.location.reload() or revalidate) so network/API errors show a proper
error state instead of the "Você ainda não é um prestador" screen.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-provider-status.ts`:
- Around line 46-52: The retry predicate in use-provider-status's retry function
is checking for numeric codes inside error.message but the thrown error is
created from response.statusText (e.g., "Unauthorized"), so update the error
creation (where you currently use response.statusText) to attach the numeric
status (e.g., set error.status = response.status or include the status in the
error message) and then change the retry predicate in retry to check the numeric
status (e.g., error.status === 401 || error.status === 403) instead of looking
for "401"/"403" in the message; this ensures 401/403 responses are not retried
and keeps the existing 404 handling intact.

In `@src/Web/MeAjudaAi.Web.Customer/types/provider.ts`:
- Around line 1-55: Update the frontend enums to match backend definitions:
modify EProviderType to include Cooperative and Freelancer (so values become
Individual, Company, Cooperative, Freelancer, and any other backend values),
change EProviderStatus to replace PendingDocuments with
PendingDocumentVerification, remove PendingVerification and Deleted, and keep
statuses that match backend (ensure the set matches backend Enum exactly), and
update EVerificationStatus to change UnderReview -> InProgress and Revoked ->
Suspended; verify EProviderTier remains unchanged. Locate and edit the
declarations for EProviderType, EProviderStatus, EVerificationStatus (and
confirm ProviderStatusDto/ProviderDto types still reference the updated enum
names) so the frontend enum string values exactly match the backend Enum
definitions.

---

Outside diff comments:
In `@docs/roadmap.md`:
- Around line 10-45: Conflicting MVP dates exist (the "MVP Target:
14/Março/2026" text and the "🎯 **28 Março 2026**: MVP Launch" entry); pick one
canonical launch date (e.g., 28 Março 2026) and update every occurrence to match
— specifically replace the "MVP Target: 14/Março/2026" token and any other
mentions (including the "🎯 MVP Launch" entry and the repeated entries
referenced by "Also applies to: 2132-2173") so the roadmap consistently shows
the single chosen date.

In `@infrastructure/compose/standalone/keycloak-only.yml`:
- Around line 20-26: The environment block contains a duplicate
KC_HOSTNAME_STRICT_HTTPS entry; remove one of the two KC_HOSTNAME_STRICT_HTTPS:
false lines so only a single KC_HOSTNAME_STRICT_HTTPS variable remains alongside
KC_HOSTNAME_STRICT, KC_HTTP_ENABLED, KEYCLOAK_ADMIN and KEYCLOAK_ADMIN_PASSWORD
to avoid redundancy and confusion.

In
`@src/Modules/Providers/Tests/Unit/Application/Validators/AddDocumentRequestValidatorTests.cs`:
- Around line 46-48: The test assertion and validator messages are using
English; update them to Portuguese: change the assertion
result.ShouldHaveValidationErrorFor(x => x.Number).WithErrorMessage("Document
number is required") to use the Portuguese message, and update the four
.WithMessage(...) calls inside the AddDocumentRequestValidator (the rules that
reference Number and the other three fields) to their Portuguese equivalents
(e.g., "Número do documento é obrigatório" or the project's established phrasing
like "Número é obrigatório", and translate the other three messages consistently
with existing validators such as "Rua é obrigatória" and "Email é obrigatório").
Ensure the test string matches the new Portuguese message used in
AddDocumentRequestValidator so the test asserts the translated message.

---

Nitpick comments:
In
`@src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs`:
- Around line 64-65: Replace returning internal error strings from
userResult.Error.Message and providerResult.Error.Message with fixed,
user-friendly Portuguese responses (e.g., "Ocorreu um erro ao registrar o
usuário." and "Ocorreu um erro ao registrar o provedor.") and return those with
Results.BadRequest; simultaneously log the full error details server-side (e.g.,
log userResult.Error and providerResult.Error with the existing ILogger or
logger instance) so the internal message/stack is not exposed to clients; apply
the same change for the checks around variables userResult and providerResult
(the failure branches currently returning their .Error.Message).

In `@src/Modules/Providers/API/Endpoints/ProvidersModuleEndpoints.cs`:
- Around line 30-46: Update the XML <remarks> block in
ProvidersModuleEndpoints.cs to include the three new endpoints that are missing
from the list: add entries for "POST /register", "POST /me/documents", and "GET
/me/status" alongside the existing entries so the documentation enumerates every
route exposed by the Providers module; modify the <remarks> text near the
existing endpoints list to insert those three lines in the same style/format as
the other bullet points.

In `@src/Modules/Providers/API/Endpoints/Public/RegisterProviderEndpoint.cs`:
- Around line 16-24: The endpoint always returns 200 OK via
RegisterProviderAsync/Handle(result) even when a new provider was created;
update the endpoint logic in Map/RegisterProviderAsync to detect when the
handler indicates creation (use the result from
RegisterProviderCommandHandler/RegisterProviderAsync) and return a 201 Created
response (e.g., Results.Created with the resource location or route name
"RegisterProvider" and the created ProviderDto) while still returning 200 OK for
idempotent/duplicate registrations; ensure Response<ProviderDto> produced
metadata matches both cases.

In
`@src/Modules/Providers/Application/Handlers/RegisterProviderCommandHandler.cs`:
- Around line 26-32: The handler's idempotent path in
Handle(RegisterProviderCommand, CancellationToken) returns
ProviderMapper.ToDto(existingProvider) when
_providerRepository.GetByUserIdAsync(...) finds an existing provider but emits
no log or status; update this branch to log an info-level message (e.g., using
the existing logger instance) indicating a duplicate registration attempt for
the given command.UserId and, optionally, return a distinct result status or
wrapper value (e.g., Result indicating AlreadyExists) so callers/monitoring can
distinguish new vs duplicate registrations instead of silently returning the
DTO.

In `@src/Modules/Providers/Domain/Entities/Provider.cs`:
- Around line 505-533: PromoteTier currently allows tier changes for any
non-deleted provider; add a guard that prevents tier updates for undesirable
statuses (e.g., PendingBasicInfo, Rejected, Suspended) or explicitly document
why it's allowed. Locate the PromoteTier method and either (a) add a check
against the provider Status property (e.g., if Status == ProviderStatus.Rejected
|| Status == ProviderStatus.Suspended || Status ==
ProviderStatus.PendingBasicInfo) and throw a ProviderDomainException, or (b) add
a clear XML doc comment clarifying that tier changes are allowed regardless of
Status; ensure the ProviderTierUpdatedDomainEvent and MarkAsUpdated() behavior
remains consistent after the change.

In `@src/Shared/Authorization/Core/EPermission.cs`:
- Around line 126-144: Add a short clarifying comment near the UsersRegister and
ProvidersRegister enum members to state these permissions correspond to public
AllowAnonymous endpoints and are included only for
documentation/auditing/consistency (they are not enforced at runtime); update
the comments adjacent to the UsersRegister and ProvidersRegister members in the
EPermission enum so reviewers and future maintainers understand these entries
are intentionally present despite not being checked at authorization time.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx`:
- Around line 16-22: The documentNumber validation in the formSchema allows
non-digit characters because it only enforces length; update the documentNumber
validator inside formSchema (in src/Web/.../page.tsx) to require digits-only by
adding a regex constraint (e.g., append .regex(/^\d+$/, "Apenas números são
permitidos") to the existing z.string().min(...).max(...)) so only numeric
CPF/CNPJ values pass client validation.

In `@src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx`:
- Around line 71-100: The UI is rendering raw enum values (providerStatus.tier
and providerStatus.status) and flashing the fallback link while providerStatus
is undefined; map those enums to Portuguese display labels (use or add a small
helper like ToDisplayName/ProviderConstants mapping for tier and status) and
replace direct usages of providerStatus.tier and providerStatus.status inside
DropdownMenuLabel and the status span with the mapped display strings;
additionally, suppress rendering the entire provider block while the hook is
loading by checking the hook's isLoading before showing either the
providerStatus branch or the fallback DropdownMenuItem (i.e., use isLoading to
skip rendering until fetch completes).

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-provider-status.ts`:
- Around line 8-13: The ApiResponse<T> interface defined in
use-provider-status.ts duplicates the ApiSuccessResponse<T> type in
use-register-provider.ts; extract a single shared type (e.g., ApiResponse<T>)
into a new shared file like `@/types/api.ts` and update both hooks to import and
use that shared type instead of their local declarations (refer to
ApiResponse<T>, ApiSuccessResponse<T>, use-provider-status.ts and
use-register-provider.ts to locate the duplicated definitions).

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts`:
- Around line 23-30: The fetch call in use-register-provider (inside the
useRegisterProvider hook) may send Authorization: "Bearer undefined" when
session or session.accessToken is missing; add a defensive guard before calling
fetch: verify session and session.accessToken (or token variable used) and if
missing either throw a clear Error (e.g., "Missing access token") or return a
rejected Promise so useMutation surfaces the error, and only include the
Authorization header when a valid token exists (or fail-fast instead of sending
"Bearer undefined").
- Around line 8-12: Replace the local ApiSuccessResponse<T> declaration with the
shared ApiResponse<T> type: remove the local interface in
use-register-provider.ts, add an import for ApiResponse from "@/types/api" and
update all references in this file (e.g., any usages in functions/hooks) to use
ApiResponse<T> instead of ApiSuccessResponse<T> so the hook aligns with the
existing shared contract used by use-provider-status.ts.

…ges, backend API, and a comprehensive development environment.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/Modules/Providers/Application/Validators/AddDocumentRequestValidator.cs (1)

25-27: ⚠️ Potential issue | 🟡 Minor

DocumentType validation message is still in English — inconsistent with the localization applied in this same PR.

The literal portion of the message on line 27 must be Portuguese per the codebase language policy (user-facing validation messages → Portuguese). Based on learnings, the project enforces Portuguese for all user-facing API/validation messages.

🌐 Proposed fix
-            .WithMessage($"DocumentType must be a valid document type. {EnumExtensions.GetValidValuesDescription<EDocumentType>()}");
+            .WithMessage($"DocumentType deve ser um tipo de documento válido. {EnumExtensions.GetValidValuesDescription<EDocumentType>()}");

Based on learnings: "User-facing validation messages and API responses shown to the frontend must be in Portuguese."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Providers/Application/Validators/AddDocumentRequestValidator.cs`
around lines 25 - 27, The validation message for DocumentType is currently
English; update the literal text in the RuleFor(...) chain that uses
.WithMessage(...) so the user-facing portion is in Portuguese while preserving
the dynamic enum description from
EnumExtensions.GetValidValuesDescription<EDocumentType>(); specifically modify
the .WithMessage call that follows RuleFor(x =>
x.DocumentType).Must(BeValidDocumentType) to a Portuguese string (e.g.,
"DocumentType deve ser um tipo de documento válido.") concatenated with
EnumExtensions.GetValidValuesDescription<EDocumentType>() so only the static
text is translated and the enum helper remains intact.
src/Modules/Providers/Tests/Unit/Application/Validators/AddDocumentRequestValidatorTests.cs (1)

127-128: ⚠️ Potential issue | 🟡 Minor

Translate the DocumentType validation message to Portuguese in the validator.

The validation message in AddDocumentRequestValidator.cs (line 27) is in English while all other validation messages in this validator are in Portuguese. Per the codebase language policy, user-facing validation messages must be in Portuguese.

Update AddDocumentRequestValidator.cs line 27 to translate the message to Portuguese, for example:

.WithMessage($"DocumentType deve ser um tipo de documento válido. Valores válidos de EDocumentType: {EnumExtensions.GetValidValuesDescription<EDocumentType>()}");

Once the validator is updated, update the test assertion at line 128 to match the Portuguese message.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Providers/Tests/Unit/Application/Validators/AddDocumentRequestValidatorTests.cs`
around lines 127 - 128, Translate the DocumentType validation message in
AddDocumentRequestValidator (update the message at the rule that currently uses
the English text) to Portuguese, e.g. use .WithMessage($"DocumentType deve ser
um tipo de documento válido. Valores válidos de EDocumentType:
{EnumExtensions.GetValidValuesDescription<EDocumentType>()}"); then update the
unit test assertion in AddDocumentRequestValidatorTests (the
.ShouldHaveValidationErrorFor(x => x.DocumentType) expectation) to assert the
new Portuguese message instead of the English text so the test matches the
validator change.
🧹 Nitpick comments (5)
infrastructure/compose/environments/development.yml (1)

59-64: OAuth env vars silently pass empty strings when unset.

${VAR} and ${VAR:-} are equivalent in Docker Compose — both resolve to empty string if the variable is not defined. Since the realm JSON reads these values directly to configure identity provider clientId/clientSecret, an unset variable produces a silently misconfigured broker at startup (login via that provider will fail with no Keycloak startup warning).

For a dev environment where OAuth is genuinely optional, document them clearly in .env.example as optional (which the AI summary says is already being done in that file). If they are required for the feature under test, prefer :? to fail fast.

♻️ Explicit optional pattern
-      GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
-      GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
-      FACEBOOK_APP_ID: ${FACEBOOK_APP_ID}
-      FACEBOOK_APP_SECRET: ${FACEBOOK_APP_SECRET}
-      INSTAGRAM_CLIENT_ID: ${INSTAGRAM_CLIENT_ID}
-      INSTAGRAM_CLIENT_SECRET: ${INSTAGRAM_CLIENT_SECRET}
+      GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
+      GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
+      FACEBOOK_APP_ID: ${FACEBOOK_APP_ID:-}
+      FACEBOOK_APP_SECRET: ${FACEBOOK_APP_SECRET:-}
+      INSTAGRAM_CLIENT_ID: ${INSTAGRAM_CLIENT_ID:-}
+      INSTAGRAM_CLIENT_SECRET: ${INSTAGRAM_CLIENT_SECRET:-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/compose/environments/development.yml` around lines 59 - 64,
The environment variables GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
FACEBOOK_APP_ID, FACEBOOK_APP_SECRET, INSTAGRAM_CLIENT_ID and
INSTAGRAM_CLIENT_SECRET currently silently resolve to empty strings when unset;
update the development compose env entries to either (a) use the fail-fast
expansion pattern (VAR:?error) for any variables required for tests so the
container fails on missing values, or (b) explicitly mark them as optional in
`.env.example` and keep plain expansion if OAuth is optional for dev. Locate the
variables in the compose file (the keys listed above) and apply the chosen
pattern consistently so missing credentials either error immediately or are
clearly documented as optional.
src/Modules/Providers/API/Endpoints/Public/Me/GetMyProviderStatusEndpoint.cs (1)

46-47: result.Error.Message may expose non-localized internal error text.

If the query handler returns an English or technical error message, it will be forwarded verbatim to the client. Consider returning a generic Portuguese message instead, consistent with line 50's pattern.

Proposed fix
         if (result.IsFailure)
-            return BadRequest(result.Error.Message);
+            return BadRequest("Não foi possível consultar o status do prestador. Tente novamente.");

Based on learnings, user-facing validation messages and API responses shown to the frontend must be in Portuguese.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Providers/API/Endpoints/Public/Me/GetMyProviderStatusEndpoint.cs`
around lines 46 - 47, The BadRequest currently returns raw result.Error.Message
which may expose internal/non-localized text; update the failure branch in
GetMyProviderStatusEndpoint (the check on result.IsFailure and the BadRequest
return) to return a generic Portuguese message instead (matching the pattern
used around line 50) rather than forwarding result.Error.Message — keep
result.Error for logging if needed but send a fixed Portuguese error string to
the client.
src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx (1)

14-18: Consolidate duplicate lucide-react imports.

Briefcase can be added to the existing line 14 import.

♻️ Proposed fix
-import { User, LogOut } from "lucide-react";
+import { User, LogOut, Briefcase } from "lucide-react";
 import Link from "next/link";
 import { useProviderStatus } from "@/hooks/use-provider-status";
 import { EProviderStatus, PROVIDER_STATUS_LABELS, PROVIDER_TIER_LABELS } from "@/types/provider";
-import { Briefcase } from "lucide-react";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx` around lines
14 - 18, Consolidate duplicate lucide-react imports in user-menu.tsx by removing
the second import of Briefcase and adding Briefcase to the first import that
currently imports User and LogOut; update the import line that references User
and LogOut to include Briefcase so there is only a single lucide-react import
statement.
src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx (1)

96-117: EProviderType.Cooperative and EProviderType.Freelancer are not selectable in the UI.

The form schema uses z.nativeEnum(EProviderType) which allows all 4 types, but the selector only renders buttons for Individual and Company. If adding Cooperative/Freelancer support is on the roadmap, consider tracking it as a TODO.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx` around lines
96 - 117, The provider-type selector only renders EProviderType.Individual and
EProviderType.Company, leaving EProviderType.Cooperative and
EProviderType.Freelancer unreachable; update the UI in the render that uses
field (inside FormControl/FormItem) to include two additional Button controls
for EProviderType.Cooperative and EProviderType.Freelancer with the same pattern
(variant={field.value === EProviderType.Cooperative ? "primary" : "outline"} and
onClick={() => field.onChange(EProviderType.Cooperative)} and likewise for
Freelancer), or if those types are intentionally not supported yet add a clear
TODO comment near the EProviderType usage indicating planned support and ensure
the form schema/validation remains consistent with the UI.
src/Web/MeAjudaAi.Web.Customer/types/api.ts (1)

1-6: Update data field to T | null to match backend response semantics.

The backend Response<T> defines Data as T? (nullable), but the frontend ApiResponse<T> declares data: T (non-nullable). This type mismatch means the TypeScript interface doesn't reflect that the backend can return null data (e.g., new Response<object>(null, 400, message)). Update to data: T | null to align the frontend type contract with the actual backend behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/types/api.ts` around lines 1 - 6, Update the
ApiResponse<T> interface so its data property allows null values to match the
backend Response<T> semantics: change the declaration of data on the
ApiResponse<T> interface to be nullable (T | null) and update any usages that
assume non-null data to handle the null case; locate the ApiResponse<T>
interface definition in src/Web/MeAjudaAi.Web.Customer/types/api.ts and adjust
the data type accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/roadmap.md`:
- Line 10: The document has inconsistent MVP dates: the header shows "MVP
Target: 28/Março/2026" while three body occurrences still read "14 de Março de
2026"; pick the canonical date (either keep 28/Março/2026 or revert header to 14
de Março de 2026) and update all instances to match—specifically search for and
change the phrases "Data de MVP atualizada para 14 de Março de 2026...", "**MVP
Launch Target**: 14 de Março de 2026 🎯", and "PRONTO PARA LAUNCH EM 14 DE MARÇO
DE 2026" to the chosen canonical date so the header and body are consistent.
- Around line 78-94: The document now contains a direct contradiction: the
"Migração Admin Portal: Blazor WASM → React?" block (heading text) declares
"Manter Blazor" while Sprint 8D ("Admin Portal Migration: Blazor WASM → React")
remains listed as planned in multiple places (phase-2 summary and the "### ⏳
Sprint 8D - Admin Portal Migration (Novo)" section and Sumário Executivo).
Resolve by removing or deprecating Sprint 8D references: either delete the
Sprint 8D entries from the phase-2 summary, sprint detail, and Sumário
Executivo, or add a clear deprecation note in each Sprint 8D occurrence
referencing the new decision (e.g., mark "Sprint 8D" as Deprecated/Cancelled and
point to "Migração Admin Portal: Blazor WASM → React?" decision), and update any
status icons/text to match "Manter Blazor". Ensure all occurrences of "Sprint
8D" and the exact heading "Migração Admin Portal: Blazor WASM → React?" are
updated so the document is consistent.
- Line 5049: The markdown list entry on the line containing "Documentação:
<https://viacep.com.br/>- **BrasilApi CEP**" is missing a newline between two
items; split that fused line into two list items by inserting a newline (or add
a line break) so the ViaCEP link and the "**BrasilApi CEP** - Lookup de CEP
(fallback)" entry are on separate lines, restoring a valid bullet list entry.

In `@infrastructure/compose/environments/development.yml`:
- Line 65: The docker-compose command currently forces Keycloak to start with
"--features=instagram-broker" (see command: ["start-dev", "--import-realm",
"--features=instagram-broker"]) which is deprecated; update this by either
removing the "--features=instagram-broker" flag and documenting the dependency
on the pinned image "26.0.2" (so future Keycloak bumps are tracked) or add a
guarded approach: keep the pinned image for development but add a clear comment
in the command/compose file about deprecation and a migration note to replace
Instagram broker usage in the imported realm (--import-realm) with a generic
OIDC/OAuth2 client configuration for Instagram if Keycloak removes the broker;
ensure the compose change references "start-dev" and the import realm behavior
so reviewers know to update realm JSON and CI when upgrading Keycloak.
- Line 68: The docker-compose mapping "8443:8443" exposes HTTPS but start-dev
does not enable TLS given KC_HTTP_ENABLED: true and no KC_HTTPS_CERTIFICATE_*
variables; either remove the "8443:8443" port mapping from the Keycloak service
to avoid a misleading binding, or add the required TLS environment variables
(the KC_HTTPS_CERTIFICATE_* family — e.g., cert/key/chain vars used by Keycloak)
and ensure KC_HTTP_ENABLED is set appropriately so HTTPS actually starts; update
the service env block accordingly and remove the port mapping if you choose not
to configure TLS.

In `@infrastructure/compose/standalone/keycloak-only.yml`:
- Around line 24-25: There is a duplicate environment variable entry
KC_HOSTNAME_STRICT_HTTPS in the compose YAML; remove the redundant declaration
so KC_HOSTNAME_STRICT_HTTPS: false appears only once in the Keycloak service
environment block, ensuring no duplicated keys remain (search for
KC_HOSTNAME_STRICT_HTTPS to locate both occurrences).

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs`:
- Line 56: The Password field in ProviderRegistrationEndpoints (currently set to
the hardcoded string "TempPassword123!") is a security risk; replace it with a
per-registration generated compliant temporary password (or mark the account
credential-less for social/login-only flows). Implement a helper like
GenerateTemporaryPassword() and call it when constructing the Keycloak user
payload instead of the static literal; ensure the generated value satisfies
Keycloak password policy (length, upper/lower/digit/special) and set the account
to require password change or to be credential-less depending on the
social-login flow. Update any relevant code in ProviderRegistrationEndpoints
where Password is assigned and add tests or validation to confirm generated
passwords meet policy.
- Around line 99-113: The compensation catch is silently swallowing failures
when attempting to delete the created Keycloak user; update the endpoint to
accept or resolve an ILogger (inject or add as a parameter) and in the catch
block log the exception with context (include userResult.Value!.Id and that
DeleteUserCommand via commandDispatcher.SendAsync failed) so compensation
failures are recorded; keep the existing try/catch/return flow but replace the
empty catch with a logged error using the ILogger instance.

In `@src/Modules/Providers/API/Endpoints/Public/RegisterProviderEndpoint.cs`:
- Around line 23-25: Remove the incorrect 200 OK response annotation from the
endpoint's OpenAPI metadata: in RegisterProviderEndpoint (the chained .Produces
calls that currently include
.Produces<Response<ProviderDto>>(StatusCodes.Status200OK)), delete that
.Produces(...) entry so the endpoint only declares 201 Created and 400
BadRequest (matching the handler which returns Results.Created(...) or an
error). Ensure only the
.Produces<Response<ProviderDto>>(StatusCodes.Status201Created) and
.Produces<Response<object>>(StatusCodes.Status400BadRequest) calls remain.

In
`@src/Modules/Providers/Application/Handlers/Commands/RegisterProviderCommandHandler.cs`:
- Around line 63-65: The catch in RegisterProviderCommandHandler currently
returns DomainException.Message to the API (Result<ProviderDto>.Failure(new
Error(ex.Message, 400))), which leaks internal English domain text; change this
to return a generic Portuguese error message (e.g., "Operação inválida" or
"Requisição inválida") in the Error sent to the client and move the detailed
ex.Message into a server-side log; update the catch(DomainException ex) block to
call your logging utility (or ILogger) to log ex and then return
Result<ProviderDto>.Failure(new Error("<mensagem genérica em Português>", 400))
so clients see Portuguese text while internals keep the detailed exception in
logs.
- Around line 63-65: Add the missing using for DomainException (add using
MeAjudaAi.Shared.Exceptions;) and do not expose ex.Message to the client;
replace the return in the DomainException catch (the
Result<ProviderDto>.Failure(new Error(...)) call) to use a generic Portuguese
error message consistent with the pattern used on line 70 (e.g. "Erro ao
processar a requisição" or the exact generic message used elsewhere) and keep
the 400 status code.

In
`@src/Modules/Providers/Tests/Unit/API/Endpoints/UpdateMyProviderProfileEndpointTests.cs`:
- Around line 49-50: Remove the duplicated comment line "// Setup Query to
return ProviderId" in the test file UpdateMyProviderProfileEndpointTests (keep a
single, clear comment where the query is being set up) so there's only one
explanatory comment near the query setup; locate the duplicate in the test class
UpdateMyProviderProfileEndpointTests and delete the extra comment to avoid
redundancy.

In
`@src/Modules/Providers/Tests/Unit/Application/Validators/AddDocumentRequestValidatorTests.cs`:
- Around line 46-47: The test file AddDocumentRequestValidatorTests contains a
duplicated inline comment "// Assert" in the test method(s); remove the
redundant comment so only one "// Assert" remains. Locate the duplicate in the
AddDocumentRequestValidatorTests class (search for "// Assert" occurrences) and
delete the extra line, keeping a single assertion comment above the Assert
block.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx`:
- Around line 16-25: The formSchema currently allows any 11–14 digit
documentNumber regardless of type; update formSchema to enforce exact lengths by
replacing the current min/max on documentNumber with a digits-only check (keep
the .regex(/^\d+$/,"Apenas números são permitidos")) and add a .superRefine on
formSchema that reads the type (EProviderType) and documentNumber and calls
ctx.addIssue when the length mismatches (if type === EProviderType.Individual
require length === 11, if type === EProviderType.Company require length === 14),
targeting the "documentNumber" path with appropriate messages like "CPF deve ter
11 dígitos" / "CNPJ deve ter 14 dígitos".

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/perfil/page.tsx`:
- Around line 84-94: The step-2 rendering currently treats any
non-PendingBasicInfo status as "Pendente", causing Active and
PendingDocumentVerification providers to appear dimmed; update the logic around
providerStatus.status (used in the container className, the badge styling, and
the conditional that renders the <Button> vs <p>) to explicitly handle at least
three states: EProviderStatus.PendingBasicInfo (step 2 locked/disabled "Em
breve"), the state where address is the current pending step (if you have an
EProviderStatus.PendingAddress value handle it here to make step 2
active/editable), and EProviderStatus.PendingDocumentVerification or
EProviderStatus.Active (treat as completed/accessible for step 2 and render the
enabled UI instead of the "Pendente" paragraph). Modify the ternary checks
referencing providerStatus.status and EProviderStatus to branch on these
explicit enums so Active providers no longer see step 2 as dimmed.
- Around line 1-4: This page is missing the "use client" directive causing
client-side hooks like useProviderStatus (which internally uses useQuery and
useSession) to run in a Server Component; add the top-of-file directive "use
client" as the first line in this file
(src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/perfil/page.tsx) so the
component that imports and calls useProviderStatus, and uses Button, Link,
EProviderStatus, PROVIDER_STATUS_LABELS and PROVIDER_TIER_LABELS, is treated as
a Client Component and the hooks run correctly.

In `@src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx`:
- Around line 69-104: The first DropdownMenuSeparator is rendered
unconditionally and causes two adjacent separators when isLoadingProvider is
true and no provider content is shown; update the JSX around the first
DropdownMenuSeparator to render it only when providerStatus is truthy (or when
not isLoadingProvider and providerStatus exists) so separators flank actual
content, referencing the isLoadingProvider and providerStatus checks and the
DropdownMenuSeparator/DropdownMenuLabel/DropdownMenuItem blocks to locate the
spot to gate.

In `@src/Web/MeAjudaAi.Web.Customer/types/provider.ts`:
- Around line 1-6: The EProviderType enum is missing the backend's undefined
value; update the EProviderType enum by adding a new member None = "None"
alongside Individual, Company, Cooperative, and Freelancer so the frontend
matches the backend's provider type definitions and can represent the
default/undefined state; ensure any code that constructs or compares
EProviderType (e.g., type checks, switch statements) can handle
EProviderType.None appropriately.

---

Outside diff comments:
In `@src/Modules/Providers/Application/Validators/AddDocumentRequestValidator.cs`:
- Around line 25-27: The validation message for DocumentType is currently
English; update the literal text in the RuleFor(...) chain that uses
.WithMessage(...) so the user-facing portion is in Portuguese while preserving
the dynamic enum description from
EnumExtensions.GetValidValuesDescription<EDocumentType>(); specifically modify
the .WithMessage call that follows RuleFor(x =>
x.DocumentType).Must(BeValidDocumentType) to a Portuguese string (e.g.,
"DocumentType deve ser um tipo de documento válido.") concatenated with
EnumExtensions.GetValidValuesDescription<EDocumentType>() so only the static
text is translated and the enum helper remains intact.

In
`@src/Modules/Providers/Tests/Unit/Application/Validators/AddDocumentRequestValidatorTests.cs`:
- Around line 127-128: Translate the DocumentType validation message in
AddDocumentRequestValidator (update the message at the rule that currently uses
the English text) to Portuguese, e.g. use .WithMessage($"DocumentType deve ser
um tipo de documento válido. Valores válidos de EDocumentType:
{EnumExtensions.GetValidValuesDescription<EDocumentType>()}"); then update the
unit test assertion in AddDocumentRequestValidatorTests (the
.ShouldHaveValidationErrorFor(x => x.DocumentType) expectation) to assert the
new Portuguese message instead of the English text so the test matches the
validator change.

---

Duplicate comments:
In `@docs/roadmap.md`:
- Around line 41-43: Sprint 8B still shows the wrong name and a status date
mismatch: find the Sprint 8B entry in docs/roadmap.md (the table row that
currently reads "Mobile App (React Native + Expo)" and the heading
"Authentication & Onboarding Flow") and change the table row to match the
heading by renaming it to "Authentication & Onboarding Flow"; also align the
status "(EM ANDAMENTO)" with the sprint start date by either shifting the sprint
start to 18 Fev or removing/preponing the status so it does not appear before
the sprint's listed start (ensure the start date and the status date are
consistent).

In `@infrastructure/compose/standalone/keycloak-only.yml`:
- Line 36: Duplicate host port mapping "8443:8443" in keycloak-only.yml can
conflict with development.yml; remove or change the host-side port binding for
the Keycloak service by editing the port mapping string "8443:8443" in the
keycloak service section—either remove the host port (leave only container
port), change to a unique host port (e.g., "8444:8443"), or document that it
must not be run alongside development.yml; ensure the change is applied to the
ports entry for the Keycloak service in standalone/keycloak-only.yml.
- Around line 27-32: The OAuth env-vars (GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
FACEBOOK_APP_ID, FACEBOOK_APP_SECRET, INSTAGRAM_CLIENT_ID,
INSTAGRAM_CLIENT_SECRET) in the compose file are silently set to empty when
unset; update the compose service environment entries to enforce presence (e.g.,
use the shell-style mandatory expansion or add runtime validation) so the
container fails fast instead of starting misconfigured identity
brokers—specifically replace each ${VAR} with a mandatory check (e.g., ${VAR:?}
or equivalent) or add a startup validation script that checks these variables
and logs/exit if any of GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, FACEBOOK_APP_ID,
FACEBOOK_APP_SECRET, INSTAGRAM_CLIENT_ID, INSTAGRAM_CLIENT_SECRET are missing.
- Line 33: The compose command includes the deprecated flag
"--features=instagram-broker" (in the command array
["start-dev","--import-realm","--features=instagram-broker"]); remove that
deprecated feature flag or replace it with the correct, supported feature name
used elsewhere (mirror the fix applied in development.yml) so the command
becomes start-dev with only valid flags (e.g., ["start-dev","--import-realm"] or
the updated feature token); ensure you update the same command occurrence in
keycloak-only.yml.

---

Nitpick comments:
In `@infrastructure/compose/environments/development.yml`:
- Around line 59-64: The environment variables GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET, FACEBOOK_APP_ID, FACEBOOK_APP_SECRET, INSTAGRAM_CLIENT_ID
and INSTAGRAM_CLIENT_SECRET currently silently resolve to empty strings when
unset; update the development compose env entries to either (a) use the
fail-fast expansion pattern (VAR:?error) for any variables required for tests so
the container fails on missing values, or (b) explicitly mark them as optional
in `.env.example` and keep plain expansion if OAuth is optional for dev. Locate
the variables in the compose file (the keys listed above) and apply the chosen
pattern consistently so missing credentials either error immediately or are
clearly documented as optional.

In
`@src/Modules/Providers/API/Endpoints/Public/Me/GetMyProviderStatusEndpoint.cs`:
- Around line 46-47: The BadRequest currently returns raw result.Error.Message
which may expose internal/non-localized text; update the failure branch in
GetMyProviderStatusEndpoint (the check on result.IsFailure and the BadRequest
return) to return a generic Portuguese message instead (matching the pattern
used around line 50) rather than forwarding result.Error.Message — keep
result.Error for logging if needed but send a fixed Portuguese error string to
the client.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx`:
- Around line 96-117: The provider-type selector only renders
EProviderType.Individual and EProviderType.Company, leaving
EProviderType.Cooperative and EProviderType.Freelancer unreachable; update the
UI in the render that uses field (inside FormControl/FormItem) to include two
additional Button controls for EProviderType.Cooperative and
EProviderType.Freelancer with the same pattern (variant={field.value ===
EProviderType.Cooperative ? "primary" : "outline"} and onClick={() =>
field.onChange(EProviderType.Cooperative)} and likewise for Freelancer), or if
those types are intentionally not supported yet add a clear TODO comment near
the EProviderType usage indicating planned support and ensure the form
schema/validation remains consistent with the UI.

In `@src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx`:
- Around line 14-18: Consolidate duplicate lucide-react imports in user-menu.tsx
by removing the second import of Briefcase and adding Briefcase to the first
import that currently imports User and LogOut; update the import line that
references User and LogOut to include Briefcase so there is only a single
lucide-react import statement.

In `@src/Web/MeAjudaAi.Web.Customer/types/api.ts`:
- Around line 1-6: Update the ApiResponse<T> interface so its data property
allows null values to match the backend Response<T> semantics: change the
declaration of data on the ApiResponse<T> interface to be nullable (T | null)
and update any usages that assume non-null data to handle the null case; locate
the ApiResponse<T> interface definition in
src/Web/MeAjudaAi.Web.Customer/types/api.ts and adjust the data type
accordingly.

Filipe Frigini added 2 commits February 19, 2026 10:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
infrastructure/compose/standalone/keycloak-only.yml (1)

1-14: ⚠️ Potential issue | 🟡 Minor

Stale header comments no longer reflect the file's actual behaviour.

Three parts of the header are now misleading:

  • Line 2 — "Minimal setup with embedded H2 database" contradicts the new KC_DB: postgres config (lines 21-26).
  • Lines 4-5, 9-10 — Instruct callers to export KEYCLOAK_ADMIN_PASSWORD / KEYCLOAK_ADMIN before running, but both are now hardcoded in the file body (lines 32-33), making these instructions incorrect.
  • Lines 6, 11 — Reference KEYCLOAK_PORT as an optional variable, but the port is now fixed at 8080 (line 43).
📝 Proposed header update
-# Standalone Keycloak for development
-# Minimal setup with embedded H2 database for quick testing
-# 
-# REQUIRED: Set KEYCLOAK_ADMIN_PASSWORD before running
-# OPTIONAL: Set KEYCLOAK_ADMIN (defaults to 'admin', consider using a custom username)
-# OPTIONAL: Set KEYCLOAK_PORT (defaults to 8081 to avoid conflicts with development.yml)
-#
-# Usage:
-#   export KEYCLOAK_ADMIN_PASSWORD=$(openssl rand -base64 32)
-#   export KEYCLOAK_ADMIN="meajudaai_admin"  # Recommended: avoid 'admin'
-#   export KEYCLOAK_PORT=8081                # Avoid port conflicts
-#   docker compose -f standalone/keycloak-only.yml up -d
-#
-# Or use .env file with secure credentials
+# Standalone Keycloak for development
+# Uses the shared PostgreSQL instance (host.docker.internal:5432) — Aspire or another
+# compose stack must already be running.
+#
+# REQUIRED: Provide social OAuth credentials in a .env file or as environment variables:
+#   GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
+#   FACEBOOK_APP_ID, FACEBOOK_APP_SECRET
+#   INSTAGRAM_CLIENT_ID, INSTAGRAM_CLIENT_SECRET
+#   DB_PASSWORD (defaults to 'postgres')
+#
+# Usage:
+#   docker compose -f standalone/keycloak-only.yml up -d
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/compose/standalone/keycloak-only.yml` around lines 1 - 14, The
header comments are stale and contradict the compose file: update the
top-of-file documentation to state that Keycloak is configured to use Postgres
(KC_DB: postgres), that KEYCLOAK_ADMIN and KEYCLOAK_ADMIN_PASSWORD are currently
hardcoded in the compose file (remove instructions to export them or note they
can be overridden via environment or an .env file), and that the service port is
fixed to 8080 (not an optional KEYCLOAK_PORT); edit the header to accurately
describe KC_DB, KC_* credentials handling, and the fixed port behavior so
readers aren’t misled by the previous H2/variable instructions.
🧹 Nitpick comments (10)
src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx (1)

5-5: AvatarProps still extends ComponentProps<"img"> but all inherited props are silently discarded.

The interface advertises the full set of native <img> props (e.g. onError, loading, style, data-*), but the component only destructures five named props and the rest are dropped. This creates a misleading contract: callers can pass any img attribute without a type error, yet it has no effect at runtime. Since the ...rest spread was removed in this PR, consider narrowing the interface to exactly what is consumed:

♻️ Suggested narrowing
-export interface AvatarProps extends Omit<ComponentProps<"img">, "src"> {
+export interface AvatarProps {
     src?: string | null;
     alt: string;
     size?: "sm" | "md" | "lg" | "xl";
     fallback?: string;
+    className?: string;
 }

Also applies to: 26-32

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx` at line 5,
AvatarProps currently extends ComponentProps<"img"> but the component only
destructures five specific props (the named props in the Avatar component) while
the rest are dropped, creating a misleading contract; either narrow AvatarProps
to list only the exact props the component consumes or restore and forward the
remaining native img attributes by reintroducing the ...rest spread and applying
it to the rendered element (update the Avatar component signature and JSX
accordingly). Locate AvatarProps and the Avatar functional component to either
replace the extends ComponentProps<"img"> with an explicit prop list (e.g., url,
alt, size, fallback, className — whatever five props are used) or add back
...rest in the parameter destructure and spread {...rest} on the rendered
img/container so native img props like onError, loading, style, data-* are
preserved.
infrastructure/compose/standalone/keycloak-only.yml (1)

43-44: Hardcoded port 8080 removes configurability and risks conflicts.

Port 8080 is a common default for many local services (e.g., Spring Boot, Tomcat, other Docker containers). The previous "${KEYCLOAK_PORT:-8081}:8080" defaulted to 8081 specifically to avoid this collision. Restoring the variable keeps the standalone compose usable alongside other stacks without editing the file:

♻️ Proposed change
-      - "8080:8080"
+      - "${KEYCLOAK_PORT:-8080}:8080"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/compose/standalone/keycloak-only.yml` around lines 43 - 44,
Replace the hardcoded port mapping "- \"8080:8080\"" with an environment-driven
mapping that uses the KEYCLOAK_PORT variable (e.g., "-
\"${KEYCLOAK_PORT:-8081}:8080\"") so the compose file keeps the previous default
of 8081 to avoid port conflicts while allowing overrides via KEYCLOAK_PORT;
update any comments if needed to reflect the change.
src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx (2)

16-45: CPF/CNPJ length enforcement added — but Cooperative and Freelancer types have no specific validation.

The superRefine correctly enforces 11 digits for Individual (CPF) and 14 for Company (CNPJ). However, Cooperative (typically CNPJ) and Freelancer (typically CPF) fall through to just the base min(11).max(14) check, accepting any length in that range. Consider extending the refinement:

Suggested extension
     } else if (data.type === EProviderType.Company) {
         // CNPJ must be 14 digits
         if (data.documentNumber.length !== 14) {
             ctx.addIssue({
                 code: z.ZodIssueCode.custom,
                 message: "CNPJ deve ter 14 dígitos",
                 path: ["documentNumber"],
             });
         }
+    } else if (data.type === EProviderType.Cooperative) {
+        if (data.documentNumber.length !== 14) {
+            ctx.addIssue({
+                code: z.ZodIssueCode.custom,
+                message: "CNPJ deve ter 14 dígitos",
+                path: ["documentNumber"],
+            });
+        }
+    } else if (data.type === EProviderType.Freelancer) {
+        if (data.documentNumber.length !== 11) {
+            ctx.addIssue({
+                code: z.ZodIssueCode.custom,
+                message: "CPF deve ter 11 dígitos",
+                path: ["documentNumber"],
+            });
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx` around lines
16 - 45, The superRefine in formSchema only checks EProviderType.Individual and
EProviderType.Company, leaving EProviderType.Cooperative and
EProviderType.Freelancer validated only by the generic min/max; update the
superRefine in page.tsx to explicitly handle EProviderType.Cooperative (require
documentNumber.length === 14, CNPJ) and EProviderType.Freelancer (require
documentNumber.length === 11, CPF), adding ctx.addIssue calls identical to the
existing messages for Company/Individual so those types get the correct length
validation.

63-73: Email is pre-filled from session but never rendered — user cannot review or correct it.

The email field is in the schema and default values, and gets populated via useEffect, but there's no <FormField> for it in the JSX. The email will be silently submitted without the user seeing it. If this is intentional (email sourced exclusively from the IdP), a comment clarifying that would help. If users should be able to edit it, add a visible form field.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx` around lines
63 - 73, The email value is populated in the useEffect using session.user.email
via form.setValue but there is no visible FormField for "email", so users cannot
see or edit it; either add a visible FormField bound to the same form key
"email" in the JSX (matching the schema/defaults) so the populated value is
rendered and editable, or if the email must be immutable, add a clear inline
comment near the useEffect and/or render a read-only FormField for "email" to
make intent explicit (refer to useEffect, form.setValue("email"),
form.getValues("email"), and the form schema).
src/Web/MeAjudaAi.Web.Customer/types/provider.ts (2)

32-53: Consider adding VERIFICATION_STATUS_LABELS for consistency.

You have label maps for type, status, and tier, but EVerificationStatus has no corresponding VERIFICATION_STATUS_LABELS. If verification status is (or will be) shown to users, the same pattern should be applied to avoid rendering raw enum strings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/types/provider.ts` around lines 32 - 53, Add a
new mapping constant VERIFICATION_STATUS_LABELS: Record<EVerificationStatus,
string> to mirror the existing PROVIDER_*_LABELS so verification enums render
user-friendly text; locate the enums usage around
PROVIDER_STATUS_LABELS/PROVIDER_TYPE_LABELS and define
VERIFICATION_STATUS_LABELS with keys like [EVerificationStatus.Unverified],
[EVerificationStatus.Pending], [EVerificationStatus.Verified], etc., mapping
each EVerificationStatus value to the appropriate Portuguese label and export it
for consumption where verification status is displayed.

70-79: ProviderDto is missing several backend fields — acceptable for now but worth tracking.

The backend ProviderMapper.ToDto maps ~17 fields (including BusinessProfile, Documents, Qualifications, Services, CreatedAt, SuspensionReason, RejectionReason, etc.), while the frontend DTO only declares 7. The "outros campos omitidos" comment acknowledges this. Just ensure this gets expanded before any UI needs those fields, to avoid silent undefined access.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/types/provider.ts` around lines 70 - 79,
ProviderDto currently only lists 7 fields while ProviderMapper.ToDto returns
~17; update ProviderDto to include the missing backend fields (e.g.,
BusinessProfile, Documents, Qualifications, Services, CreatedAt,
SuspensionReason, RejectionReason and any other properties emitted by
ProviderMapper.ToDto), mark fields optional if they may be absent, and import or
define the correct types for each field so the frontend type matches the backend
mapper; also add a short TODO/TICKET comment referencing ProviderMapper.ToDto so
future changes remain synchronized.
src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/perfil/page.tsx (1)

114-120: Step 3 is permanently locked regardless of provider status.

For Active or PendingDocumentVerification providers, step 3 ("Serviços e Docs") is still rendered as "Bloqueado" with 50% opacity. If this is intentional for this sprint, consider adding a brief comment noting the planned behavior; otherwise, this step should react to status like step 2 does.

src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx (2)

83-87: Suspended and Rejected statuses share the same amber colour as pending states.

All non-Active statuses map to text-amber-600, but terminal-failure states (Suspended, Rejected) conventionally warrant a destructive red to communicate they require action. This is purely a UX consideration.

✨ Proposed improvement — differentiate negative terminal states
-                                <span className={providerStatus.status === EProviderStatus.Active ? "text-green-600 font-medium" : "text-amber-600"}>
+                                <span className={
+                                    providerStatus.status === EProviderStatus.Active
+                                        ? "text-green-600 font-medium"
+                                        : (providerStatus.status === EProviderStatus.Suspended || providerStatus.status === EProviderStatus.Rejected)
+                                            ? "text-red-600 font-medium"
+                                            : "text-amber-600"
+                                }>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx` around lines
83 - 87, The status color currently uses a single conditional on
providerStatus.status === EProviderStatus.Active and falls back to
"text-amber-600" for all non-active values; change the logic so terminal-failure
states (EProviderStatus.Suspended and EProviderStatus.Rejected) render with a
destructive red class (e.g., "text-red-600 font-medium") while other non-active
states keep the amber class; update the JSX around providerStatus.status (or
extract a small helper like getStatusClass(status)) so the span's className
picks "text-green-600 font-medium" for Active, "text-red-600 font-medium" for
Suspended/Rejected, and "text-amber-600" for the remaining statuses, leaving
PROVIDER_STATUS_LABELS unchanged.

24-25: Duplicate comment — remove one instance.

Lines 24 and 25 carry the identical comment // Loading state - prevent flash of unauthenticated UI.

✨ Proposed fix
     // Fail-safe: Show buttons by default (avoids infinite loading if JS fails)
     // If authenticated, we show the avatar.
-    // Loading state - prevent flash of unauthenticated UI
     // Loading state - prevent flash of unauthenticated UI
     if (status === "loading") {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx` around lines
24 - 25, In the UserMenu React component in user-menu.tsx there is a duplicated
inline comment "// Loading state - prevent flash of unauthenticated UI" on two
consecutive lines; remove the redundant line so only one instance of that
comment remains (keep a single comment immediately before the loading-state
logic or return statement where it currently sits).
src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs (1)

45-45: Use typeof instead of a hardcoded string for the logger category.

If the class is renamed or moved, the string silently drifts out of sync.

♻️ Proposed fix
-        var logger = loggerFactory.CreateLogger("MeAjudaAi.ApiService.Endpoints.ProviderRegistrationEndpoints");
+        var logger = loggerFactory.CreateLogger(typeof(ProviderRegistrationEndpoints).FullName!);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs`
at line 45, Replace the hardcoded category string passed to
loggerFactory.CreateLogger with a type-based category to avoid drift when the
class is renamed; specifically, use the ProviderRegistrationEndpoints type
(e.g., pass typeof(ProviderRegistrationEndpoints).FullName or use a generic
ILogger<T>/CreateLogger<T> variant) in the call to loggerFactory.CreateLogger so
the logger category is tied to the ProviderRegistrationEndpoints type instead of
a literal string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/roadmap.md`:
- Around line 2173-2174: The note "Atualizado para 14 de Março de 2026."
directly contradicts the updated heading "**MVP Launch Target**: 28 de Março de
2026 🎯"; update or remove the stale note so both lines agree — either change
the "Atualizado para 14 de Março de 2026." text to "Atualizado para 28 de Março
de 2026." or delete the note entirely; make the edit where the strings "**MVP
Launch Target**: 28 de Março de 2026 🎯" and "Atualizado para 14 de Março de
2026." appear.

In `@infrastructure/compose/standalone/keycloak-only.yml`:
- Around line 32-33: Replace the hardcoded weak credentials by using
environment-variable interpolation for KEYCLOAK_ADMIN and
KEYCLOAK_ADMIN_PASSWORD (similar to the existing DB_PASSWORD pattern): reference
KEYCLOAK_ADMIN and KEYCLOAK_ADMIN_PASSWORD in the compose file and change them
to read from env vars with either a safe non-trivial default or a required/guard
pattern (e.g., use ${KEYCLOAK_ADMIN:?} style or a strong default) so the compose
no longer embeds "admin" / "admin123" in source control and will fail or prompt
when not provided.
- Around line 35-40: The compose YAML forces all social OAuth vars to be present
using the :? expansion (GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, FACEBOOK_APP_ID,
FACEBOOK_APP_SECRET, INSTAGRAM_CLIENT_ID, INSTAGRAM_CLIENT_SECRET), which aborts
docker compose for the standalone quick-test scenario; update those expansions
to use a permissive default (:-) so they become optional (e.g., change
${GOOGLE_CLIENT_ID:?GOOGLE_CLIENT_ID is required} → ${GOOGLE_CLIENT_ID:-}) and
ensure the realm import or provider setup code that consumes these env vars
skips configuring a provider when its values are empty (or document providing
placeholder values in a separate .env.standalone).

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs`:
- Around line 55-63: CreateUserCommand is currently constructed with LastName:
string.Empty which fails CreateUserCommandValidator (.NotEmpty(), min length and
pattern). In ProviderRegistrationEndpoints change the LastName assignment to a
non-empty placeholder (for example use request.Name or request.Name.Trim() as
LastName) so the CreateUserCommand passes validation for providers, or
alternatively update CreateUserCommandValidator to allow empty LastName when
Roles contains UserRoles.ProviderStandard; modify either the CreateUserCommand
construction or the validator logic referencing CreateUserCommandValidator,
CreateUserCommand, ProviderRegistrationEndpoints and UserRoles.ProviderStandard
accordingly.
- Around line 21-34: The public RegisterProvider route lacks rate limiting:
update the MapProviderRegistrationEndpoints pipeline by attaching a named
rate-limit policy to the "/register" endpoint (the fluent chain that calls
RegisterProviderAsync and currently uses AllowAnonymous) using
.RequireRateLimiting("provider-registration"); then register a corresponding
policy in the app startup (e.g., in Program.cs) with
builder.Services.AddRateLimiter and addPolicy "provider-registration"
(partitioning by IP and appropriate PermitLimit/Window/QueueLimit) and ensure
options.RejectionStatusCode is set to 429 so excessive anonymous registration
attempts are throttled.

In
`@src/Modules/Providers/Application/Handlers/Commands/RegisterProviderCommandHandler.cs`:
- Around line 23-27: In RegisterProviderCommandHandler, make the registration
idempotent by catching DbUpdateException specifically around the
providerRepository.AddAsync(...) call: add a catch (DbUpdateException ex) when
(ex.InnerException?.Message.Contains("ix_providers_user_id") ?? false) that
calls providerRepository.GetByUserIdAsync(command.UserId, cancellationToken) and
returns Result<ProviderDto>.Success(existingProvider.ToDto()); keep the generic
exception handling for other errors but replace the current generic 500 path for
unique-index violations with this lookup-and-success flow to match the earlier
existence check using providerRepository.GetByUserIdAsync.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx`:
- Around line 153-165: The document label currently uses form.watch("type") ===
EProviderType.Individual to show "CPF" and defaults to "CNPJ", which mislabels
Freelancers; update the condition in the FormField render (the block using
FormField, form.watch("type"), and EProviderType) to treat
EProviderType.Freelancer the same as EProviderType.Individual (e.g., check for
type === EProviderType.Individual || type === EProviderType.Freelancer or use an
inclusion check) so the FormLabel shows "CPF" for both Individual and Freelancer
and "CNPJ" for business types.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/perfil/page.tsx`:
- Around line 101-111: The "Preencher Endereço" Button rendered when
providerStatus.status === EProviderStatus.PendingBasicInfo is missing a
navigation handler; add either an onClick that calls the app router (e.g.,
router.push('/endereco') or the correct address form route) or wrap the Button
in a Link component so it actually navigates to the address form; update the
Button usage in page.tsx where providerStatus and
EProviderStatus.PendingBasicInfo are checked, ensuring the chosen route
string/href matches your address form route and preserving size/className props.

In `@src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx`:
- Around line 79-86: Guard against undefined label lookups by providing nullish
fallbacks: replace direct index usage of
PROVIDER_TIER_LABELS[providerStatus.tier] and
PROVIDER_STATUS_LABELS[providerStatus.status] with expressions that fall back to
a safe string (e.g. "Unknown" or "—") when undefined, e.g. use
(PROVIDER_TIER_LABELS[providerStatus.tier] ?? "Unknown") and
(PROVIDER_STATUS_LABELS[providerStatus.status] ?? "Unknown"); keep the existing
status class conditional using EProviderStatus.Active and providerStatus.status
unchanged so styling still depends on the enum value.
- Around line 69-103: The DropdownMenuSeparator currently only renders when
providerStatus is truthy; update the conditional around the separator so it
renders whenever isLoadingProvider is false (i.e., show the separator for both
the provider and non-provider paths). Locate the separator usage near the JSX
that references isLoadingProvider and providerStatus in the user-menu component
(symbols: isLoadingProvider, providerStatus, DropdownMenuSeparator) and move or
change its conditional to check !isLoadingProvider instead of providerStatus so
"Quero trabalhar" is preceded by the separator when not loading.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx`:
- Around line 54-60: Update Next's image config to allow external OAuth avatar
hosts by adding an images.remotePatterns array in next.config.ts including
entries for lh3.googleusercontent.com and avatars.githubusercontent.com (and
other provider hostnames as needed) so next/image calls in the Avatar component
won't throw; then modify the Avatar component (types: AvatarProps, component
function where Image is used) to collect and forward the remaining native img
props by including ...rest in the destructuring and passing {...rest} to the
Image component so props like onError, loading, and style are not dropped.

---

Outside diff comments:
In `@infrastructure/compose/standalone/keycloak-only.yml`:
- Around line 1-14: The header comments are stale and contradict the compose
file: update the top-of-file documentation to state that Keycloak is configured
to use Postgres (KC_DB: postgres), that KEYCLOAK_ADMIN and
KEYCLOAK_ADMIN_PASSWORD are currently hardcoded in the compose file (remove
instructions to export them or note they can be overridden via environment or an
.env file), and that the service port is fixed to 8080 (not an optional
KEYCLOAK_PORT); edit the header to accurately describe KC_DB, KC_* credentials
handling, and the fixed port behavior so readers aren’t misled by the previous
H2/variable instructions.

---

Duplicate comments:
In `@docs/roadmap.md`:
- Around line 41-43: Multiple sections still show inconsistent Sprint
names/statuses: normalize all occurrences so Sprint 8B uses the canonical title
"Sprint 8B - Authentication & Onboarding Flow" with status "EM ANDAMENTO" and
remove any "Mobile App" labeling, and ensure Sprint 8D is marked "CANCELADO"
everywhere (not "PLANEJADO"); search for and update all mentions of "Sprint 8B",
"Sprint 8D", "Mobile App (React Native)", and any "Planejado/⏳" state in the
sprint table and Sumário Executivo to match the canonical entries already
present in the roadmap header.
- Around line 59-75: Update the timing wording to remove the contradiction
between "pós-MVP" and "Antes de iniciar o Provider App (Sprint 8C+)" by choosing
one clear placement; for example, change the Status line that currently reads "⏳
Planejado pós-MVP" or the sentence "Antes de iniciar o Provider App (Sprint
8C+)" so they both indicate post‑MVP (e.g., "Planejado para depois do MVP" and
"Antes de iniciar o Provider App (Sprint 8C+), i.e., scheduled after MVP") or
explicitly state a sprint range (e.g., "Planned for after MVP — target: Sprint
9+"); ensure you update the phrases "pós-MVP" and "Sprint 8C+" so they
consistently reflect the chosen timing.

In
`@src/Modules/Providers/API/Endpoints/Public/Me/GetMyProviderStatusEndpoint.cs`:
- Around line 22-31: The OpenAPI metadata on GetMyProviderStatusEndpoint.Map
already includes the missing 400 response, so no code change is required to the
Map method or GetMyStatusAsync handler; mark the PR approval and remove the
duplicate reviewer/comment marker ([duplicate_comment]/[approve_code_changes])
from the review thread to avoid confusion.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/perfil/page.tsx`:
- Around line 1-9: No further changes required—this file correctly includes the
"use client" directive, destructures error from useProviderStatus, renders a
retry/error screen, and uses PROVIDER_STATUS_LABELS and PROVIDER_TIER_LABELS for
localization; you can proceed to merge as-is (no modifications needed to
ProviderProfilePage, useProviderStatus, or the referenced label constants).

In `@src/Web/MeAjudaAi.Web.Customer/types/provider.ts`:
- Around line 1-30: Enums EProviderType, EProviderStatus, EProviderTier, and
EVerificationStatus already match the backend definitions; no code change
required—keep the current exported enums (EProviderType, EProviderStatus,
EProviderTier, EVerificationStatus) as-is and run the related DTO/unit tests to
confirm parity.

---

Nitpick comments:
In `@infrastructure/compose/standalone/keycloak-only.yml`:
- Around line 43-44: Replace the hardcoded port mapping "- \"8080:8080\"" with
an environment-driven mapping that uses the KEYCLOAK_PORT variable (e.g., "-
\"${KEYCLOAK_PORT:-8081}:8080\"") so the compose file keeps the previous default
of 8081 to avoid port conflicts while allowing overrides via KEYCLOAK_PORT;
update any comments if needed to reflect the change.

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs`:
- Line 45: Replace the hardcoded category string passed to
loggerFactory.CreateLogger with a type-based category to avoid drift when the
class is renamed; specifically, use the ProviderRegistrationEndpoints type
(e.g., pass typeof(ProviderRegistrationEndpoints).FullName or use a generic
ILogger<T>/CreateLogger<T> variant) in the call to loggerFactory.CreateLogger so
the logger category is tied to the ProviderRegistrationEndpoints type instead of
a literal string.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx`:
- Around line 16-45: The superRefine in formSchema only checks
EProviderType.Individual and EProviderType.Company, leaving
EProviderType.Cooperative and EProviderType.Freelancer validated only by the
generic min/max; update the superRefine in page.tsx to explicitly handle
EProviderType.Cooperative (require documentNumber.length === 14, CNPJ) and
EProviderType.Freelancer (require documentNumber.length === 11, CPF), adding
ctx.addIssue calls identical to the existing messages for Company/Individual so
those types get the correct length validation.
- Around line 63-73: The email value is populated in the useEffect using
session.user.email via form.setValue but there is no visible FormField for
"email", so users cannot see or edit it; either add a visible FormField bound to
the same form key "email" in the JSX (matching the schema/defaults) so the
populated value is rendered and editable, or if the email must be immutable, add
a clear inline comment near the useEffect and/or render a read-only FormField
for "email" to make intent explicit (refer to useEffect, form.setValue("email"),
form.getValues("email"), and the form schema).

In `@src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx`:
- Around line 83-87: The status color currently uses a single conditional on
providerStatus.status === EProviderStatus.Active and falls back to
"text-amber-600" for all non-active values; change the logic so terminal-failure
states (EProviderStatus.Suspended and EProviderStatus.Rejected) render with a
destructive red class (e.g., "text-red-600 font-medium") while other non-active
states keep the amber class; update the JSX around providerStatus.status (or
extract a small helper like getStatusClass(status)) so the span's className
picks "text-green-600 font-medium" for Active, "text-red-600 font-medium" for
Suspended/Rejected, and "text-amber-600" for the remaining statuses, leaving
PROVIDER_STATUS_LABELS unchanged.
- Around line 24-25: In the UserMenu React component in user-menu.tsx there is a
duplicated inline comment "// Loading state - prevent flash of unauthenticated
UI" on two consecutive lines; remove the redundant line so only one instance of
that comment remains (keep a single comment immediately before the loading-state
logic or return statement where it currently sits).

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx`:
- Line 5: AvatarProps currently extends ComponentProps<"img"> but the component
only destructures five specific props (the named props in the Avatar component)
while the rest are dropped, creating a misleading contract; either narrow
AvatarProps to list only the exact props the component consumes or restore and
forward the remaining native img attributes by reintroducing the ...rest spread
and applying it to the rendered element (update the Avatar component signature
and JSX accordingly). Locate AvatarProps and the Avatar functional component to
either replace the extends ComponentProps<"img"> with an explicit prop list
(e.g., url, alt, size, fallback, className — whatever five props are used) or
add back ...rest in the parameter destructure and spread {...rest} on the
rendered img/container so native img props like onError, loading, style, data-*
are preserved.

In `@src/Web/MeAjudaAi.Web.Customer/types/provider.ts`:
- Around line 32-53: Add a new mapping constant VERIFICATION_STATUS_LABELS:
Record<EVerificationStatus, string> to mirror the existing PROVIDER_*_LABELS so
verification enums render user-friendly text; locate the enums usage around
PROVIDER_STATUS_LABELS/PROVIDER_TYPE_LABELS and define
VERIFICATION_STATUS_LABELS with keys like [EVerificationStatus.Unverified],
[EVerificationStatus.Pending], [EVerificationStatus.Verified], etc., mapping
each EVerificationStatus value to the appropriate Portuguese label and export it
for consumption where verification status is displayed.
- Around line 70-79: ProviderDto currently only lists 7 fields while
ProviderMapper.ToDto returns ~17; update ProviderDto to include the missing
backend fields (e.g., BusinessProfile, Documents, Qualifications, Services,
CreatedAt, SuspensionReason, RejectionReason and any other properties emitted by
ProviderMapper.ToDto), mark fields optional if they may be absent, and import or
define the correct types for each field so the frontend type matches the backend
mapper; also add a short TODO/TICKET comment referencing ProviderMapper.ToDto so
future changes remain synchronized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts (1)

73-137: ⚠️ Potential issue | 🟠 Major

Remove unnecessary @ts-ignore directives or add missing properties to backend DTO.

Most properties accessed in this mapper (businessProfile, contactInfo, primaryAddress, fantasyName, legalName, email, phoneNumber, city, state, status, verificationStatus, documents, qualifications) already exist in the generated MeAjudaAiModulesProvidersApplicationDtosProviderDto type and don't require suppression. However, four properties are genuinely missing from the generated type and should be added to the backend DTO: services, averageRating, reviewCount, and tier. Either update the backend ProviderDto to include these fields and regenerate types, or remove the @ts-ignore comments for properties that are already properly typed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts` around lines 73 - 137, The
mapper mapApiProviderToProvider is using many unnecessary `@ts-ignore` directives
and is missing four actual DTO fields (services, averageRating, reviewCount,
tier) on MeAjudaAiModulesProvidersApplicationDtosProviderDto; fix by either (A)
updating the backend ProviderDto to include services, averageRating, reviewCount
and tier and then regenerate the TypeScript DTOs so mapApiProviderToProvider can
rely on strong types, or (B) if the generated DTO already includes the
referenced properties (businessProfile, contactInfo, primaryAddress,
fantasyName, legalName, email, phoneNumber, city, state, status,
verificationStatus, documents, qualifications), remove the redundant `@ts-ignore`
lines in mapApiProviderToProvider and update the mapper signatures/field
accesses to match the generated types (ensuring services is typed as an array
and tier/averageRating/reviewCount have appropriate types) so TypeScript type
checking passes without ignores.
🧹 Nitpick comments (8)
src/Web/MeAjudaAi.Web.Customer/hooks/use-my-provider-profile.ts (1)

18-22: Content-Type: application/json is unnecessary on a GET request with no body.

This won't cause issues but adds noise. Consider removing it for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-my-provider-profile.ts` around lines
18 - 22, The GET request in the use-my-provider-profile hook is sending an
unnecessary "Content-Type: application/json" header; remove "Content-Type" from
the headers object passed to fetch(`${baseUrl}/api/v1/providers/me`, ...) (or
conditionally add Content-Type only when a request body exists) so the const
response = await fetch(...) call only includes Authorization and any required
headers for GET.
src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts (1)

17-47: Consider wrapping fetchAddress in useCallback.

fetchAddress gets a new reference on every render since it's a plain async arrow function. If a consumer passes it as a dependency (e.g., in a useEffect dep array), it would re-trigger unnecessarily. Wrapping in useCallback with an empty dependency array stabilizes the reference.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts` around lines 17 - 47,
fetchAddress is recreated each render causing unstable references; wrap the
async function in React's useCallback to stabilize it. Update the definition of
fetchAddress (the async function that validates/cleans cep, calls fetch, sets
setIsLoading/setError and returns ViaCepResponse|null) to use useCallback(() =>
{ ... }, []) so its identity is stable for consumers (e.g., effects or prop
deps); ensure any state setters used (setIsLoading, setError) are referenced
safely and include the same logic inside the callback.
src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts (1)

13-14: Base URL resolution and fetch pattern duplicated across all provider hooks.

The client.getConfig().baseUrl || process.env.NEXT_PUBLIC_API_URL || "http://localhost:7002" pattern plus manual fetch with bearer token headers is repeated verbatim in use-register-provider, use-update-provider-profile, and use-my-provider-profile. Extract a shared authenticated-fetch helper to reduce duplication and ensure consistent error handling (e.g., this hook doesn't check error.detail, but the update hook does).

Also applies to: 20-27, 29-31

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts` around lines
13 - 14, Extract the repeated baseUrl resolution and bearer-token fetch logic
into a shared helper (e.g., create an exported function authFetch or
useAuthenticatedFetch) and replace the duplicate code in useRegisterProvider
(use-register-provider.ts), useUpdateProviderProfile
(use-update-provider-profile.ts) and useMyProviderProfile
(use-my-provider-profile.ts) to call that helper; the helper should compute
baseUrl once (using client.getConfig().baseUrl ||
process.env.NEXT_PUBLIC_API_URL || "http://localhost:7002"), attach
Authorization: `Bearer ${token}` and Content-Type headers, perform fetch, parse
JSON, and normalize error handling so callers consistently check error.detail
(or return a standardized { ok, data, error } shape) rather than each hook
duplicating parsing/validation.
src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts (1)

8-31: Inline request type duplicates shared DTO structure.

UpdateProviderProfileRequest is defined inline here with nested businessProfile, contactInfo, and primaryAddress shapes that overlap with BusinessProfileDto, ContactInfoDto, and AddressDto from @/types/api/provider. This introduces drift risk (e.g., logoUrl here isn't in the shared types). Consider composing from the shared types.

Example
-interface UpdateProviderProfileRequest {
-    name: string;
-    businessProfile: {
-        legalName: string;
-        ...
-    };
-}
+import { BusinessProfileDto } from "@/types/api/provider";
+
+interface UpdateProviderProfileRequest {
+    name: string;
+    businessProfile: BusinessProfileDto;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts` around
lines 8 - 31, The inline UpdateProviderProfileRequest duplicates nested DTO
shapes; replace it by importing and composing the shared types
(BusinessProfileDto, ContactInfoDto, AddressDto) from "@/types/api/provider" and
use them in the UpdateProviderProfileRequest signature (e.g., name: string;
businessProfile: BusinessProfileDto) ensuring you reconcile optional fields like
logoUrl to match the shared DTOs (or add a clear comment if an extra optional
field is intentionally required) and remove the duplicated nested definitions
(update any usages of UpdateProviderProfileRequest to the composed type).
src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts (1)

136-157: Label constants look correct for pt-BR UI.

The label maps cover all enum members. One minor note: Standard (Line 153) is kept in English while other tiers are translated — this appears intentional per the comment on Line 154.

Based on learnings: prefer reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code. Consider whether these enums and label maps could be auto-generated or sourced from a shared contract to prevent future drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts` around lines 136 - 157,
PROVIDER_* label maps are fine but to avoid drift you should import and reuse
the canonical enums from Shared.Contracts instead of relying on local enum
definitions; update the module to reference the shared EProviderType,
EProviderStatus and EProviderTier types and ensure PROVIDER_TYPE_LABELS,
PROVIDER_STATUS_LABELS and PROVIDER_TIER_LABELS are keyed by those imported
enums (or generate the maps from a shared contract/utility at build time),
keeping the existing translated strings (note the intentional "Standard"
comment) and add a short TODO comment to indicate auto-generation is preferred.
src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/perfil/endereco/page.tsx (2)

52-64: CEP auto-fill doesn't clear stale validation errors.

form.setValue calls (lines 57-60) don't pass { shouldValidate: true }, so if the user previously attempted submission and has validation errors on street, neighborhood, city, or state, those error messages will persist even after successful auto-fill.

Suggested fix
-                form.setValue("street", data.logradouro);
-                form.setValue("neighborhood", data.bairro);
-                form.setValue("city", data.localidade);
-                form.setValue("state", data.uf);
+                form.setValue("street", data.logradouro, { shouldValidate: true });
+                form.setValue("neighborhood", data.bairro, { shouldValidate: true });
+                form.setValue("city", data.localidade, { shouldValidate: true });
+                form.setValue("state", data.uf, { shouldValidate: true });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/perfil/endereco/page.tsx`
around lines 52 - 64, The CEP autofill in handleCepBlur does not clear prior
validation errors because form.setValue calls for "street", "neighborhood",
"city", and "state" are missing the validation option; update each form.setValue
call inside handleCepBlur to include the options object { shouldValidate: true }
(e.g., form.setValue("street", data.logradouro, { shouldValidate: true })) so
that stale errors are re-validated/cleared after autofill, leaving
form.setFocus("number") as-is.

36-50: form in the useEffect dependency array may trigger unnecessary resets.

While useForm returns a stable object reference in practice, including form in the dependency array is a common source of lint warnings and can cause unexpected re-runs if the reference ever changes (e.g., due to component re-mount patterns). Consider using form.reset directly or extracting it:

Suggested fix
+    const { reset } = form;
+
     useEffect(() => {
         if (profile?.businessProfile?.primaryAddress) {
             const addr = profile.businessProfile.primaryAddress;
-            form.reset({
+            reset({
                 zipCode: addr.zipCode || "",
                 ...
             });
         }
-    }, [profile, form]);
+    }, [profile, reset]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/perfil/endereco/page.tsx`
around lines 36 - 50, The useEffect currently depends on the entire form object
which can cause unnecessary re-runs; extract the reset method from the form
(e.g., const { reset } = form or destructure the useForm return) and call
reset(...) inside useEffect, then update the dependency array to only include
profile (or profile.businessProfile.primaryAddress) and reset — e.g.,
useEffect(..., [profile, reset]) so the effect only runs when the profile or
reset changes rather than when the whole form reference changes. Ensure you
reference the existing symbols: useEffect,
profile.businessProfile.primaryAddress, and form.reset (now reset).
src/Web/MeAjudaAi.Web.Customer/components/ui/verified-badge.tsx (1)

6-6: Type says required but runtime guards for null/undefined.

status is typed as EVerificationStatus (non-optional), yet line 13 explicitly checks === undefined || === null. Either mark the prop optional (status?: EVerificationStatus) to match the defensive guard, or drop the guard if callers are expected to always provide it. Current mismatch lets TypeScript-unaware callers pass undefined without a type error.

Suggested fix (make optional to match guard)
 interface VerifiedBadgeProps {
-    status: EVerificationStatus;
+    status?: EVerificationStatus;
     className?: string;

Also applies to: 13-13

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/verified-badge.tsx` at line 6,
The prop declaration for status is typed as non-optional (status:
EVerificationStatus) but the component contains runtime guards checking for
undefined/null; make the types align by updating the prop to be optional
(status?: EVerificationStatus) in the VerifiedBadge/props interface so
TypeScript reflects that callers may omit or pass undefined, and keep the
existing null/undefined checks, or alternatively remove the runtime guard and
keep status required—pick the optional change to match current guards and update
the prop signature accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/Modules/Providers/Tests/Unit/Application/Validators/AddDocumentRequestValidatorTests.cs`:
- Line 127: The test fails because AddDocumentRequestValidator produces a
mixed-language message (Portuguese prefix + English enum suffix); update the
validator (AddDocumentRequestValidator) to format the enum validation message
entirely in Portuguese (translate or replace the helper text that currently
emits "Valid EDocumentType values: ...") and ensure it lists the EDocumentType
values in Portuguese if applicable; then update the test assertion in
AddDocumentRequestValidatorTests.cs to expect the fully Portuguese message
instead of the mixed string (replace the .WithErrorMessage(...) expected text to
the corrected Portuguese-only message that AddDocumentRequestValidator now
returns).

In
`@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/perfil/endereco/page.tsx`:
- Around line 17-21: Destructure and check the error returned by
useMyProviderProfile in ProviderAddressPage (e.g., const { data: profile, error,
isLoading: isLoadingProfile } = useMyProviderProfile()) and add an error branch
that renders a visible error state (matching the profile page pattern) instead
of rendering the form when error is present; also update onSubmit (the function
that currently early-returns when !profile) to guard against silent no-ops by
preventing submission and surface the error to the user if profile is missing,
and apply the same error-destructure + error-branch pattern to the other
identical hook usage later in the file (the block referenced around 106-112).
- Around line 93-95: Update the local UpdateProviderProfileRequest interface in
use-update-provider-profile.ts to exactly mirror the generated API type
MeAjudaAiModulesProvidersApplicationDtosRequestsUpdateProviderProfileRequest:
make all top-level fields optional, make nested BusinessProfileDto fields
optional as in the generated contract, and remove the non-existent logoUrl
property; then remove the `@ts-ignore` by letting updateProfile accept the
corrected UpdateProviderProfileRequest so TypeScript validates calls correctly.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-my-provider-profile.ts`:
- Around line 37-43: The current fallback "return json.data || json" masks
legitimate null ApiResponse.data values; in the useMyProviderProfile hook (the
json handling block) change the logic to explicitly check for the presence of
the data field and return its value (which may be null) instead of falling back
to the wrapper object — e.g., if ('data' in json) return json.data; otherwise
return json; keep the existing Result<T> handling for json.value as-is.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts`:
- Around line 42-44: The onSuccess handler in the useRegisterProvider hook only
invalidates ["providerStatus"] so myProviderProfile stays stale after
registration; update the onSuccess in use-register-provider.ts (the mutation
success callback where queryClient.invalidateQueries is called) to also
invalidate the ["myProviderProfile"] queryKey (i.e., call
queryClient.invalidateQueries for ["myProviderProfile"] alongside
["providerStatus"]) so the fresh profile is refetched immediately after
registration.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts`:
- Around line 60-68: The response handling in use-update-provider-profile.ts
incorrectly uses "json.data || json", causing an ApiResponse<ProviderDto> to be
returned when data is null; update the code to explicitly extract the
ProviderDto using nullish coalescing and any Result wrapper (e.g., return
json.data ?? json.value ?? /* fallback */ undefined or throw) and remove the
`@ts-ignore` so types are preserved; additionally factor this behavior into a
shared helper (e.g., normalizeApiResponse<T>(json: ApiResponse<T> | T)) and call
it from the update function to ensure consistent, type-safe normalization across
hooks.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 60-63: Replace the magic numbers in the object literal with the
corresponding enum members: use EProviderStatus.<appropriateActiveMember>
instead of 2, EVerificationStatus.<appropriateVerifiedMember> instead of 1, and
EProviderTier.<appropriateTierMember> instead of 0; also ensure the file imports
the enums (EProviderType, EProviderStatus, EVerificationStatus, EProviderTier
and ProviderServiceDto) from '@/types/api/provider' so the mapped object in the
mapper function uses the named enum constants rather than numeric literals.

In `@src/Web/MeAjudaAi.Web.Customer/lib/schemas/auth.ts`:
- Around line 13-32: The superRefine block in the schema only enforces document
length for EProviderType.Individual (CPF 11) and EProviderType.Company (CNPJ
14), leaving EProviderType.Freelancer and EProviderType.Cooperative to rely on
the generic .min(11).max(14) rule; update the superRefine in auth.ts to handle
those types explicitly (e.g., validate EProviderType.Freelancer as CPF length 11
and EProviderType.Cooperative as either CPF or CNPJ per your business rule) by
adding branches for EProviderType.Freelancer and EProviderType.Cooperative that
call ctx.addIssue on invalid lengths for documentNumber, or if the current
permissive behavior is intended, add a clear inline comment in the superRefine
mentioning that Freelancer and Cooperative intentionally allow 11–14 digit
documents.

In `@src/Web/MeAjudaAi.Web.Customer/middleware.ts`:
- Around line 11-15: Middleware currently only protects exact "/prestador" via
isProviderDashboard and thus misses the new provider self-service routes under
"/cadastro", allowing unauthenticated access; update the isProtected logic
(referencing pathname, isProviderDashboard, isProfile, isClientDashboard, and
isProtected) to include the cadastro routes (e.g.,
pathname.startsWith("/cadastro") or specific "/cadastro/prestador*" checks) so
those pages are covered by authentication middleware or alternatively enforce
redirect() in the corresponding layout.

In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts`:
- Around line 85-89: The current DocumentDto interface is missing and misnamed
fields compared to the backend; replace the simple { type: string; url: string;
status: string } with a shape that matches the backend DTO: include Id: string,
ProviderId: string, DocumentType: EDocumentType (or a string union matching the
backend enum), FileName: string, FileUrl: string, Status: EDocumentStatus (or
matching enum), UploadedAt: string | Date, VerifiedAt?: string | Date,
RejectionReason?: string, and OcrData?: string; update the DocumentDto
definition and any consumers to use these exact property names/types (and import
or declare EDocumentType/EDocumentStatus) so serialization/deserialization
aligns with the API.

---

Outside diff comments:
In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 73-137: The mapper mapApiProviderToProvider is using many
unnecessary `@ts-ignore` directives and is missing four actual DTO fields
(services, averageRating, reviewCount, tier) on
MeAjudaAiModulesProvidersApplicationDtosProviderDto; fix by either (A) updating
the backend ProviderDto to include services, averageRating, reviewCount and tier
and then regenerate the TypeScript DTOs so mapApiProviderToProvider can rely on
strong types, or (B) if the generated DTO already includes the referenced
properties (businessProfile, contactInfo, primaryAddress, fantasyName,
legalName, email, phoneNumber, city, state, status, verificationStatus,
documents, qualifications), remove the redundant `@ts-ignore` lines in
mapApiProviderToProvider and update the mapper signatures/field accesses to
match the generated types (ensuring services is typed as an array and
tier/averageRating/reviewCount have appropriate types) so TypeScript type
checking passes without ignores.

---

Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts`:
- Around line 56-83: Enums EProviderType, EProviderStatus, EVerificationStatus,
and EProviderTier now match backend numeric definitions so no code changes are
needed; leave the enum declarations (EProviderType, EProviderStatus,
EVerificationStatus, EProviderTier) as-is and proceed to approve/merge the
change.

---

Nitpick comments:
In
`@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/perfil/endereco/page.tsx`:
- Around line 52-64: The CEP autofill in handleCepBlur does not clear prior
validation errors because form.setValue calls for "street", "neighborhood",
"city", and "state" are missing the validation option; update each form.setValue
call inside handleCepBlur to include the options object { shouldValidate: true }
(e.g., form.setValue("street", data.logradouro, { shouldValidate: true })) so
that stale errors are re-validated/cleared after autofill, leaving
form.setFocus("number") as-is.
- Around line 36-50: The useEffect currently depends on the entire form object
which can cause unnecessary re-runs; extract the reset method from the form
(e.g., const { reset } = form or destructure the useForm return) and call
reset(...) inside useEffect, then update the dependency array to only include
profile (or profile.businessProfile.primaryAddress) and reset — e.g.,
useEffect(..., [profile, reset]) so the effect only runs when the profile or
reset changes rather than when the whole form reference changes. Ensure you
reference the existing symbols: useEffect,
profile.businessProfile.primaryAddress, and form.reset (now reset).

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/verified-badge.tsx`:
- Line 6: The prop declaration for status is typed as non-optional (status:
EVerificationStatus) but the component contains runtime guards checking for
undefined/null; make the types align by updating the prop to be optional
(status?: EVerificationStatus) in the VerifiedBadge/props interface so
TypeScript reflects that callers may omit or pass undefined, and keep the
existing null/undefined checks, or alternatively remove the runtime guard and
keep status required—pick the optional change to match current guards and update
the prop signature accordingly.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-my-provider-profile.ts`:
- Around line 18-22: The GET request in the use-my-provider-profile hook is
sending an unnecessary "Content-Type: application/json" header; remove
"Content-Type" from the headers object passed to
fetch(`${baseUrl}/api/v1/providers/me`, ...) (or conditionally add Content-Type
only when a request body exists) so the const response = await fetch(...) call
only includes Authorization and any required headers for GET.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts`:
- Around line 13-14: Extract the repeated baseUrl resolution and bearer-token
fetch logic into a shared helper (e.g., create an exported function authFetch or
useAuthenticatedFetch) and replace the duplicate code in useRegisterProvider
(use-register-provider.ts), useUpdateProviderProfile
(use-update-provider-profile.ts) and useMyProviderProfile
(use-my-provider-profile.ts) to call that helper; the helper should compute
baseUrl once (using client.getConfig().baseUrl ||
process.env.NEXT_PUBLIC_API_URL || "http://localhost:7002"), attach
Authorization: `Bearer ${token}` and Content-Type headers, perform fetch, parse
JSON, and normalize error handling so callers consistently check error.detail
(or return a standardized { ok, data, error } shape) rather than each hook
duplicating parsing/validation.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts`:
- Around line 8-31: The inline UpdateProviderProfileRequest duplicates nested
DTO shapes; replace it by importing and composing the shared types
(BusinessProfileDto, ContactInfoDto, AddressDto) from "@/types/api/provider" and
use them in the UpdateProviderProfileRequest signature (e.g., name: string;
businessProfile: BusinessProfileDto) ensuring you reconcile optional fields like
logoUrl to match the shared DTOs (or add a clear comment if an extra optional
field is intentionally required) and remove the duplicated nested definitions
(update any usages of UpdateProviderProfileRequest to the composed type).

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts`:
- Around line 17-47: fetchAddress is recreated each render causing unstable
references; wrap the async function in React's useCallback to stabilize it.
Update the definition of fetchAddress (the async function that validates/cleans
cep, calls fetch, sets setIsLoading/setError and returns ViaCepResponse|null) to
use useCallback(() => { ... }, []) so its identity is stable for consumers
(e.g., effects or prop deps); ensure any state setters used (setIsLoading,
setError) are referenced safely and include the same logic inside the callback.

In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts`:
- Around line 136-157: PROVIDER_* label maps are fine but to avoid drift you
should import and reuse the canonical enums from Shared.Contracts instead of
relying on local enum definitions; update the module to reference the shared
EProviderType, EProviderStatus and EProviderTier types and ensure
PROVIDER_TYPE_LABELS, PROVIDER_STATUS_LABELS and PROVIDER_TIER_LABELS are keyed
by those imported enums (or generate the maps from a shared contract/utility at
build time), keeping the existing translated strings (note the intentional
"Standard" comment) and add a short TODO comment to indicate auto-generation is
preferred.

Filipe Frigini added 3 commits February 19, 2026 10:29
…hentication, authorization, CORS, and rate limiting.
…d detailed viewing pages with supporting API types and hooks.
… validation rules for document number and type.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (10)
src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts (3)

28-28: Extract the ViaCEP base URL to a named constant.

The URL is hardcoded inline and would need updating in multiple places if the hook is later extended (or if a mock/proxy is needed in test environments).

♻️ Proposed refactor: named constant
+const VIA_CEP_BASE_URL = "https://viacep.com.br/ws";
+
 export function useViaCep() {
     ...
-            const response = await fetch(`https://viacep.com.br/ws/${cleanCep}/json/`);
+            const response = await fetch(`${VIA_CEP_BASE_URL}/${cleanCep}/json/`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts` at line 28, The fetch
URL in the useViaCep hook is hardcoded; extract the ViaCEP base URL into a named
constant (e.g., VIACEP_BASE_URL) and use it when building the request (replace
the inline string in the fetch call that uses cleanCep). Update any other
occurrences in useViaCep to reference the constant so tests or environments can
override it easily and keep behavior unchanged.

41-43: err is declared but never used — prefer a no-binding catch or a typed guard.

♻️ Proposed fix
-        } catch (err) {
+        } catch {
             setError("Falha ao consultar CEP");
             return null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts` around lines 41 - 43,
The catch block in use-via-cep.ts declares an unused variable err; remove the
unused binding by changing the catch to a no-binding catch (catch { ... }) and
keep the existing setError("Falha ao consultar CEP"); return null; flow, or if
you need error details, type the parameter as unknown (catch (err: unknown)) and
add a narrow/checked handling (e.g., extract message safely) before logging or
using it; update the catch around the code in the useViaCep hook where setError
is called.

17-47: Add AbortController to prevent stale state from concurrent/overlapping requests.

If fetchAddress is triggered on rapid successive inputs (or on every keystroke), earlier in-flight requests can resolve after later ones — leaving error/isLoading reflecting a stale response. Additionally, if the component unmounts mid-flight, the finally block still calls setIsLoading(false) on a dead component tree.

♻️ Proposed refactor: AbortController for request lifecycle
-    const fetchAddress = useCallback(async (cep: string): Promise<ViaCepResponse | null> => {
+    const abortControllerRef = useRef<AbortController | null>(null);
+
+    const fetchAddress = useCallback(async (cep: string): Promise<ViaCepSuccessResponse | null> => {
         const cleanCep = cep.replace(/\D/g, "");
         if (cleanCep.length !== 8) {
             return null;
         }

+        // Cancel any in-flight request before starting a new one
+        abortControllerRef.current?.abort();
+        const controller = new AbortController();
+        abortControllerRef.current = controller;
+
         setIsLoading(true);
         setError(null);

         try {
-            const response = await fetch(`https://viacep.com.br/ws/${cleanCep}/json/`);
+            const response = await fetch(`https://viacep.com.br/ws/${cleanCep}/json/`, {
+                signal: controller.signal,
+            });
             if (!response.ok) {
                 throw new Error("Erro ao buscar CEP");
             }

-            const data: ViaCepResponse = await response.json();
+            const data: ViaCepApiResponse = await response.json();

             if (data.erro) {
                 setError("CEP não encontrado");
                 return null;
             }

-            return data;
-        } catch (err) {
+            return data as ViaCepSuccessResponse;
+        } catch (err) {
+            if (err instanceof Error && err.name === "AbortError") return null;
             setError("Falha ao consultar CEP");
             return null;
         } finally {
             setIsLoading(false);
         }
     }, []);

Also add useRef to the import on line 1:

-import { useState, useCallback } from "react";
+import { useState, useCallback, useRef } from "react";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts` around lines 17 - 47,
The fetchAddress useCallback can leave stale state from overlapping requests and
update state after unmount; fix it by adding an AbortController stored in a ref
(add useRef to imports) and, inside fetchAddress, abort any previous
controller.current before creating a new controller, pass controller.signal to
fetch, and set controller.current = controller; in catch, ignore AbortError
(don't call setError) and in finally only call setIsLoading(false) when
controller.signal.aborted is false (or when the current controller still matches
the one used for this fetch) to avoid updating state from stale or unmounted
requests; also add a useEffect cleanup that aborts controller.current on
unmount.
src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx (2)

20-20: type preprocessing silently coerces any string to EProviderType.Individual.

typeof val === 'string' ? EProviderType.Individual : val maps every string value — "Company", "Freelancer", "2" — to Individual (1). While type is not rendered in the current JSX, this schema will mislead any future consumer that reads providerData.type. A proper string-to-enum mapping or a plain .optional() without coercion would be more correct.

♻️ Proposed fix
-    type: z.preprocess((val) => typeof val === 'string' ? EProviderType.Individual : val, z.nativeEnum(EProviderType).optional()), // Relaxed
+    type: z.preprocess((val) => {
+        if (typeof val === 'number') return val;
+        if (typeof val === 'string') {
+            const key = val as keyof typeof EProviderType;
+            return EProviderType[key] ?? EProviderType.Individual;
+        }
+        return val;
+    }, z.nativeEnum(EProviderType).optional()),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx at line
20, The z.preprocess for the `type` field currently coerces any string to
EProviderType.Individual which is incorrect; update the preprocessing in the
schema that defines `type` so it either (a) maps known string values to the
corresponding EProviderType members (e.g., convert "Individual"/"Company"
strings to EProviderType.Individual/EProviderType.Company) and returns the
original value for non-string inputs, or (b) remove the coercion entirely and
use z.nativeEnum(EProviderType).optional() so strings are not silently
converted; ensure references like providerData.type and the enum EProviderType
are used to validate/convert only well-known values.

198-198: Prefer ?? over || for numeric-enum fallback.

providerData.verificationStatus || EVerificationStatus.Pending treats 0 (Pending) as falsy and falls through to the right-hand side — which is also 0, so the result is accidentally correct today. If EVerificationStatus values are ever reshuffled, || will silently pass 0 through incorrectly. Use ?? to be explicit.

-<VerifiedBadge status={providerData.verificationStatus || EVerificationStatus.Pending} size="lg" />
+<VerifiedBadge status={providerData.verificationStatus ?? EVerificationStatus.Pending} size="lg" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx at line
198, Replace the falsy-check fallback using || with the nullish coalescing
operator so numeric enum values like 0 aren't treated as absent: update the
VerifiedBadge prop expression that uses providerData.verificationStatus ||
EVerificationStatus.Pending to use providerData.verificationStatus ??
EVerificationStatus.Pending instead, leaving the VerifiedBadge component and the
enum EVerificationStatus unchanged.
src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx (1)

10-10: Import path inconsistency: use @/types/api/provider directly.

This file imports EProviderType from @/types/provider, while every other file in this PR (e.g., prestador/[id]/page.tsx, prestador/page.tsx, verified-badge.tsx) imports from @/types/api/provider. If @/types/provider is a thin re-export, both work, but the inconsistency is confusing and could diverge if @/types/provider is extended independently.

-import { EProviderType } from "@/types/provider";
+import { EProviderType } from "@/types/api/provider";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx` at line 10,
The import for EProviderType is inconsistent—change the import source from
"@/types/provider" to "@/types/api/provider" so it matches other files (e.g.,
prestador/[id]/page.tsx, prestador/page.tsx, verified-badge.tsx); update the
import statement that references EProviderType in the
cadastro/prestador/page.tsx module to import EProviderType from
"@/types/api/provider" to avoid divergence.
src/Bootstrapper/MeAjudaAi.ApiService/Extensions/SecurityExtensions.cs (1)

455-464: Extract the policy name to a constant and consider the "anonymous" IP-fallback risk.

The raw string "provider-registration" is used consistently in both the policy definition (line 455) and the endpoint requirement (ProviderRegistrationEndpoints.cs). However, the pattern exists elsewhere in RateLimitPolicies (e.g., RateLimitPolicies.Public). Extract the policy name as a constant to align with the existing convention:

public const string ProviderRegistration = "provider-registration";

Additionally, consider the "anonymous" IP-fallback: every request with a null RemoteIpAddress (common behind certain reverse proxies or in containerized environments) shares a single 5-req/min partition. This can simultaneously false-throttle legitimate users and be exploited by attackers who strip the IP header. Prefer keying on X-Forwarded-For (with validation) or a unique per-connection identifier like context.Connection.Id.

Suggested approach
-            options.AddPolicy("provider-registration", context =>
+            options.AddPolicy(RateLimitPolicies.ProviderRegistration, context =>
                 RateLimitPartition.GetFixedWindowLimiter(
-                    partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "anonymous",
+                    partitionKey: context.Request.Headers["X-Forwarded-For"].FirstOrDefault()
+                                  ?? context.Connection.RemoteIpAddress?.ToString()
+                                  ?? context.Connection.Id,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Bootstrapper/MeAjudaAi.ApiService/Extensions/SecurityExtensions.cs`
around lines 455 - 464, Extract the literal "provider-registration" into a
constant (e.g., add public const string ProviderRegistration =
"provider-registration" in RateLimitPolicies) and use that constant when calling
options.AddPolicy and when requiring the policy in
ProviderRegistrationEndpoints; also stop using the hardcoded "anonymous"
fallback for the partition key — replace partitionKey:
context.Connection.RemoteIpAddress?.ToString() ?? "anonymous" with a fallback
that uses a validated X-Forwarded-For header when present
(sanitized/whitelisted) or, if absent, a per-connection identifier such as
context.Connection.Id to avoid collapsing all null-IP requests into one shared
bucket.
src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts (2)

36-39: Unused data parameter in onSuccess.

The callback receives the mutation result but never uses it. Prefix with _ or drop it to silence the linter.

♻️ Proposed fix
-        onSuccess: (data) => {
+        onSuccess: () => {
             queryClient.invalidateQueries({ queryKey: ["providerStatus"] });
             queryClient.invalidateQueries({ queryKey: ["myProviderProfile"] });
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts` around
lines 36 - 39, The onSuccess callback in the use-update-provider-profile hook
declares a parameter named data that is never used; either remove the parameter
or rename it to _data to satisfy the linter. Update the onSuccess declaration in
use-update-provider-profile.ts (the onSuccess handler that calls
queryClient.invalidateQueries and queryClient.invalidateQueries for
"providerStatus" and "myProviderProfile") so it no longer introduces an unused
variable.

3-6: Remove unused imports client and ApiResponse.

client (Line 3) and ApiResponse (Line 5) are never referenced in this file. The hook fully delegates to authenticatedFetch, which internally calls client.getConfig() and normalises ApiResponse<T> / Result<T> wrappers — the consumer no longer needs either import.

♻️ Proposed fix
 import { useMutation, useQueryClient } from "@tanstack/react-query";
 import { useSession } from "next-auth/react";
-import { client } from "@/lib/api/client";
 import { ProviderDto, ContactInfoDto, AddressDto } from "@/types/api/provider";
-import { ApiResponse } from "@/types/api";
 import { authenticatedFetch } from "@/lib/api/fetch-client";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts` around
lines 3 - 6, Remove the unused imports `client` and `ApiResponse` from the top
of use-update-provider-profile.ts; keep the imports needed by the hook (e.g.,
`authenticatedFetch`, `ProviderDto`, `ContactInfoDto`, `AddressDto`) and ensure
no other references to `client` or `ApiResponse` remain in the file (the hook
delegates to `authenticatedFetch` which handles the client/config and response
normalization).
src/Web/MeAjudaAi.Web.Customer/app/(main)/buscar/page.tsx (1)

207-207: Redundant non-null assertion ! alongside .filter(Boolean).

s.serviceName! asserts the value is non-null to satisfy TypeScript, but .filter(Boolean) discards falsy values at runtime anyway — the assertion suppresses the type error that the filter correctly handles. Using a type predicate in the filter removes the need for ! and preserves narrowed typing.

♻️ Proposed fix
-                                        services={provider.services.map(s => s.serviceName!).filter(Boolean)}
+                                        services={provider.services
+                                            .map(s => s.serviceName)
+                                            .filter((name): name is string => Boolean(name))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/buscar/page.tsx at line 207, The
mapping currently uses a redundant non-null assertion in
services={provider.services.map(s => s.serviceName!).filter(Boolean)}; remove
the unnecessary `!` and use a type-narrowing predicate so TypeScript knows falsy
values are filtered out — e.g. map to s.serviceName (no !) and then filter with
a type guard like .filter((v): v is string => Boolean(v)) so the resulting array
is correctly typed as string[] for the services prop.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/Modules/Providers/Application/Handlers/Commands/RegisterProviderCommandHandler.cs`:
- Around line 52-53: The provider document type mapping currently treats any
non-Individual as CNPJ; update the logic in RegisterProviderCommandHandler where
docType is derived from command.Type so that EProviderType.Individual and
EProviderType.Freelancer map to EDocumentType.CPF and EProviderType.Company and
EProviderType.Cooperative map to EDocumentType.CNPJ, then construct the
Document(command.DocumentNumber, docType, isPrimary: true) as before to accept
Freelancer CPF numbers correctly.

In
`@src/Modules/Providers/Tests/Unit/Application/Validators/AddDocumentRequestValidatorTests.cs`:
- Around line 126-128: Remove the duplicated assertion call and chain the
error-message assertion onto the single ShouldHaveValidationErrorFor invocation:
replace the two separate calls to result.ShouldHaveValidationErrorFor(x =>
x.DocumentType) with one call followed by .WithErrorMessage(...). Update the
test in AddDocumentRequestValidatorTests (the assertion using the result
variable) so it reads a single ShouldHaveValidationErrorFor(x =>
x.DocumentType).WithErrorMessage("Tipo de documento inválido. Valores aceitos:
None, CPF, CNPJ, RG, CNH, Passport, Other").

In `@src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx`:
- Around line 79-89: Replace the hardcoded English fallback "Unknown" in the
provider tier and status displays with a Portuguese localized value; update the
expressions that use PROVIDER_TIER_LABELS[providerStatus.tier] ?? "Unknown" and
PROVIDER_STATUS_LABELS[providerStatus.status] ?? "Unknown" to return a PT-BR
string (e.g., "Desconhecido" or a i18n lookup like t('common.unknown')). Ensure
you apply this change where providerStatus.tier and providerStatus.status are
used and keep the existing conditional classes for EProviderStatus.Active,
EProviderStatus.Rejected, and EProviderStatus.Suspended unchanged.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/verified-badge.tsx`:
- Around line 26-32: The visible label for the rejected verification branch
currently shows "Suspenso" which conflates verification rejection with provider
suspension; in the VerifiedBadge render branch that checks
EVerificationStatus.Rejected (the component/JSX block that returns the Ban icon
and uses showLabel), change the visible text when showLabel is true from
"Suspenso" to "Rejeitado" while keeping the existing title/tooltip as-is (or
adjust to just "Rejeitado" if desired); update only the text node associated
with the showLabel span to reflect EVerificationStatus.Rejected correctly.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts`:
- Around line 3-11: The ViaCepResponse interface is inaccurate because ViaCEP
can return just { erro: true } (no string fields); change ViaCepResponse to a
discriminated union (e.g. { erro: true } | { erro?: false; cep?: string;
logradouro?: string; complemento?: string; bairro?: string; localidade?: string;
uf?: string }) or make the string fields optional, then update the code in the
useViaCep hook where you cast/assign data (the variable named data) so the
runtime guard (data.erro) properly narrows to the non-error branch instead of
relying on an unsafe cast, and update the exported hook return type to reflect
that those address fields may be undefined (or use the union type) so consumers
get correct types.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts`:
- Around line 46-49: The handling of 204 in fetch-client.ts currently returns an
unsafe "{} as T" from authenticatedFetch which lies about the result shape;
remove the "{} as T" return and instead either (A) change authenticatedFetch's
contract to Promise<T | undefined> and return undefined when response.status ===
204 (then update callers), or (B) add an explicit overload / separate helper
(e.g. authenticatedVoidFetch or an authenticatedFetch<void> overload) for
no-body endpoints and return void/undefined there; update the response.status
=== 204 branch in authenticatedFetch to return undefined (or call the new
void-helper) and adjust calling sites accordingly so no caller assumes a
populated T.
- Around line 51-66: authenticatedFetch currently uses body?: any and suppresses
TypeScript checks with `@ts-ignore` when unwrapping ApiResponse<T>, which can
return data: null and violate the Promise<T> contract; change the request body
parameter from any to a stricter type (e.g., body?: unknown or body?:
Record<string, unknown>) and update callers to validate/serialize as needed,
then remove the `@ts-ignore` lines and add a runtime null-check for ApiResponse<T>
shapes inside authenticatedFetch (the block that checks 'data' in json): if
json.data is null, throw a clear error (or return a typed Result/optional)
rather than returning null so authenticatedFetch<T> always fulfills its
Promise<T> contract; update any tests/call-sites accordingly.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 61-62: The mapping currently hardcodes status:
EProviderStatus.Active and verificationStatus: EVerificationStatus.Verified
which makes every search result appear active and verified; update the mapper
that builds the SearchableProviderDto (the block setting status and
verificationStatus in src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts) to use
safe defaults instead—set verificationStatus to EVerificationStatus.Pending (or
derive from actual source if available) and status to a neutral/default like
EProviderStatus.Pending/Inactive as appropriate, so ProviderCard/VerifiedBadge
reflect real provider state instead of always showing Verified/Active.
- Line 137: The mapping currently substitutes new Date().toISOString() when
dto.createdAt is missing, creating a misleading "now" timestamp; update the
mapping(s) so createdAt is left undefined if absent by replacing the fallback
expression with a direct pass-through (e.g., use dto.createdAt or dto.createdAt
?? undefined) in both mapSearchableProviderToProvider and the other mapping
function that sets createdAt at the shown line (remove the new
Date().toISOString() fallback).

In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts`:
- Around line 56-99: Update the TypeScript enums to precisely match the C#
backend numeric values: change EProviderStatus members to the backend mapping
(PendingBasicInfo=1, PendingDocumentVerification=2, Active=3, Suspended=4,
Rejected=5) so comparisons like provider.status === EProviderStatus.Active
resolve correctly; replace EVerificationStatus with the full backend set and
values (None=0, Pending=1, InProgress=2, Verified=3, Rejected=4, Suspended=5);
set EDocumentType.Other to 99 to match C#; and replace EDocumentStatus with the
Documents module values (Uploaded=1, PendingVerification=2, Verified=3). Locate
and update the enums EProviderStatus, EVerificationStatus, EDocumentType, and
EDocumentStatus in provider.ts to use these exact numeric values.

---

Duplicate comments:
In `@docs/roadmap.md`:
- Line 10: The roadmap contains inconsistent MVP dates: both "28 de Março de
2026" and "14 de Março" appear; decide on the single canonical launch date and
update every occurrence in docs/roadmap.md so they match (replace all instances
of "14 de Março" or "28 de Março de 2026" depending on the chosen date).
Specifically search and replace the strings found in the sprint table section
and the phase summary text (and any other mentions) to ensure the MVP Target,
sprint table, and phase summary all show the same date.
- Around line 41-47: Update all occurrences in docs/roadmap.md to make Sprint
labels consistent: replace any instance that lists "Sprint 8B" as Mobile with
"Sprint 8B - Authentication & Onboarding Flow", ensure "Sprint 8C" is "Sprint 8C
- Mobile App (React Native + Expo)" and mark "Sprint 8D" as cancelled (keep "🚫
Sprint 8D - Admin Portal Migration (Manter Blazor)"). Search for the specific
strings "Sprint 8B", "Sprint 8C", and "Sprint 8D" (and any phase/priority
summaries mentioning them) and harmonize their descriptions so the Phase
summary, priorities, and sprint list all match the new labels.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx`:
- Around line 5-62: Avatar currently spreads ...rest onto the wrapper <div>, so
image-specific attributes (loading, onError, referrerPolicy, sizes, decoding,
fetchPriority, etc.) never reach the <Image>; change Avatar to forward image
props to <Image> instead: update AvatarProps to separate container props from
image props (e.g., keep existing props but add imageProps?:
ComponentProps<"img"> or rename incoming rest to containerProps and imageProps),
stop spreading ...rest on the div, spread only containerProps there and pass
imageProps (or the extracted image-specific keys from rest) into the <Image>
element along with src, alt, width/height and its className; ensure types are
updated (AvatarProps and any usages) and merge className appropriately for both
wrapper and Image.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 60-63: The magic-number placeholders were replaced with enums;
update the mapping in the provider DTO->model code to use the enum constants
EProviderType.Individual, EProviderStatus.Active, EVerificationStatus.Verified,
and EProviderTier.Standard (already present) and ensure you actually map a real
provider type when available—fall back to EProviderType.Individual only if the
DTO has no type; remove or update the inline comment "// Default or map if
available" so it reflects this fallback behavior in the function that constructs
the provider model.

---

Nitpick comments:
In `@src/Bootstrapper/MeAjudaAi.ApiService/Extensions/SecurityExtensions.cs`:
- Around line 455-464: Extract the literal "provider-registration" into a
constant (e.g., add public const string ProviderRegistration =
"provider-registration" in RateLimitPolicies) and use that constant when calling
options.AddPolicy and when requiring the policy in
ProviderRegistrationEndpoints; also stop using the hardcoded "anonymous"
fallback for the partition key — replace partitionKey:
context.Connection.RemoteIpAddress?.ToString() ?? "anonymous" with a fallback
that uses a validated X-Forwarded-For header when present
(sanitized/whitelisted) or, if absent, a per-connection identifier such as
context.Connection.Id to avoid collapsing all null-IP requests into one shared
bucket.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/buscar/page.tsx:
- Line 207: The mapping currently uses a redundant non-null assertion in
services={provider.services.map(s => s.serviceName!).filter(Boolean)}; remove
the unnecessary `!` and use a type-narrowing predicate so TypeScript knows falsy
values are filtered out — e.g. map to s.serviceName (no !) and then filter with
a type guard like .filter((v): v is string => Boolean(v)) so the resulting array
is correctly typed as string[] for the services prop.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Line 20: The z.preprocess for the `type` field currently coerces any string to
EProviderType.Individual which is incorrect; update the preprocessing in the
schema that defines `type` so it either (a) maps known string values to the
corresponding EProviderType members (e.g., convert "Individual"/"Company"
strings to EProviderType.Individual/EProviderType.Company) and returns the
original value for non-string inputs, or (b) remove the coercion entirely and
use z.nativeEnum(EProviderType).optional() so strings are not silently
converted; ensure references like providerData.type and the enum EProviderType
are used to validate/convert only well-known values.
- Line 198: Replace the falsy-check fallback using || with the nullish
coalescing operator so numeric enum values like 0 aren't treated as absent:
update the VerifiedBadge prop expression that uses
providerData.verificationStatus || EVerificationStatus.Pending to use
providerData.verificationStatus ?? EVerificationStatus.Pending instead, leaving
the VerifiedBadge component and the enum EVerificationStatus unchanged.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx`:
- Line 10: The import for EProviderType is inconsistent—change the import source
from "@/types/provider" to "@/types/api/provider" so it matches other files
(e.g., prestador/[id]/page.tsx, prestador/page.tsx, verified-badge.tsx); update
the import statement that references EProviderType in the
cadastro/prestador/page.tsx module to import EProviderType from
"@/types/api/provider" to avoid divergence.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts`:
- Around line 36-39: The onSuccess callback in the use-update-provider-profile
hook declares a parameter named data that is never used; either remove the
parameter or rename it to _data to satisfy the linter. Update the onSuccess
declaration in use-update-provider-profile.ts (the onSuccess handler that calls
queryClient.invalidateQueries and queryClient.invalidateQueries for
"providerStatus" and "myProviderProfile") so it no longer introduces an unused
variable.
- Around line 3-6: Remove the unused imports `client` and `ApiResponse` from the
top of use-update-provider-profile.ts; keep the imports needed by the hook
(e.g., `authenticatedFetch`, `ProviderDto`, `ContactInfoDto`, `AddressDto`) and
ensure no other references to `client` or `ApiResponse` remain in the file (the
hook delegates to `authenticatedFetch` which handles the client/config and
response normalization).

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts`:
- Line 28: The fetch URL in the useViaCep hook is hardcoded; extract the ViaCEP
base URL into a named constant (e.g., VIACEP_BASE_URL) and use it when building
the request (replace the inline string in the fetch call that uses cleanCep).
Update any other occurrences in useViaCep to reference the constant so tests or
environments can override it easily and keep behavior unchanged.
- Around line 41-43: The catch block in use-via-cep.ts declares an unused
variable err; remove the unused binding by changing the catch to a no-binding
catch (catch { ... }) and keep the existing setError("Falha ao consultar CEP");
return null; flow, or if you need error details, type the parameter as unknown
(catch (err: unknown)) and add a narrow/checked handling (e.g., extract message
safely) before logging or using it; update the catch around the code in the
useViaCep hook where setError is called.
- Around line 17-47: The fetchAddress useCallback can leave stale state from
overlapping requests and update state after unmount; fix it by adding an
AbortController stored in a ref (add useRef to imports) and, inside
fetchAddress, abort any previous controller.current before creating a new
controller, pass controller.signal to fetch, and set controller.current =
controller; in catch, ignore AbortError (don't call setError) and in finally
only call setIsLoading(false) when controller.signal.aborted is false (or when
the current controller still matches the one used for this fetch) to avoid
updating state from stale or unmounted requests; also add a useEffect cleanup
that aborts controller.current on unmount.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (4)
tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs (1)

56-66: Consider adding test coverage for new provider-tier features.

The new provider-tier functionality introduced in UserRoles.cs lacks corresponding test coverage:

  1. RoleConstants_ShouldHaveExpectedValues doesn't verify the new provider-tier constants (ProviderStandard, ProviderSilver, ProviderGold, ProviderPlatinum).
  2. IsValidRole_WithValidRole_ShouldReturnTrue InlineData (lines 73-79) doesn't include the new provider-tier role strings.
  3. No #region IsProviderRole Tests analogous to IsAdminRole Tests.
  4. No ProviderRoles_ShouldBeSubsetOfAllRoles consistency test.

Would you like me to generate the missing test methods for the new provider-tier roles?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
56 - 66, The tests are missing coverage for the new provider-tier roles: update
RoleConstants_ShouldHaveExpectedValues to assert UserRoles.ProviderStandard,
ProviderSilver, ProviderGold, and ProviderPlatinum equal their expected strings;
add the provider-tier strings to the InlineData list in
IsValidRole_WithValidRole_ShouldReturnTrue so those cases return true; add a new
region "IsProviderRole Tests" with unit tests for the IsProviderRole method
(valid provider roles -> true, non-provider roles -> false, null/empty ->
false); and add a ProviderRoles_ShouldBeSubsetOfAllRoles test that verifies the
UserRoles.ProviderRoles collection is a subset of UserRoles.AllRoles to ensure
consistency.
src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts (1)

52-58: Mock businessProfile uses as any cast.

The partial mock object is cast to any to bypass type checking. If the BusinessProfileDto interface changes, this won't surface compile-time errors.

♻️ Type the partial mock explicitly
         businessProfile: {
             legalName: dto.name ?? '',
             contactInfo: { email: '' },
             primaryAddress: {
                 street: '', number: '', neighborhood: '', city: dto.city ?? '', state: dto.state ?? '', zipCode: '', country: 'Brasil'
             }
-        } as any, // Partial mock since Search doesn't return full profile
+        } as BusinessProfileDto, // Ensure import and fill required fields

Or define a minimal createMockBusinessProfile helper that returns a properly-typed partial.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts` around lines 52 - 58,
Replace the unsafe "as any" cast on the partial businessProfile in mappers.ts by
returning a properly-typed partial: either create a small helper function
createMockBusinessProfile(dto) that builds and returns a
Partial<BusinessProfileDto> (populating legalName, contactInfo.email,
primaryAddress fields) and use that instead of the inline object, or explicitly
type the inline object as Partial<BusinessProfileDto> (not any) so future
changes to BusinessProfileDto surface compile-time errors; update references to
businessProfile in the mapper to accept Partial<BusinessProfileDto> if needed.
src/Web/MeAjudaAi.Web.Customer/components/ui/verified-badge.tsx (1)

12-44: Missing handling for InProgress, Suspended, and None statuses.

The component only renders badges for Verified, Rejected, and Pending. With EVerificationStatus now including None=0, InProgress=2, and Suspended=5, providers in these states will show no badge (returns null). Consider whether InProgress should display a distinct indicator or fall through to Pending styling.

♻️ Optional: Handle InProgress status
+    if (status === EVerificationStatus.InProgress) {
+        return (
+            <div className={cn("flex items-center gap-1 text-blue-400", className)} title="Verificação em Andamento">
+                <BadgeCheck size={iconSize} className="text-blue-400 animate-pulse" />
+                {showLabel && <span className="font-medium text-sm">Em Análise</span>}
+            </div>
+        );
+    }
+
     if (status === EVerificationStatus.Pending) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/verified-badge.tsx` around lines
12 - 44, VerifiedBadge currently only handles EVerificationStatus.Verified,
Rejected, and Pending so statuses like InProgress, Suspended, and None render
nothing; update VerifiedBadge to handle EVerificationStatus.InProgress (either
reuse Pending styling or add a distinct look), EVerificationStatus.Suspended
(treat same as Rejected or add separate styling), and EVerificationStatus.None
(explicitly return null or a neutral/hidden state) by adding branches for these
enum values in the VerifiedBadge function and adjusting
icon/text/title/className appropriately (refer to VerifiedBadge,
EVerificationStatus, and the existing branches for Verified/Rejected/Pending to
copy patterns).
src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx (1)

88-117: Type selector buttons may overflow on smaller viewports.

The four buttons in a horizontal flex gap-4 layout will likely wrap awkwardly or overflow on mobile screens. Consider a responsive grid or wrapping strategy.

♻️ Suggested responsive layout
                                 <FormControl>
-                                    <div className="flex gap-4">
+                                    <div className="grid grid-cols-2 gap-2 sm:flex sm:gap-4">
                                         <Button
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx` around lines
88 - 117, The horizontal button group using <div className="flex gap-4"> with
<Button> instances (tied to field and EProviderType values) can overflow on
small viewports; change the layout to be responsive by enabling wrapping and
stacking on narrow screens — e.g., replace the container's fixed flex with a
responsive pattern (flex flex-wrap gap-4 and use responsive child sizing like
w-full sm:w-auto or a responsive grid such as grid grid-cols-1 sm:grid-cols-2
gap-4) so the <Button> elements don't overflow and the active selection logic
(field.onChange(EProviderType.*)) remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/Modules/Providers/Application/Handlers/Commands/RegisterProviderCommandHandler.cs`:
- Around line 29-43: The ContactInfo constructor can throw ArgumentException for
malformed emails which escapes the DomainException handler in
RegisterProviderCommandHandler; either validate the email format before creating
ContactInfo in RegisterProviderCommandHandler.Handle (e.g., using a reusable
EmailValidator or regex) and return a BadRequest for invalid formats, or expand
the existing exception handling block to catch ArgumentException alongside
DomainException (add ArgumentException to the catch that currently handles
DomainException) so malformed-email errors map to 400 instead of falling through
to the generic Exception handler.
- Around line 65-74: Replace the brittle string-match catch in
RegisterProviderCommandHandler with the project's
PostgreSqlExceptionProcessor-based handling: remove the DbUpdateException catch
that checks ex.InnerException?.Message.Contains("ix_providers_user_id") and
either let GlobalExceptionHandler handle the constraint violation or invoke
PostgreSqlExceptionProcessor.ProcessException(ex) and handle a returned
UniqueConstraintException accordingly (e.g., resolve existing via
providerRepository.GetByUserIdAsync(command.UserId) if needed, or rethrow/return
a conflict result). Use the PostgreSqlExceptionProcessor and
UniqueConstraintException types rather than matching error text to detect
unique-constraint violations.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 38-46: The verificationStatus z.preprocess currently maps only
'verified' and 'rejected' (defaulting all other strings to Pending), which
misses other API string values; update the preprocessing in verificationStatus
to explicitly map 'inprogress', 'suspended', and 'none' (and any other expected
string variants/casing) to their corresponding EVerificationStatus enum members
using EVerificationStatus.InProgress, EVerificationStatus.Suspended,
EVerificationStatus.None (or the exact enum member names in
EVerificationStatus), while preserving existing handling for 'verified' and
'rejected' and leaving non-string values to pass through to
z.nativeEnum(EVerificationStatus).optional().nullable().

In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx`:
- Around line 35-44: The useEffect is depending on the entire form object which
is unstable and causes extra re-renders; destructure the stable methods from the
form (e.g. const { getValues, setValue } = form) and update the effect to use
those instead of form, then change the dependency array to [session, getValues,
setValue] and keep the logic that calls getValues("name")/getValues("email") and
setValue(...) inside the effect (references: useEffect, session, form.getValues,
form.setValue).

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts`:
- Around line 32-41: In fetchAddress (the useCallback that defines
fetchAddress), move the "Cancel previous request" logic (checking and calling
abortControllerRef.current.abort()) and any stale-error clearing (e.g.,
setError(null) or similar) to occur before computing/validating cleanCep and the
early return; this ensures any in-flight request is aborted and stale errors
cleared even when the cleaned CEP length is invalid, while keeping the existing
validation (cleanCep.length !== 8) and subsequent behavior intact.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 77-79: The generated SDK type
MeAjudaIModulesProvidersApplicationDtosProviderDto is missing properties
(services, averageRating, reviewCount, tier) and causing many `@ts-ignore` uses
around businessProfile and contactInfo in mappers.ts; fix by regenerating the
SDK types from the updated OpenAPI spec so the DTO matches the real API, or add
a local interface (e.g. ProviderDtoExact) that includes services: ServiceDto[],
averageRating: number, reviewCount: number, tier: string and the
optional/required shapes for businessProfile and contactInfo, replace usages of
MeAjudaIModulesProvidersApplicationDtosProviderDto (or cast to the new
interface) and remove the `@ts-ignore` lines, and update any mapping functions in
this file (look for contactInfo and businessProfile references) to use the
properly typed properties.

In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts`:
- Around line 98-102: The frontend EDocumentStatus enum is missing backend
states which will break mapping for statuses 4 and 5; update the EDocumentStatus
enum (symbol: EDocumentStatus) to include Rejected = 4 and Failed = 5 so it
matches the backend contract and correctly handles API responses with those
statuses.

---

Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts`:
- Around line 46-49: The code currently returns "{} as T" for 204 No Content in
the fetch client (the branch checking response.status === 204); change this to
return undefined instead and update the function's type signature to allow
undefined (e.g., make authenticatedFetch<T> return Promise<T | undefined>) or
add a dedicated void variant (e.g., authenticatedVoidFetch) for 204 endpoints;
ensure all call sites (authenticatedFetch / authenticatedVoidFetch) are updated
to handle undefined or use the void variant accordingly rather than fabricating
an empty object.
- Around line 58-65: The current ApiResponse<T> handling in fetch-client.ts only
throws when apiRes.data === null && apiRes.isSuccess === false, but ApiResponse
allows data to be null and callers (e.g., use-update-provider-profile,
use-register-provider) expect non-nullable T; update the block that handles
ApiResponse<T> (the code using apiRes = json as ApiResponse<T>) to always throw
if apiRes.data === null (regardless of apiRes.isSuccess) and include
apiRes.message in the thrown Error to preserve context; keep returning
apiRes.data as T only when it is non-null.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Line 66: The mapper currently fabricates a timestamp by always setting
createdAt: new Date().toISOString(); modify the mapping in
src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts so that the createdAt field
uses the source value when present (e.g., parse/format source.createdAt) and
otherwise returns null/undefined (or omits the field) instead of generating the
current time; update the mapping function (e.g., mapSearchResult or the mapper
that sets createdAt) to perform a presence check and proper conversion to ISO
only when an original timestamp exists.

---

Nitpick comments:
In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx`:
- Around line 88-117: The horizontal button group using <div className="flex
gap-4"> with <Button> instances (tied to field and EProviderType values) can
overflow on small viewports; change the layout to be responsive by enabling
wrapping and stacking on narrow screens — e.g., replace the container's fixed
flex with a responsive pattern (flex flex-wrap gap-4 and use responsive child
sizing like w-full sm:w-auto or a responsive grid such as grid grid-cols-1
sm:grid-cols-2 gap-4) so the <Button> elements don't overflow and the active
selection logic (field.onChange(EProviderType.*)) remains unchanged.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/verified-badge.tsx`:
- Around line 12-44: VerifiedBadge currently only handles
EVerificationStatus.Verified, Rejected, and Pending so statuses like InProgress,
Suspended, and None render nothing; update VerifiedBadge to handle
EVerificationStatus.InProgress (either reuse Pending styling or add a distinct
look), EVerificationStatus.Suspended (treat same as Rejected or add separate
styling), and EVerificationStatus.None (explicitly return null or a
neutral/hidden state) by adding branches for these enum values in the
VerifiedBadge function and adjusting icon/text/title/className appropriately
(refer to VerifiedBadge, EVerificationStatus, and the existing branches for
Verified/Rejected/Pending to copy patterns).

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 52-58: Replace the unsafe "as any" cast on the partial
businessProfile in mappers.ts by returning a properly-typed partial: either
create a small helper function createMockBusinessProfile(dto) that builds and
returns a Partial<BusinessProfileDto> (populating legalName, contactInfo.email,
primaryAddress fields) and use that instead of the inline object, or explicitly
type the inline object as Partial<BusinessProfileDto> (not any) so future
changes to BusinessProfileDto surface compile-time errors; update references to
businessProfile in the mapper to accept Partial<BusinessProfileDto> if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs`:
- Around line 56-66: The tests are missing coverage for the new provider-tier
roles: update RoleConstants_ShouldHaveExpectedValues to assert
UserRoles.ProviderStandard, ProviderSilver, ProviderGold, and ProviderPlatinum
equal their expected strings; add the provider-tier strings to the InlineData
list in IsValidRole_WithValidRole_ShouldReturnTrue so those cases return true;
add a new region "IsProviderRole Tests" with unit tests for the IsProviderRole
method (valid provider roles -> true, non-provider roles -> false, null/empty ->
false); and add a ProviderRoles_ShouldBeSubsetOfAllRoles test that verifies the
UserRoles.ProviderRoles collection is a subset of UserRoles.AllRoles to ensure
consistency.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Web/MeAjudaAi.Web.Customer/components/ui/verified-badge.tsx (1)

35-44: ⚠️ Potential issue | 🟡 Minor

Handle InProgress/Suspended verification states.

EVerificationStatus.InProgress and Suspended currently render nothing, which can hide meaningful status.

🛠️ Suggested handling
-    if (status === EVerificationStatus.Pending) {
+    if (status === EVerificationStatus.InProgress) {
+        return (
+            <div className={cn("flex items-center gap-1 text-amber-600", className)} title="Em Análise">
+                <BadgeCheck size={iconSize} className="text-amber-600" />
+                {showLabel && <span className="font-medium text-sm">Em análise</span>}
+            </div>
+        );
+    }
+
+    if (status === EVerificationStatus.Suspended) {
+        return (
+            <div className={cn("flex items-center gap-1 text-red-500", className)} title="Prestador Suspenso">
+                <Ban size={iconSize} />
+                {showLabel && <span className="font-medium text-sm">Suspenso</span>}
+            </div>
+        );
+    }
+
+    if (status === EVerificationStatus.Pending) {
         return (
             <div className={cn("flex items-center gap-1 text-yellow-600", className)} title="Pendente de Verificação">
                 <BadgeCheck size={iconSize} className="text-yellow-600" />
                 {showLabel && <span className="font-medium text-sm">Pendente</span>}
             </div>
         );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/verified-badge.tsx` around lines
35 - 44, The component currently only renders when status ===
EVerificationStatus.Pending and returns null for EVerificationStatus.InProgress
and EVerificationStatus.Suspended; update the verified-badge component to handle
those cases by adding branches for EVerificationStatus.InProgress and
EVerificationStatus.Suspended (use the existing props status, showLabel,
iconSize, className and icon components like BadgeCheck or swap to appropriate
icons for each state) and render a distinct icon, color class and label for each
(e.g., InProgress -> spinner/clock icon with blue/gray text and label "Em
andamento"; Suspended -> warning/x icon with red/gray text and label "Suspenso")
instead of returning null so those statuses are visible. Ensure to reuse the
same JSX structure (div with cn(...) and conditional showLabel) and only change
icon, text and color classes for each status.
🧹 Nitpick comments (4)
src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts (1)

38-44: Consider a typed error class for attaching status.

Using (err as any).status works but bypasses type checking. A small custom error class improves downstream handling:

♻️ Suggested improvement
// At the top of the file or in a shared types module
class ApiError extends Error {
    constructor(message: string, public status: number) {
        super(message);
        this.name = 'ApiError';
    }
}

// In the error handling block
if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    const userMessage = error.message || error.detail || `Request failed: ${response.statusText}`;
    throw new ApiError(userMessage, response.status);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts` around lines 38 - 44,
Create a small typed error class (e.g., ApiError extends Error with a public
status: number property and name = 'ApiError') in this module (or a shared types
module) and replace the untyped cast in the response error branch inside the
fetch-client error handling: instead of setting (err as any).status, throw new
ApiError(userMessage, response.status) where userMessage is computed from the
parsed error or response.statusText; update any callers if they currently rely
on the loose shape to expect ApiError for typed status access.
tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs (2)

15-28: Add test coverage for the new provider-tier roles beyond AllRoles.

With the provider tiers now part of AllRoles, consider extending RoleConstants_ShouldHaveExpectedValues and the IsValidRole test cases to include ProviderStandard, ProviderSilver, ProviderGold, and ProviderPlatinum so regressions in the new role surface are caught early.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
15 - 28, Extend the existing tests to explicitly assert the new provider-tier
roles: add assertions for ProviderStandard, ProviderSilver, ProviderGold, and
ProviderPlatinum in the RoleConstants_ShouldHaveExpectedValues test (or
equivalent that verifies role constants) and include those four values in the
IsValidRole test cases that validate valid/invalid roles; update any test data
arrays or parameterized test inputs that enumerate roles (e.g., the AllRoles
containment check and IsValidRole parameter list) so the new provider-tier
constants are individually asserted and validated.

14-16: Translate C# comments to Portuguese to comply with the language policy.

Comments like // Assert should be in Portuguese across this file to align with the repository’s language rules.

Based on learnings: Adopt a language policy across the codebase: 1) All logs must be in English, 2) Comments and XML summaries must be in Portuguese, 3) User-facing validation messages and API responses must be in Portuguese.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
14 - 16, Replace inline C# comments in this test file (e.g., the "// Assert"
comment near the assertions that reference UserRoles.AllRoles and any other
single-line comments) with Portuguese equivalents (e.g., "// Asserção" or "//
Verificar"), and update any XML doc summaries in the same file to Portuguese as
well; leave code, identifiers (UserRoles, AllRoles, test method names) and
logging text untouched and continue to keep logs in English per policy.
src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts (1)

56-102: Add provider and document enums to Shared.Contracts to prevent duplication and drift.

These enums duplicate backend domain definitions (e.g., src/Modules/Providers/Domain/Enums/EProviderType.cs). Enums should be defined in Contracts layer and imported here, aligning Web with the backend source of truth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts` around lines 56 - 102,
The enums EProviderType, EProviderStatus, EVerificationStatus, EProviderTier,
EDocumentType, and EDocumentStatus defined in this file should be removed and
replaced by imports from the centralized Contracts package: create or reuse
equivalent enums in the Shared.Contracts/Contracts layer (matching the backend
domain enums), export them there, then update this file to import those enums
and re-export/use them instead of redefining; ensure names (EProviderType,
EProviderStatus, EVerificationStatus, EProviderTier, EDocumentType,
EDocumentStatus) match exactly so existing usages compile and remove the
duplicated enum declarations from provider.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/Web/MeAjudaAi.Web.Customer/components/ui/verified-badge.tsx`:
- Around line 35-44: The component currently only renders when status ===
EVerificationStatus.Pending and returns null for EVerificationStatus.InProgress
and EVerificationStatus.Suspended; update the verified-badge component to handle
those cases by adding branches for EVerificationStatus.InProgress and
EVerificationStatus.Suspended (use the existing props status, showLabel,
iconSize, className and icon components like BadgeCheck or swap to appropriate
icons for each state) and render a distinct icon, color class and label for each
(e.g., InProgress -> spinner/clock icon with blue/gray text and label "Em
andamento"; Suspended -> warning/x icon with red/gray text and label "Suspenso")
instead of returning null so those statuses are visible. Ensure to reuse the
same JSX structure (div with cn(...) and conditional showLabel) and only change
icon, text and color classes for each status.

---

Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts`:
- Around line 32-41: The early-return when CEP is invalid currently skips
cancelling any in-flight request and leaves stale error state; inside
fetchAddress, before returning null when cleanCep.length !== 8, abort any
existing request via abortControllerRef.current.abort() and set
abortControllerRef.current = null, and also clear the component's via-CEP error
state (call the appropriate setter, e.g., setViaCepError(null) or setError(null)
used in this hook) so no stale error remains.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts`:
- Around line 46-49: The code returns "{} as T" for 204 responses which fakes
the type; instead return a real empty value and update the function signature to
reflect it (e.g., return undefined or null). In fetch-client.ts, modify the
response handling for "response.status === 204" to "return undefined" (or
"null") and change the fetch wrapper's generic return type (the function that
processes the Response in this file, e.g., the fetchClient/fetchJson handler)
from Promise<T> to Promise<T | undefined> (or Promise<T | null>), then update
callers to handle the new nullable/undefined return or accept an injectable
default value.
- Around line 54-56: The current Result<T> unwrapping (the if branch checking
"json && typeof json === 'object' && 'value' in json") must also verify that
(json as any).value is not null or undefined; update that branch in
fetch-client.ts to check value !== null && value !== undefined before returning
it, and if the check fails throw or reject with a clear error (e.g., "Response
contained null/undefined value for expected Result<T>") so the function's
Promise<T> contract isn't violated; refer to the existing conditional and the
returned expression (json as any).value to locate and change the code.
- Around line 58-65: The null guard for ApiResponse<T> is too permissive; update
the logic in fetch-client.ts where you handle ApiResponse to throw whenever
apiRes.data === null (regardless of apiRes.isSuccess), using apiRes.message ||
"API interaction failed" for the Error message, so the function never returns
null as T; locate the ApiResponse<T> handling block (the const apiRes = json as
ApiResponse<T> section) and replace the current conditional (apiRes.data ===
null && apiRes.isSuccess === false) with a check that throws on apiRes.data ===
null.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 60-67: The mapping currently fabricates a createdAt value with
createdAt: new Date().toISOString(); instead, stop creating phantom timestamps:
map createdAt from the source payload when present (use the original field if
available) or leave createdAt undefined/null when missing, and do not assign a
default timestamp; update the mapper that produces the object (the block using
EProviderType, EProviderStatus, EVerificationStatus, EProviderTier, documents,
qualifications) and ensure the target type/interface allows createdAt to be
optional so no fake date is emitted for search results.
- Around line 73-135: The mapper mapApiProviderToProvider is littered with
`@ts-ignore` because the generated DTO types (e.g., ProviderDto, businessProfile,
contactInfo, address, services and fields like dto.type/dto.status) are out of
sync with the actual API response; fix by either regenerating the SDK types from
the latest OpenAPI spec so ProviderDto and related types (BusinessProfileDto,
ContactInfoDto, ProviderServiceDto, etc.) match the runtime shape, or declare a
precise local interface that matches the API response and replace the `@ts-ignore`
usages by typing dto and intermediate vars (businessProfile, contactInfo,
address, services) correctly and removing all ignores; ensure fields mapped
(email, phoneNumber, serviceId, price, currency, serviceName, type/status enums)
have correct names/types and convert/cast only after validating those types in
mapApiProviderToProvider.

In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts`:
- Around line 98-102: EDocumentStatus enum in provider.ts currently defines
Uploaded/PendingVerification/Verified and may be missing backend states like
Rejected/Failed; confirm the backend EDocumentStatus values and add any missing
members (e.g., Rejected, Failed) to the EDocumentStatus enum in this file with
the exact numeric values used by the backend, then update any switch/conditional
logic that consumes EDocumentStatus (search for usages of EDocumentStatus) and
adjust UI/serialization handling/tests to account for the new statuses.

---

Nitpick comments:
In `@src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts`:
- Around line 38-44: Create a small typed error class (e.g., ApiError extends
Error with a public status: number property and name = 'ApiError') in this
module (or a shared types module) and replace the untyped cast in the response
error branch inside the fetch-client error handling: instead of setting (err as
any).status, throw new ApiError(userMessage, response.status) where userMessage
is computed from the parsed error or response.statusText; update any callers if
they currently rely on the loose shape to expect ApiError for typed status
access.

In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts`:
- Around line 56-102: The enums EProviderType, EProviderStatus,
EVerificationStatus, EProviderTier, EDocumentType, and EDocumentStatus defined
in this file should be removed and replaced by imports from the centralized
Contracts package: create or reuse equivalent enums in the
Shared.Contracts/Contracts layer (matching the backend domain enums), export
them there, then update this file to import those enums and re-export/use them
instead of redefining; ensure names (EProviderType, EProviderStatus,
EVerificationStatus, EProviderTier, EDocumentType, EDocumentStatus) match
exactly so existing usages compile and remove the duplicated enum declarations
from provider.ts.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs`:
- Around line 15-28: Extend the existing tests to explicitly assert the new
provider-tier roles: add assertions for ProviderStandard, ProviderSilver,
ProviderGold, and ProviderPlatinum in the RoleConstants_ShouldHaveExpectedValues
test (or equivalent that verifies role constants) and include those four values
in the IsValidRole test cases that validate valid/invalid roles; update any test
data arrays or parameterized test inputs that enumerate roles (e.g., the
AllRoles containment check and IsValidRole parameter list) so the new
provider-tier constants are individually asserted and validated.
- Around line 14-16: Replace inline C# comments in this test file (e.g., the "//
Assert" comment near the assertions that reference UserRoles.AllRoles and any
other single-line comments) with Portuguese equivalents (e.g., "// Asserção" or
"// Verificar"), and update any XML doc summaries in the same file to Portuguese
as well; leave code, identifiers (UserRoles, AllRoles, test method names) and
logging text untouched and continue to keep logs in English per policy.

… API mappers, backend command, and user role tests.
@frigini
Copy link
Owner Author

frigini commented Feb 19, 2026

@coderabbitai resume

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

✅ Actions performed

Reviews resumed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (8)
tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs (2)

76-91: New provider roles missing from IsValidRole_WithValidRole_ShouldReturnTrue.

The four new provider roles (provider-standard, provider-silver, provider-gold, provider-platinum) are part of AllRoles and should return true from IsValidRole, but they are not covered by this theory. The existing IsProviderRole_WithProviderRole_ShouldReturnTrue does not close this gap since it only exercises IsProviderRole, not IsValidRole.

✅ Proposed fix — extend InlineData
 [InlineData("customer")]
+[InlineData("provider-standard")]
+[InlineData("provider-silver")]
+[InlineData("provider-gold")]
+[InlineData("provider-platinum")]
 public void IsValidRole_WithValidRole_ShouldReturnTrue(string role)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
76 - 91, The test IsValidRole_WithValidRole_ShouldReturnTrue is missing the four
new provider roles; update its InlineData to include "provider-standard",
"provider-silver", "provider-gold", and "provider-platinum" so that
UserRoles.IsValidRole(role) is exercised for those values as well, ensuring the
test covers all entries in UserRoles.AllRoles.

204-252: IsProviderRole lacks a case-insensitive theory test.

IsValidRole (lines 93-105) and IsAdminRole (lines 152-163) both have a dedicated _WithValidRoleDifferentCase_ShouldReturnTrue theory that exercises OrdinalIgnoreCase behavior. IsProviderRole is implemented with the same comparer (from src/Shared/Utilities/UserRoles.cs line 141) but has no equivalent coverage.

✅ Proposed addition — case-insensitive theory for IsProviderRole
+[Theory]
+[InlineData("PROVIDER-STANDARD")]
+[InlineData("Provider-Silver")]
+[InlineData("PROVIDER-GOLD")]
+public void IsProviderRole_WithProviderRoleDifferentCase_ShouldReturnTrue(string role)
+{
+    // Ação
+    var result = UserRoles.IsProviderRole(role);
+
+    // Asserção
+    result.Should().BeTrue();
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
204 - 252, Add a case-insensitive theory for UserRoles.IsProviderRole similar to
the existing tests for IsValidRole and IsAdminRole: create a new Theory named
IsProviderRole_WithValidRoleDifferentCase_ShouldReturnTrue that passes provider
role strings with varying case (e.g., "Provider-Standard", "PROVIDER-SILVER",
"provider-GOLD", "PrOvIdEr-PlAtInUm") and asserts UserRoles.IsProviderRole(...)
returns true; reference the UserRoles.IsProviderRole method and reuse the test
naming pattern used by IsValidRole and IsAdminRole to verify OrdinalIgnoreCase
behavior.
src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx (1)

60-61: Redundant as number cast.

sizePx[size] already resolves to number (the object's value type), so the as number assertion adds no type safety.

♻️ Proposed cleanup
-                    width={sizePx[size] as number}
-                    height={sizePx[size] as number}
+                    width={sizePx[size]}
+                    height={sizePx[size]}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx` around lines 60 -
61, In Avatar (avatar.tsx) the props width={sizePx[size] as number} and
height={sizePx[size] as number} use a redundant type assertion; remove the "as
number" casts and pass width={sizePx[size]} and height={sizePx[size]} directly
(ensure the sizePx constant/lookup is correctly typed as mapping to number so
TypeScript still infers number for sizePx and size).
src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts (1)

75-75: Optional: replace console.error with structured logging.

console.error in a production hook is noisy and uncontrolled. Consider wiring into whatever logging/observability layer the project uses, or at least guarding it behind a process.env.NODE_ENV !== 'production' check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts` at line 75, The
console.error(err) call inside the use-via-cep hook should be replaced with the
project's structured logging or at minimum guarded in non-production; locate the
error handling in the useViaCep hook (the function where console.error(err) is
used) and either call the shared logger (e.g., logger.error with a contextual
message and the err object) or wrap the console.error call with a check like
process.env.NODE_ENV !== 'production' so that logs in production use the
centralized observability layer instead of console.error.
src/Modules/Providers/Application/Handlers/Commands/RegisterProviderCommandHandler.cs (1)

67-80: throw processedEx on line 80 silently exits the Result<ProviderDto> contract for non-unique-constraint DB errors.

When PostgreSqlExceptionProcessor.ProcessException(ex) returns anything other than UniqueConstraintException (FK violation, check constraint, etc.), or when existing is null despite a unique constraint hit, the re-thrown exception propagates out of HandleAsync uncaught. The sibling catch (Exception ex) at line 87 cannot intercept exceptions thrown from another catch block in C#—only middleware (GlobalExceptionHandler) will catch them. This is intentional given the codebase's pattern, but the inconsistency means callers of this method get an exception instead of a Result for unexpected DB errors.

Consider wrapping in a final Result.Failure or explicitly documenting the reliance on middleware for these paths:

♻️ Optional: make the handler fully Result-returning for DB errors
         catch (DbUpdateException ex)
         {
             var processedEx = PostgreSqlExceptionProcessor.ProcessException(ex);
 
             if (processedEx is UniqueConstraintException)
             {
                 logger.LogWarning(ex, "Duplicate provider registration attempt for user {UserId}", command.UserId);
                 var existing = await providerRepository.GetByUserIdAsync(command.UserId, cancellationToken);
                 if (existing is not null)
                 {
                     return Result<ProviderDto>.Success(existing.ToDto());
                 }
             }
-            throw processedEx;
+            logger.LogError(processedEx, "Database error in RegisterProviderCommand for user {UserId}", command.UserId);
+            return Result<ProviderDto>.Failure(new Error("Erro inesperado ao registrar prestador.", 500));
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Providers/Application/Handlers/Commands/RegisterProviderCommandHandler.cs`
around lines 67 - 80, In RegisterProviderCommandHandler.HandleAsync, do not
re-throw the processed exception from
PostgreSqlExceptionProcessor.ProcessException(ex); instead handle
non-UniqueConstraintException and the case where UniqueConstraintException
yields a null existing provider by logging the error (use logger) and returning
a Result<ProviderDto>.Failure with an appropriate error message/details so the
method always returns a Result; keep the existing branch that returns
Result<ProviderDto>.Success(existing.ToDto()) when existing is found, but
replace the throw processedEx and the null path after
providerRepository.GetByUserIdAsync with calls to Result<ProviderDto>.Failure
(including processedEx.Message or a mapped error code) to preserve the Result
contract.
src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx (2)

20-28: type preprocess only handles numeric strings, unlike verificationStatus which maps named strings.

The verificationStatus preprocess (lines 38-49) maps named strings like 'verified', 'rejected', etc. to enum values. But the type preprocess only does parseInt for numeric strings — if the API returns "Individual" or "Company" as a string, it will produce NaN, pass through as the raw string, and fail z.nativeEnum(EProviderType) validation.

Consider adding named string mapping for consistency:

Proposed fix
     type: z.preprocess((val) => {
         if (typeof val === 'string') {
-            // Basic mapping from string to Enum if needed, or rely on nativeEnum to handle values
-            // If val comes as "1", "2" string, we might need to parse int.
             const parsed = parseInt(val, 10);
             if (!isNaN(parsed)) return parsed;
+            const lower = val.toLowerCase();
+            if (lower === 'individual') return EProviderType.Individual;
+            if (lower === 'company') return EProviderType.Company;
+            if (lower === 'freelancer') return EProviderType.Freelancer;
+            if (lower === 'cooperative') return EProviderType.Cooperative;
+            if (lower === 'none') return EProviderType.None;
         }
         return val;
     }, z.nativeEnum(EProviderType).optional()),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around
lines 20 - 28, The type preprocess for the Zod schema (the preprocess passed for
the 'type' field) only parses numeric strings but doesn't map named string
values to the EProviderType enum like the verificationStatus preprocess does;
update the preprocess for 'type' to detect named strings (e.g., 'Individual',
'Company' and common lowercase variants or API-specific names) and map them to
the corresponding EProviderType enum members before returning, while still
handling numeric-string parsing and returning the raw value otherwise so
z.nativeEnum(EProviderType).optional() can validate correctly.

86-90: 404 detection via string matching on error.message is fragile.

Line 87 checks error.message.includes("404") which could match unrelated messages containing "404". The explicit res.status === 404 check on line 61 already handles the fetch response case. This catch block pattern could mask real errors if their messages happen to contain "404".

Consider narrowing this to only re-throw:

Proposed simplification
     } catch (error) {
-        if (error instanceof Error && (error.message.includes("404") || (error as { status?: number }).status === 404)) return null;
         console.error(`Exception fetching public provider ${id}:`, error);
         throw error;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around
lines 86 - 90, The catch block in page.tsx currently treats any error whose
message includes "404" as a not-found and returns null; remove the fragile
string-match and only treat explicit 404 cases (e.g., where (error as { status?:
number }).status === 404 or a thrown NotFoundError) as not-found. Update the
catch in the async fetch/loader (the block that logs "Exception fetching public
provider ${id}:") to drop error.message.includes("404") and instead rely on the
existing response-status handling or a specific error type/`status` property;
for all other errors, log and rethrow unchanged.
src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts (1)

52-58: as any on partial businessProfile silently drops type enforcement.

The search DTO doesn't carry a full BusinessProfileDto, so the partial object is cast to any. Consider typing it as Partial<BusinessProfileDto> and making businessProfile optional on ProviderDto, or create a minimal search-specific type. This would catch field-access errors downstream instead of silently passing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts` around lines 52 - 58, The
current mapper in mappers.ts casts a partial businessProfile to any which
removes type safety; change the businessProfile assignment to use a proper type
(e.g., Partial<BusinessProfileDto>) instead of as any, and update ProviderDto to
make businessProfile optional or introduce a SearchProviderDto/minimal DTO that
defines businessProfile as Partial<BusinessProfileDto>; ensure the mapper
returns that typed shape (referencing businessProfile, ProviderDto, and
BusinessProfileDto) so downstream code cannot silently access missing fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 13-16: Duplicate comment "// Zod Schema for Runtime Validation"
appears twice; remove the redundant instance so the file only contains a single
comment occurrence. Edit the page component file (page.tsx) and delete one of
the duplicate comment lines adjacent to the imports (keep the one that best
matches surrounding context), ensuring imports like EVerificationStatus and
EProviderType remain unchanged and the file compiles.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/verified-badge.tsx`:
- Around line 26-33: The title tooltip in the JSX branch that checks status ===
EVerificationStatus.Rejected still reads "Prestador Suspenso/Rejeitado"; update
that title to match the visible label (e.g., "Rejeitado" or "Prestador
Rejeitado") in the component (verified-badge.tsx) so the Rejected branch’s div
(the one rendering <Ban size={iconSize} /> and using className, showLabel and
iconSize) uses the corrected title text.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts`:
- Around line 33-46: The loading flag can remain true when an in-flight fetch is
aborted then an early return happens; to fix, ensure setIsLoading(false) is
invoked whenever you abort/clear the current controller or when you early-return
for an invalid CEP. Concretely, inside use-via-cep.ts update the abort block
that manipulates abortControllerRef (and the similar block at 78–82) to call
setIsLoading(false) after aborting and before nulling
abortControllerRef.current, and/or call setIsLoading(false) immediately before
returning when cleanCep.length !== 8; keep the existing finally handler that
checks the controller variable (the same controller created for the fetch)
untouched so it still clears loading for normal completion.

---

Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Customer/app/cadastro/prestador/page.tsx`:
- Around line 34-45: The effect is re-running because the whole form object
(which can be unstable) is in the dependency array; pull stable callbacks out
and depend on them instead — destructure const { getValues, setValue } = form
outside the useEffect and then change the effect dependencies to [session,
getValues, setValue] (or otherwise use stable refs) so the useEffect that sets
name/email only runs when session or the actual form methods change; update the
useEffect callback to use those extracted getValues and setValue references.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx`:
- Around line 57-64: The Image in Avatar component uses external absolute avatar
URLs (see Image usage in avatar.tsx), so update next.config.ts to include
images.remotePatterns entries for the OAuth hosts (e.g.,
lh3.googleusercontent.com and avatars.githubusercontent.com) allowing https
hostnames and appropriate path patterns; ensure the exported NextConfig includes
this images.remotePatterns block and that the config is being used by the build
so next/image will accept those external avatar URLs at runtime.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 108-175: The mapApiProviderToProvider function currently uses many
unsafe casts (e.g., as unknown as, as any, and : any) around LocalProviderDto,
dto.type, dto.status, dto.verificationStatus, documents and qualifications;
regenerate the SDK types (types.gen.ts) from the updated OpenAPI spec to restore
correct typings, then replace the ad-hoc casts in mapApiProviderToProvider with
the proper generated types so you can remove LocalProviderDto shims and narrow
DTO properties (documents, qualifications, type/status enums,
createdAt/updatedAt) to their real types; after regen, update mapping logic to
use the concrete SDK interfaces instead of as any/unknown and adjust any
nullable/optional handling accordingly.

In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts`:
- Around line 98-102: The frontend enum EDocumentStatus is missing backend
values Rejected = 4 and Failed = 5; update the EDocumentStatus enum to include
Rejected = 4 and Failed = 5 so numeric values match the backend contract (ensure
any switch/map logic consuming EDocumentStatus handles these new members).

---

Nitpick comments:
In
`@src/Modules/Providers/Application/Handlers/Commands/RegisterProviderCommandHandler.cs`:
- Around line 67-80: In RegisterProviderCommandHandler.HandleAsync, do not
re-throw the processed exception from
PostgreSqlExceptionProcessor.ProcessException(ex); instead handle
non-UniqueConstraintException and the case where UniqueConstraintException
yields a null existing provider by logging the error (use logger) and returning
a Result<ProviderDto>.Failure with an appropriate error message/details so the
method always returns a Result; keep the existing branch that returns
Result<ProviderDto>.Success(existing.ToDto()) when existing is found, but
replace the throw processedEx and the null path after
providerRepository.GetByUserIdAsync with calls to Result<ProviderDto>.Failure
(including processedEx.Message or a mapped error code) to preserve the Result
contract.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 20-28: The type preprocess for the Zod schema (the preprocess
passed for the 'type' field) only parses numeric strings but doesn't map named
string values to the EProviderType enum like the verificationStatus preprocess
does; update the preprocess for 'type' to detect named strings (e.g.,
'Individual', 'Company' and common lowercase variants or API-specific names) and
map them to the corresponding EProviderType enum members before returning, while
still handling numeric-string parsing and returning the raw value otherwise so
z.nativeEnum(EProviderType).optional() can validate correctly.
- Around line 86-90: The catch block in page.tsx currently treats any error
whose message includes "404" as a not-found and returns null; remove the fragile
string-match and only treat explicit 404 cases (e.g., where (error as { status?:
number }).status === 404 or a thrown NotFoundError) as not-found. Update the
catch in the async fetch/loader (the block that logs "Exception fetching public
provider ${id}:") to drop error.message.includes("404") and instead rely on the
existing response-status handling or a specific error type/`status` property;
for all other errors, log and rethrow unchanged.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx`:
- Around line 60-61: In Avatar (avatar.tsx) the props width={sizePx[size] as
number} and height={sizePx[size] as number} use a redundant type assertion;
remove the "as number" casts and pass width={sizePx[size]} and
height={sizePx[size]} directly (ensure the sizePx constant/lookup is correctly
typed as mapping to number so TypeScript still infers number for sizePx and
size).

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-via-cep.ts`:
- Line 75: The console.error(err) call inside the use-via-cep hook should be
replaced with the project's structured logging or at minimum guarded in
non-production; locate the error handling in the useViaCep hook (the function
where console.error(err) is used) and either call the shared logger (e.g.,
logger.error with a contextual message and the err object) or wrap the
console.error call with a check like process.env.NODE_ENV !== 'production' so
that logs in production use the centralized observability layer instead of
console.error.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 52-58: The current mapper in mappers.ts casts a partial
businessProfile to any which removes type safety; change the businessProfile
assignment to use a proper type (e.g., Partial<BusinessProfileDto>) instead of
as any, and update ProviderDto to make businessProfile optional or introduce a
SearchProviderDto/minimal DTO that defines businessProfile as
Partial<BusinessProfileDto>; ensure the mapper returns that typed shape
(referencing businessProfile, ProviderDto, and BusinessProfileDto) so downstream
code cannot silently access missing fields.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs`:
- Around line 76-91: The test IsValidRole_WithValidRole_ShouldReturnTrue is
missing the four new provider roles; update its InlineData to include
"provider-standard", "provider-silver", "provider-gold", and "provider-platinum"
so that UserRoles.IsValidRole(role) is exercised for those values as well,
ensuring the test covers all entries in UserRoles.AllRoles.
- Around line 204-252: Add a case-insensitive theory for
UserRoles.IsProviderRole similar to the existing tests for IsValidRole and
IsAdminRole: create a new Theory named
IsProviderRole_WithValidRoleDifferentCase_ShouldReturnTrue that passes provider
role strings with varying case (e.g., "Provider-Standard", "PROVIDER-SILVER",
"provider-GOLD", "PrOvIdEr-PlAtInUm") and asserts UserRoles.IsProviderRole(...)
returns true; reference the UserRoles.IsProviderRole method and reuse the test
naming pattern used by IsValidRole and IsAdminRole to verify OrdinalIgnoreCase
behavior.

…lic "become provider" endpoint and introduce a new base class for integration tests.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/Modules/Providers/API/Endpoints/Public/BecomeProviderEndpoint.cs (1)

62-62: Hardcoded location URI is fragile.

The "/api/v1/providers/me" string duplicates the route prefix logic from BaseEndpoint.CreateVersionedGroup. If the API version or module name changes, this will silently become stale.

Consider building the URI dynamically or using a named route with TypedResults.CreatedAtRoute.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Providers/API/Endpoints/Public/BecomeProviderEndpoint.cs` at line
62, The return uses a hardcoded URI string in BecomeProviderEndpoint which will
break if BaseEndpoint.CreateVersionedGroup route/version changes; instead
register the provider GET route with a route name (e.g., "GetCurrentProvider" or
reuse an existing named route) and replace
Results.Created($"/api/v1/providers/me", ...) with
Results.CreatedAtRoute(routeName, new { /* route values: id or "me" */ }, new
Response<ProviderDto>(result.Value!)); ensure the named route is defined on the
corresponding GET endpoint and pass the correct route values so the location is
generated dynamically.
tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs (2)

479-486: Broad catch (Exception) silently swallows non-PostGIS failures.

catch (Exception ex) will also catch OperationCanceledException, Npgsql authentication errors, or a wrong connection string, masking real infrastructure problems. The warning message alone ("Migrations might fail if not included") may not be enough signal for those cases.

Consider narrowing the catch to PostGIS-specific errors:

♻️ Proposed refinement
-catch (Exception ex)
+catch (Npgsql.PostgresException ex)
 {
-   logger?.LogWarning(ex, "⚠️ Failed to explicitly create PostGIS extension. Migrations might fail if not included.");
+   logger?.LogWarning(ex, "Failed to create PostGIS extension (SqlState={SqlState}). Migrations may fail if PostGIS is unavailable.", ex.SqlState);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs` around lines 479 -
486, The current try/catch around context.Database.ExecuteSqlRawAsync("CREATE
EXTENSION IF NOT EXISTS postgis;") swallows all exceptions; change it to only
catch Postgres/Npgsql errors related to CREATE EXTENSION (e.g., catch
Npgsql.PostgresException or PostgresException) and log the warning via
logger.LogWarning(ex, ...) for those cases, while letting other exceptions (like
OperationCanceledException, authentication, or connection string errors) bubble
up (or rethrow) so infrastructure failures are not masked; keep the existing
warning message for Postgres-specific exceptions and ensure other unexpected
exceptions are not caught silently.

476-479: New inline comments are in English — should be in Portuguese.

The three comments introduced in the changed segment are in English. Per the project's coding guidelines, all in-code comments must be written in Portuguese (log messages remain in English).

♻️ Proposed translation
-               // Ensure PostGIS extension exists (required for geometry types)
-               // This is necessary because EnsureDeletedAsync drops the database and extension
-               // And we need it before SearchProviders migrations if they use geometry types
+               // Garante que a extensão PostGIS existe (obrigatória para tipos de geometria)
+               // Necessário pois EnsureDeletedAsync remove o banco e a extensão
+               // Deve ser executado antes das migrations do SearchProviders que usam tipos de geometria

Based on learnings: "In all C# files, keep logging messages in English, but ensure comments and XML documentation are written in Portuguese across the codebase."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs` around lines 476 -
479, Os comentários recém-adicionados acima do bloco try (os três comentários
que explicam a necessidade da extensão PostGIS antes das migrations do
SearchProviders e que EnsureDeletedAsync apaga a base e a extensão) estão em
inglês; traduza-os para português mantendo o mesmo significado e contexto (por
exemplo referenciando "extensão PostGIS", "EnsureDeletedAsync" e "migrations do
SearchProviders") para cumprir a diretriz do projeto de comentários em português
enquanto preserva logs/strings em inglês.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/Modules/Providers/API/Endpoints/Public/BecomeProviderEndpoint.cs`:
- Line 62: The return uses a hardcoded URI string in BecomeProviderEndpoint
which will break if BaseEndpoint.CreateVersionedGroup route/version changes;
instead register the provider GET route with a route name (e.g.,
"GetCurrentProvider" or reuse an existing named route) and replace
Results.Created($"/api/v1/providers/me", ...) with
Results.CreatedAtRoute(routeName, new { /* route values: id or "me" */ }, new
Response<ProviderDto>(result.Value!)); ensure the named route is defined on the
corresponding GET endpoint and pass the correct route values so the location is
generated dynamically.

In `@tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs`:
- Around line 479-486: The current try/catch around
context.Database.ExecuteSqlRawAsync("CREATE EXTENSION IF NOT EXISTS postgis;")
swallows all exceptions; change it to only catch Postgres/Npgsql errors related
to CREATE EXTENSION (e.g., catch Npgsql.PostgresException or PostgresException)
and log the warning via logger.LogWarning(ex, ...) for those cases, while
letting other exceptions (like OperationCanceledException, authentication, or
connection string errors) bubble up (or rethrow) so infrastructure failures are
not masked; keep the existing warning message for Postgres-specific exceptions
and ensure other unexpected exceptions are not caught silently.
- Around line 476-479: Os comentários recém-adicionados acima do bloco try (os
três comentários que explicam a necessidade da extensão PostGIS antes das
migrations do SearchProviders e que EnsureDeletedAsync apaga a base e a
extensão) estão em inglês; traduza-os para português mantendo o mesmo
significado e contexto (por exemplo referenciando "extensão PostGIS",
"EnsureDeletedAsync" e "migrations do SearchProviders") para cumprir a diretriz
do projeto de comentários em português enquanto preserva logs/strings em inglês.

…with new UI components and API integration.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (10)
tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs (2)

47-53: Add a mirror test for ProviderRoles content and count.

AdminRoles_ShouldContainOnlyAdminRoles (line 33) and CustomerRoles_ShouldContainCustomerRole (line 48) both pin the count and exact members of their collections. ProviderRoles has no equivalent, meaning an accidental modification to the array (e.g., a role added or removed) would go undetected until the subset consistency test or a runtime bug surfaces it.

♻️ Proposed addition (place alongside the other Constants Tests)
+[Fact]
+public void ProviderRoles_ShouldContainOnlyProviderRoles()
+{
+    // Asserção
+    UserRoles.ProviderRoles.Should().HaveCount(4);
+    UserRoles.ProviderRoles.Should().Contain(new[]
+    {
+        UserRoles.ProviderStandard,
+        UserRoles.ProviderSilver,
+        UserRoles.ProviderGold,
+        UserRoles.ProviderPlatinum
+    });
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
47 - 53, Add a new unit test that mirrors the existing
CustomerRoles_ShouldContainCustomerRole to assert ProviderRoles' count and exact
members: create a test (e.g., ProviderRoles_ShouldContainOnlyProviderRoles) that
uses UserRoles.ProviderRoles and verifies the expected number of roles with
HaveCount(...) and that it contains the exact role constants from
UserRoles.Provider (or the specific provider role constants), following the
pattern used by AdminRoles_ShouldContainOnlyAdminRoles and
CustomerRoles_ShouldContainCustomerRole so any accidental additions/removals to
ProviderRoles are caught.

169-179: Add provider-tier roles to IsAdminRole_WithNonAdminRole_ShouldReturnFalse coverage.

The four new provider roles are valid system roles but are not admin roles — they should be explicitly asserted as such to prevent regression if AdminRoles is inadvertently expanded. Currently only "customer" and "viewer" are tested.

♻️ Proposed addition
 [Theory]
 [InlineData("customer")]
 [InlineData("viewer")]
+[InlineData("provider-standard")]
+[InlineData("provider-silver")]
+[InlineData("provider-gold")]
+[InlineData("provider-platinum")]
 public void IsAdminRole_WithNonAdminRole_ShouldReturnFalse(string role)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
169 - 179, Update the IsAdminRole_WithNonAdminRole_ShouldReturnFalse test to
include the four provider-tier role values so they are explicitly asserted
non-admin; add additional [InlineData(...)] entries for each provider role
string (or reference the constants if provider role constants exist) and ensure
the test calls UserRoles.IsAdminRole(role) and asserts result.Should().BeFalse()
for those values (keep the test name and assertion logic unchanged).
src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx (2)

60-61: Unnecessary as number casts.

sizePx[size] is already typed as number — the casts are redundant.

Suggested cleanup
-                    width={sizePx[size] as number}
-                    height={sizePx[size] as number}
+                    width={sizePx[size]}
+                    height={sizePx[size]}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx` around lines 60 -
61, The width/height props in the Avatar component are using redundant casts —
remove the unnecessary "as number" from the assignments where you set
width={sizePx[size] as number} and height={sizePx[size] as number}; instead use
width={sizePx[size]} and height={sizePx[size]}; ensure the referenced symbol
sizePx retains a number-typed index signature (e.g., number[] or Record<... ,
number>) and that the prop names/variables size and sizePx in avatar.tsx remain
correctly typed so the compiler accepts the direct usage.

5-5: Type derivation mismatch: AvatarProps should derive from ImageProps instead of native img props.

AvatarProps extends Omit<ComponentProps<"img">, "src">, but the component renders next/image <Image>. While current usages only pass safe props (src, alt, className, fallback), this type structure allows consumers to pass native img props incompatible with <Image> (e.g., srcSet, useMap).

Consider using ImageProps as the base to align the type with the rendered component and prevent future misuse. Also, the as number cast on lines 60–61 is unnecessary since sizePx[size] is already typed as number.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx` at line 5, Change
AvatarProps to extend Next.js ImageProps instead of ComponentProps<"img"> so the
prop types match the rendered <Image> component (replace the current extends
Omit<ComponentProps<"img">, "src"> with something based on ImageProps and
omit/adjust any conflicting fields like "src" if needed); update any imports to
pull ImageProps from "next/image"; also remove the unnecessary "as number" casts
around sizePx[size] in the avatar sizing logic since sizePx is already typed as
number and the cast is redundant (refer to AvatarProps, sizePx and the usage of
size in the component).
src/Modules/Providers/API/Endpoints/Public/BecomeProviderEndpoint.cs (1)

66-72: Add input validation to RegisterProviderApiRequest.

Name and DocumentNumber are non-nullable strings with no length or format constraints, and Type has no [EnumDataType] guard. Without annotations (or a registered FluentValidation validator), the model binder will silently accept empty strings, oversized payloads, or out-of-range enum values.

♻️ Proposed fix
+using System.ComponentModel.DataAnnotations;
+
 public record RegisterProviderApiRequest(
+    [Required, MinLength(2), MaxLength(100)]
     string Name,
+    [EnumDataType(typeof(EProviderType))]
     EProviderType Type,
+    [Required, MinLength(5), MaxLength(20)]
     string DocumentNumber,
     string? PhoneNumber,
     string? Email
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Providers/API/Endpoints/Public/BecomeProviderEndpoint.cs` around
lines 66 - 72, RegisterProviderApiRequest lacks validation attributes on Name,
DocumentNumber and Type; add data-annotation attributes to enforce non-empty and
size constraints and validate the enum. Annotate Name with [Required] and a
reasonable [StringLength(max)] (and optionally [MinLength]) to prevent
empty/oversized values; annotate DocumentNumber with [Required], a
[StringLength(max)] and an appropriate [RegularExpression] if a format is
expected; and annotate Type with [Required] and
[EnumDataType(typeof(EProviderType))] to reject out-of-range enum values. Ensure
nullable PhoneNumber and Email keep appropriate validation (e.g. [Phone],
[EmailAddress]) if desired.
src/Modules/Providers/Application/Handlers/Commands/RegisterProviderCommandHandler.cs (1)

80-84: Inline comments should be in Portuguese per language policy.

Lines 80–81 and 84 use English comments, but the project convention requires comments to be in Portuguese (logs stay in English, comments/XML in Portuguese).

Suggested translation
-                // If we have a unique constraint violation but can't find the provider,
-                // it implies a race condition or data inconsistency that we should report as a failure
+                // Se houve violação de constraint única mas o prestador não foi encontrado,
+                // isso indica uma condição de corrida ou inconsistência de dados
                 return Result<ProviderDto>.Failure(new Error("Um prestador já está registrado para este usuário.", 409));
             }
-            // For other database errors, rethrow to be handled by global exception handler or outer catch
+            // Para outros erros de banco de dados, relançar para ser tratado pelo handler global de exceções
             throw processedEx;

Based on learnings: "Comments and XML summaries (///,

, , etc.) must be in Portuguese."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Providers/Application/Handlers/Commands/RegisterProviderCommandHandler.cs`
around lines 80 - 84, Os comentários em inglês dentro do manipulador
RegisterProviderCommandHandler (na seção que trata da violação de constraint
única e do rethrow para o handler global) devem ser convertidos para português;
localize o trecho que retorna Result<ProviderDto>.Failure(...) (e o comentário
logo após) e substitua os comentários em inglês por comentários em português
mantendo o código inalterado (mensagens de erro/logs em inglês permanecem como
estão) — garanta que as observações sobre condição de corrida/consistência de
dados e sobre rethrow para o handler global sejam expressas claramente em
português.
src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts (3)

52-58: Avoid as any on the mock businessProfile.

The as any cast silently hides shape mismatches between this mock object and BusinessProfileDto. Since the target ProviderDto.businessProfile expects ContactInfoDto and AddressDto, type the mock properly or use a type assertion to BusinessProfileDto so you get compile-time feedback if the shape drifts.

♻️ Proposed fix
+        businessProfile: {
+            legalName: dto.name ?? '',
+            fantasyName: null,
+            description: null,
+            contactInfo: { email: '' },
+            primaryAddress: {
+                street: '', number: '', neighborhood: '', city: dto.city ?? '', state: dto.state ?? '', zipCode: '', country: 'Brasil'
+            }
+        } as BusinessProfileDto,
-        businessProfile: {
-            legalName: dto.name ?? '',
-            contactInfo: { email: '' },
-            primaryAddress: {
-                street: '', number: '', neighborhood: '', city: dto.city ?? '', state: dto.state ?? '', zipCode: '', country: 'Brasil'
-            }
-        } as any, // Partial mock since Search doesn't return full profile

Add BusinessProfileDto to the import on line 5:

import type { ProviderDto, BusinessProfileDto } from '@/types/api/provider';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts` around lines 52 - 58, The
mock businessProfile is cast with "as any", hiding shape mismatches; update the
mapper to import and use the proper BusinessProfileDto type and replace "as any"
with a typed object or a type assertion to BusinessProfileDto so TypeScript
enforces the expected ContactInfoDto and AddressDto shapes for
ProviderDto.businessProfile; ensure imports include BusinessProfileDto
(alongside ProviderDto) and that the object fields match the BusinessProfileDto
structure used by the code paths that consume businessProfile.

210-221: Triple casts (as unknown as number) as any) defeat the purpose of the type extension.

Lines 213 and 216 use (d.documentType as unknown as number) as any and (d.status as unknown as number) as any. Since ExtendedProviderDto already defines documentType and status as number, these casts should be unnecessary. If the target DocumentDto uses enums (EDocumentType, EDocumentStatus), cast directly to those enums instead of any.

♻️ Proposed fix
         documents: dto.documents?.map(d => ({
             id: d.id ?? '',
             providerId: d.providerId ?? '',
-            documentType: (d.documentType as unknown as number) as any,
+            documentType: (d.documentType ?? 0) as EDocumentType,
             fileName: d.fileName ?? '',
             fileUrl: d.fileUrl ?? '',
-            status: (d.status as unknown as number) as any,
+            status: (d.status ?? 1) as EDocumentStatus,
             uploadedAt: d.uploadedAt ?? '',
             verifiedAt: d.verifiedAt,
             rejectionReason: d.rejectionReason,
             ocrData: d.ocrData
         })) || [],

You'll need to add EDocumentType and EDocumentStatus to the import on line 8:

import { EProviderType, EProviderStatus, EVerificationStatus, EProviderTier, EDocumentType, EDocumentStatus, ProviderServiceDto } from '@/types/api/provider';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts` around lines 210 - 221,
The triple-cast usage in the mapper for documents is unnecessary and unsafe:
replace `(d.documentType as unknown as number) as any` and `(d.status as unknown
as number) as any` with direct casts to the target enums (EDocumentType and
EDocumentStatus) so DocumentDto.documentType and DocumentDto.status get proper
enum types; add EDocumentType and EDocumentStatus to the import list (alongside
EProviderType/EProviderStatus/etc.) and change the mapping inside documents (the
mapping function that references documentType and status on the
ExtendedProviderDto `d`) to cast to those enums instead of any.

70-103: Remove unused interface definitions LocalBusinessProfileDto and LocalProviderDto.

These two interfaces (lines 70–103) are defined but never used anywhere in the file or project. mapApiProviderToProvider uses ExtendedProviderDto instead, making these definitions redundant dead code.

♻️ Proposed fix
-// Interface definitions to match actual API response structure to avoid generation sync issues
-interface LocalBusinessProfileDto {
-    legalName: string;
-    fantasyName?: string;
-    description?: string;
-    contactInfo?: {
-        email: string;
-        phoneNumber: string;
-    };
-    primaryAddress?: {
-        street: string;
-        number: string;
-        complement?: string;
-        neighborhood: string;
-        city: string;
-        state: string;
-        zipCode: string;
-        country: string;
-    };
-}
-
-interface LocalProviderDto extends MeAjudaAiModulesProvidersApplicationDtosProviderDto {
-    businessProfile?: LocalBusinessProfileDto;
-    services?: Array<{
-        serviceId: string;
-        price: number;
-        currency: string;
-        serviceName: string;
-    }>;
-    averageRating?: number;
-    reviewCount?: number;
-    tier?: EProviderTier;
-    createdAt?: string;
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts` around lines 70 - 103,
Remove the dead interfaces LocalBusinessProfileDto and LocalProviderDto from
this file: they are not referenced (mapApiProviderToProvider uses
ExtendedProviderDto), so delete both interface declarations and any unused
imports that were only needed for them; search for LocalBusinessProfileDto and
LocalProviderDto to ensure no external usages, update mapApiProviderToProvider
if it incorrectly referenced them, then run the TypeScript build/linters to
confirm no type errors remain.
src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts (1)

119-148: Consider sourcing these enums from a shared contracts package.

The enums and DTOs here are manually synchronized with the C# backend. Per project conventions, reusing Shared.Contracts for enums/constants helps prevent future drift. This is fine for now but worth tracking as a follow-up. Based on learnings, prefer reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts` around lines 119 - 148,
ProviderDto currently declares enum types (EProviderType, EProviderStatus,
EVerificationStatus, EProviderTier) that are manually synchronized with backend;
update ProviderDto to import and use the shared enums/constants from the
centralized Shared.Contracts package instead of local copies. Locate occurrences
of EProviderType, EProviderStatus, EVerificationStatus, and EProviderTier in the
ProviderDto declaration and replace them with the imported symbols from
Shared.Contracts (ensuring the
BusinessProfileDto/DocumentDto/QualificationDto/ProviderServiceDto DTOs stay
compatible), add the appropriate import statements at the top of the file, and
remove or mark any local duplicate enum definitions so the web types reference
the single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Modules/Providers/API/Endpoints/Public/BecomeProviderEndpoint.cs`:
- Around line 38-44: The handler in BecomeProviderEndpoint.cs currently falls
back to request.Email when the JWT lacks an email claim (variable email from
context.User?.FindFirst("email")?.Value), allowing unverified client-supplied
addresses; change the logic so that if the token has no email claim you
immediately return BadRequest("Email é obrigatório e não foi encontrado no
token.") instead of reading request.Email, i.e., remove the fallback to
request.Email and require the email-only from the JWT
(context.User.FindFirst("email")), ensuring provider creation uses only the
verified token claim.

In
`@src/Modules/Providers/Application/Handlers/Commands/RegisterProviderCommandHandler.cs`:
- Around line 80-85: Replace the incorrect Error constructor call in
RegisterProviderCommandHandler that passes two strings; the Error type expects
(string Message, int StatusCode). Construct the Error so the second argument is
the HTTP 409 status code and include the Portuguese message in the first
argument (or combine the key and message into the Message string) before
returning Result<ProviderDto>.Failure; reference:
RegisterProviderCommandHandler, ProviderDto, Error.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 88-94: Remove the dead 404 branch in the catch block: the thrown
errors from the try (plain Error instances created earlier) never have a status,
so delete the `(error as { status?: number }).status === 404` check and its
`return null;` path; instead, in the catch for the public provider fetch (the
block that logs `Exception fetching public provider ${id}:`), just log the error
and rethrow it (keep the existing console.error and `throw error;`) so error
handling relies on the earlier `res.status === 404` check.
- Around line 20-32: The type preprocess in page.tsx currently converts several
string labels to EProviderType but omits the 'none' case causing validation
failures; update the z.preprocess for the type field to detect when val (string)
lowercased === 'none' and return EProviderType.None (the enum value 0) before
attempting parseInt, so z.nativeEnum(EProviderType).optional() receives a valid
enum value.

---

Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 13-16: Remove the duplicated comment string "// Zod Schema for
Runtime Validation" so it only appears once; locate the two identical comments
surrounding the import of EVerificationStatus and EProviderType in the
prestador/[id]/page.tsx module and delete the redundant occurrence, leaving a
single clear comment above the relevant Zod-related imports or declarations.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx`:
- Around line 57-64: The Avatar component uses next/image with external OAuth
avatar URLs and will fail unless next.config.ts includes those hostnames in
images.remotePatterns; update next.config.ts (images.remotePatterns) to allow
common OAuth avatar hosts such as googleusercontent.com,
lh3.googleusercontent.com, avatars.githubusercontent.com (and any other provider
hostnames you expect), specifying the proper protocol/hostname patterns so the
Image in the Avatar component can load external avatars without runtime errors.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 148-149: The double-cast "const dto = rawDto as unknown as
ExtendedProviderDto" bypasses type safety; add a TODO comment immediately above
this line referencing the tracking issue (e.g., TODO: replace double-cast when
SDK types are updated — tracking: <link-to-issue>) and include a short note to
remove the cast and restore proper typed usage of rawDto/ExtendedProviderDto
when the generated SDK is fixed; optionally mention adding runtime validation
(e.g., a small isExtendedProviderDto check) as a follow-up to avoid silent
runtime failures.

---

Nitpick comments:
In `@src/Modules/Providers/API/Endpoints/Public/BecomeProviderEndpoint.cs`:
- Around line 66-72: RegisterProviderApiRequest lacks validation attributes on
Name, DocumentNumber and Type; add data-annotation attributes to enforce
non-empty and size constraints and validate the enum. Annotate Name with
[Required] and a reasonable [StringLength(max)] (and optionally [MinLength]) to
prevent empty/oversized values; annotate DocumentNumber with [Required], a
[StringLength(max)] and an appropriate [RegularExpression] if a format is
expected; and annotate Type with [Required] and
[EnumDataType(typeof(EProviderType))] to reject out-of-range enum values. Ensure
nullable PhoneNumber and Email keep appropriate validation (e.g. [Phone],
[EmailAddress]) if desired.

In
`@src/Modules/Providers/Application/Handlers/Commands/RegisterProviderCommandHandler.cs`:
- Around line 80-84: Os comentários em inglês dentro do manipulador
RegisterProviderCommandHandler (na seção que trata da violação de constraint
única e do rethrow para o handler global) devem ser convertidos para português;
localize o trecho que retorna Result<ProviderDto>.Failure(...) (e o comentário
logo após) e substitua os comentários em inglês por comentários em português
mantendo o código inalterado (mensagens de erro/logs em inglês permanecem como
estão) — garanta que as observações sobre condição de corrida/consistência de
dados e sobre rethrow para o handler global sejam expressas claramente em
português.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx`:
- Around line 60-61: The width/height props in the Avatar component are using
redundant casts — remove the unnecessary "as number" from the assignments where
you set width={sizePx[size] as number} and height={sizePx[size] as number};
instead use width={sizePx[size]} and height={sizePx[size]}; ensure the
referenced symbol sizePx retains a number-typed index signature (e.g., number[]
or Record<... , number>) and that the prop names/variables size and sizePx in
avatar.tsx remain correctly typed so the compiler accepts the direct usage.
- Line 5: Change AvatarProps to extend Next.js ImageProps instead of
ComponentProps<"img"> so the prop types match the rendered <Image> component
(replace the current extends Omit<ComponentProps<"img">, "src"> with something
based on ImageProps and omit/adjust any conflicting fields like "src" if
needed); update any imports to pull ImageProps from "next/image"; also remove
the unnecessary "as number" casts around sizePx[size] in the avatar sizing logic
since sizePx is already typed as number and the cast is redundant (refer to
AvatarProps, sizePx and the usage of size in the component).

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 52-58: The mock businessProfile is cast with "as any", hiding
shape mismatches; update the mapper to import and use the proper
BusinessProfileDto type and replace "as any" with a typed object or a type
assertion to BusinessProfileDto so TypeScript enforces the expected
ContactInfoDto and AddressDto shapes for ProviderDto.businessProfile; ensure
imports include BusinessProfileDto (alongside ProviderDto) and that the object
fields match the BusinessProfileDto structure used by the code paths that
consume businessProfile.
- Around line 210-221: The triple-cast usage in the mapper for documents is
unnecessary and unsafe: replace `(d.documentType as unknown as number) as any`
and `(d.status as unknown as number) as any` with direct casts to the target
enums (EDocumentType and EDocumentStatus) so DocumentDto.documentType and
DocumentDto.status get proper enum types; add EDocumentType and EDocumentStatus
to the import list (alongside EProviderType/EProviderStatus/etc.) and change the
mapping inside documents (the mapping function that references documentType and
status on the ExtendedProviderDto `d`) to cast to those enums instead of any.
- Around line 70-103: Remove the dead interfaces LocalBusinessProfileDto and
LocalProviderDto from this file: they are not referenced
(mapApiProviderToProvider uses ExtendedProviderDto), so delete both interface
declarations and any unused imports that were only needed for them; search for
LocalBusinessProfileDto and LocalProviderDto to ensure no external usages,
update mapApiProviderToProvider if it incorrectly referenced them, then run the
TypeScript build/linters to confirm no type errors remain.

In `@src/Web/MeAjudaAi.Web.Customer/types/api/provider.ts`:
- Around line 119-148: ProviderDto currently declares enum types (EProviderType,
EProviderStatus, EVerificationStatus, EProviderTier) that are manually
synchronized with backend; update ProviderDto to import and use the shared
enums/constants from the centralized Shared.Contracts package instead of local
copies. Locate occurrences of EProviderType, EProviderStatus,
EVerificationStatus, and EProviderTier in the ProviderDto declaration and
replace them with the imported symbols from Shared.Contracts (ensuring the
BusinessProfileDto/DocumentDto/QualificationDto/ProviderServiceDto DTOs stay
compatible), add the appropriate import statements at the top of the file, and
remove or mark any local duplicate enum definitions so the web types reference
the single source of truth.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs`:
- Around line 47-53: Add a new unit test that mirrors the existing
CustomerRoles_ShouldContainCustomerRole to assert ProviderRoles' count and exact
members: create a test (e.g., ProviderRoles_ShouldContainOnlyProviderRoles) that
uses UserRoles.ProviderRoles and verifies the expected number of roles with
HaveCount(...) and that it contains the exact role constants from
UserRoles.Provider (or the specific provider role constants), following the
pattern used by AdminRoles_ShouldContainOnlyAdminRoles and
CustomerRoles_ShouldContainCustomerRole so any accidental additions/removals to
ProviderRoles are caught.
- Around line 169-179: Update the IsAdminRole_WithNonAdminRole_ShouldReturnFalse
test to include the four provider-tier role values so they are explicitly
asserted non-admin; add additional [InlineData(...)] entries for each provider
role string (or reference the constants if provider role constants exist) and
ensure the test calls UserRoles.IsAdminRole(role) and asserts
result.Should().BeFalse() for those values (keep the test name and assertion
logic unchanged).

…rs, configure Next.js for image handling, and introduce unit tests for user roles.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx (1)

148-152: ⚠️ Potential issue | 🟠 Major

WhatsApp link double-prepends country code 55 when API returns phone with country code included.

The API returns phone numbers with the Brazilian country code (e.g., "+5511999999999"). The frontend function strips non-digits to get "5511999999999", then unconditionally prepends "55", resulting in "555511999999999" — an invalid WhatsApp link.

The fix should guard against the country code already being present:

🐛 Proposed fix — guard against existing country code
     const getWhatsappLink = (phone: string) => {
         const cleanPhone = phone.replace(/\D/g, "");
-        // Validate: Brazilian phone should have at least 10 digits (DDD + number)
-        return cleanPhone.length >= 10 ? `https://wa.me/55${cleanPhone}` : null;
+        // Validate: Brazilian phone should have at least 10 digits (DDD + number)
+        if (cleanPhone.length < 10) return null;
+        // Avoid double-prepending country code
+        const fullNumber = cleanPhone.startsWith('55') && cleanPhone.length >= 12
+            ? cleanPhone
+            : `55${cleanPhone}`;
+        return `https://wa.me/${fullNumber}`;
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around
lines 148 - 152, The getWhatsappLink function currently always prepends "55"
after stripping non-digits, causing double country codes when the API already
returns +55; update getWhatsappLink to: strip non-digits into cleanPhone, detect
and remove any leading international prefixes as needed (e.g., if cleanPhone
startsWith "55" treat it as already containing the country code and do not add
another "55"), validate that the resulting number (without the country code) has
at least 10 digits (DDD + number), and return the wa.me link using a single "55"
prefix or null if invalid; refer to the getWhatsappLink function to implement
this guard and validation.
🧹 Nitpick comments (7)
tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs (1)

226-237: Optional: extend IsProviderRole_WithNonProviderRole_ShouldReturnFalse with admin-tier roles for symmetry.

IsAdminRole_WithNonAdminRole_ShouldReturnFalse explicitly includes all four new provider-tier roles to guard against category bleed. The inverse test here only covers admin, customer, and viewer, leaving the admin-tier roles (provider-manager, document-reviewer, catalog-manager, operator) untested as non-provider roles.

♻️ Suggested additional InlineData entries
 [Theory]
 [InlineData("admin")]
+[InlineData("provider-manager")]
+[InlineData("document-reviewer")]
+[InlineData("catalog-manager")]
+[InlineData("operator")]
 [InlineData("customer")]
 [InlineData("viewer")]
 public void IsProviderRole_WithNonProviderRole_ShouldReturnFalse(string role)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
226 - 237, Update the test IsProviderRole_WithNonProviderRole_ShouldReturnFalse
so it also asserts that the new admin-tier roles are not considered provider
roles: add InlineData entries for "provider-manager", "document-reviewer",
"catalog-manager", and "operator" and keep the assertion calling
UserRoles.IsProviderRole(role).Should().BeFalse(); to mirror the symmetry used
in IsAdminRole_WithNonAdminRole_ShouldReturnFalse.
src/Modules/Providers/API/Endpoints/Public/BecomeProviderEndpoint.cs (1)

64-70: Dead Email field in RegisterProviderApiRequest — remove it.

Since the handler now derives the email exclusively from the JWT (context.User?.FindFirst("email")?.Value) and never reads request.Email, the Email field on the request record is dead code. Keeping it:

  • misleads API consumers into believing they can supply/override the email via the request body, and
  • runs [EmailAddress] validation on a value that is silently ignored.
♻️ Proposed refactor
 public record RegisterProviderApiRequest(
     [Required, StringLength(100)] string Name,
     [Required, EnumDataType(typeof(EProviderType))] EProviderType Type,
     [Required, StringLength(20)] string DocumentNumber,
-    [Phone, StringLength(20)] string? PhoneNumber,
-    [EmailAddress] string? Email
+    [Phone, StringLength(20)] string? PhoneNumber
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Providers/API/Endpoints/Public/BecomeProviderEndpoint.cs` around
lines 64 - 70, Remove the dead Email field from the RegisterProviderApiRequest
record since the handler derives email from the JWT
(context.User?.FindFirst("email")?.Value) and never uses request.Email; update
the record declaration to drop the Email property and any [EmailAddress]
attribute, then search for any references to RegisterProviderApiRequest.Email
and remove or refactor them so nothing relies on the removed property (this
keeps model validation and API surface accurate).
src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx (1)

5-10: width, height, and fill should be omitted from AvatarProps to prevent silent dimension conflicts.

Because AvatarProps extends Omit<ImageProps, "src" | "alt">, width, height, and fill are all valid consumer props. Two problems follow:

  1. width/height override{...rest} is spread after the explicit width={sizePx[size]} / height={sizePx[size]}, so any caller-supplied width or height silently replaces the size-derived values, undermining the size prop contract.
  2. fill conflictfill={true} passed through rest conflicts with the explicit fixed dimensions; Next.js raises a dev error ("Image with src ... has both "fill" and "width" props.") and layout breaks.
♻️ Proposed fix
-export interface AvatarProps extends Omit<ImageProps, "src" | "alt"> {
+export interface AvatarProps extends Omit<ImageProps, "src" | "alt" | "width" | "height" | "fill"> {

Also applies to: 60-63

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx` around lines 5 - 10,
The AvatarProps type currently extends Omit<ImageProps, "src" | "alt"> which
still allows caller-supplied width/height/fill to override the component's size
behavior; update the type to Omit<ImageProps, "src" | "alt" | "width" | "height"
| "fill"> so width, height and fill are removed from the public props, and keep
the explicit width={sizePx[size]} / height={sizePx[size]} and fill handling
inside the Avatar component (e.g., where AvatarProps and the Image usage are
defined) so callers cannot silently override dimensions or pass fill.
src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx (1)

58-68: Consider guarding against missing API URL in production.

Line 60 falls back to 'http://localhost:7002' if neither API_URL nor NEXT_PUBLIC_API_URL is set. In a production deployment this would silently send requests to localhost, causing confusing failures. A defensive approach would log a warning or throw during build/startup if the env var is absent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around
lines 58 - 68, The current getCachedProvider function falls back to
'http://localhost:7002' when API_URL/NEXT_PUBLIC_API_URL are missing; change
this to fail fast: inside getCachedProvider (where apiUrl is computed) detect if
neither process.env.API_URL nor process.env.NEXT_PUBLIC_API_URL is set and throw
a clear Error (or call a logger.warn + throw) that names the missing env vars,
so production doesn't silently call localhost; update any related error handling
to surface this during startup/runtime rather than using the localhost fallback.
src/Web/MeAjudaAi.Web.Customer/next.config.ts (1)

6-19: Omitting pathname implies ** on all patterns — consider adding explicit path restrictions.

When omitting protocol, port, pathname, or search, the wildcard ** is implied. This is not recommended because it may allow malicious actors to optimize URLs you did not intend. All three entries leave pathname unspecified, which allows Next.js to proxy and optimize any image path from those domains through /_next/image. While fine for profile avatars today, consider restricting to actual avatar path prefixes for better posture.

♻️ Proposed path restrictions
     {
       protocol: "https",
       hostname: "avatars.githubusercontent.com",
+      pathname: "/u/**",
     },
     {
       protocol: "https",
       hostname: "**.googleusercontent.com",
+      pathname: "/a/**",
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/next.config.ts` around lines 6 - 19, The
remotePatterns entries in next.config.ts (the remotePatterns array) omit
pathname which implies a ** wildcard and allows any path to be proxied; update
each object in remotePatterns to include explicit pathname restrictions that
match the actual avatar/image path prefixes used by those hosts (e.g., narrow to
the avatar or profile image path segments for lh3.googleusercontent.com,
avatars.githubusercontent.com, and googleusercontent.com) so Next.js only
optimizes intended URLs; modify the remotePatterns objects (protocol/hostname
pairs) to add appropriate pathname strings rather than leaving them unspecified.
src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts (2)

59-65: as BusinessProfileDto cast may silently suppress structural type errors.

contactInfo: { email: '' } is cast via as BusinessProfileDto rather than typed structurally. If ContactInfoDto has additional required fields (e.g., phoneNumber), TypeScript won't surface the gap. Consider typing contactInfo inline against the actual ContactInfoDto shape, or using as unknown as BusinessProfileDto with an explicit comment noting this is intentionally partial.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts` around lines 59 - 65, The
businessProfile object in the mapper uses a loose cast "as BusinessProfileDto"
which can hide missing required fields (specifically contactInfo). Replace the
inline contactInfo literal with a value explicitly typed to the actual
ContactInfoDto shape (e.g., declare contactInfo: ContactInfoDto = { ... } or
annotate the property as ContactInfoDto) so TypeScript validates required
fields, or if you intentionally want a partial stub, change the cast to "as
unknown as BusinessProfileDto" and add a clear comment on businessProfile /
contactInfo noting this is an intentional partial mock; update the
businessProfile assignment in mappers.ts (the businessProfile / contactInfo
entries) accordingly.

135-135: ?? undefined is a no-op — remove for clarity.

contactInfo?.phoneNumber ?? undefined and q.fileUrl ?? undefined both reduce to the left-hand expression; undefined is already the result when the operand is undefined. The ?? fallback serves no purpose here.

♻️ Proposed fix
-        phone: contactInfo?.phoneNumber ?? undefined,
+        phone: contactInfo?.phoneNumber,
-            fileUrl: q.fileUrl ?? undefined
+            fileUrl: q.fileUrl

Also applies to: 196-196

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts` at line 135, The
expressions using the nullish coalescing fallback to undefined are redundant;
remove the "?? undefined" occurrences so fields like the phone mapping (phone:
contactInfo?.phoneNumber ?? undefined) and file URL mapping (q.fileUrl ??
undefined) simply use the optional chain result (contactInfo?.phoneNumber,
q.fileUrl). Update the mapper code in mappers.ts where these patterns appear
(e.g., the phone mapping and the q.fileUrl mapping) to return the left-hand
expression directly for clarity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Modules/Providers/API/Endpoints/Public/BecomeProviderEndpoint.cs`:
- Around line 38-42: The comment above the email extraction in
BecomeProviderEndpoint.cs is stale—remove or rewrite the phrase "senão usar do
request" so it accurately reflects that the handler now strictly requires the
token claim; update the comment that precedes the
context.User?.FindFirst("email")?.Value extraction (and the subsequent
string.IsNullOrEmpty(email) check) to state that email must come from the token
and there is no fallback to request.Email.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Line 183: When mapping ExtendedProviderDto -> DocumentDto ensure you provide
safe fallbacks for numeric-optional enum fields: replace the direct casts of
d.documentType and d.status to EDocumentType/EDocumentStatus with
nullish-coalescing fallbacks (e.g., d.documentType ?? EDocumentType.Other and
d.status ?? EDocumentStatus.Pending) so undefined values aren’t silently cast;
update the mapper that constructs DocumentDto (references: ExtendedProviderDto,
DocumentDto, d.documentType, d.status, EDocumentType, EDocumentStatus) to use
those appropriate sentinel defaults.
- Line 68: The mapper currently sets status: EProviderStatus.PendingBasicInfo
which causes search results to render as "incomplete"—change the mapped status
in lib/api/mappers.ts to EProviderStatus.Active (keeping or adding the note that
the Search API only indexes active providers) so ProviderCard sees an active
status; update the mapping that produces the provider object (reference
EProviderStatus and the provider mapping function in this file) to use
EProviderStatus.Active instead of PendingBasicInfo.

In `@src/Web/MeAjudaAi.Web.Customer/next.config.ts`:
- Around line 7-18: Replace the two separate hostname entries
("lh3.googleusercontent.com" and "googleusercontent.com") with a single wildcard
entry matching all subdomains (use "**.googleusercontent.com") so Next.js image
hostname matching will allow lh3, lh4, etc.; update the hostname objects in the
images/domains configuration (where "lh3.googleusercontent.com" and
"googleusercontent.com" are defined) to a single object with the wildcard
hostname.

---

Outside diff comments:
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 148-152: The getWhatsappLink function currently always prepends
"55" after stripping non-digits, causing double country codes when the API
already returns +55; update getWhatsappLink to: strip non-digits into
cleanPhone, detect and remove any leading international prefixes as needed
(e.g., if cleanPhone startsWith "55" treat it as already containing the country
code and do not add another "55"), validate that the resulting number (without
the country code) has at least 10 digits (DDD + number), and return the wa.me
link using a single "55" prefix or null if invalid; refer to the getWhatsappLink
function to implement this guard and validation.

---

Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 88-93: Previous dead check for `status === 404` has been removed;
ensure the catch block in the fetch flow (the catch that logs `Exception
fetching public provider ${id}:`) simply logs the error and rethrows it without
reintroducing any status-specific handling. Remove any remaining conditional
checks for `status === 404` in that catch and keep the single console.error +
throw error behavior so the 404 handling on the earlier path remains the single
source of truth.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx`:
- Around line 57-64: The avatar Image in components/ui/avatar.tsx uses external
OAuth avatar URLs (prop src passed into the Image component), so ensure Next.js
image config allows those hosts by adding the OAuth providers to
images.remotePatterns or images.domains in next.config.ts; update next.config.ts
to include remotePatterns entries (or domain entries) for googleusercontent.com
and avatars.githubusercontent.com (and any other OAuth hostnames used by your
auth code), then restart the dev server and verify the avatar Image renders
without Next.js host-blocking errors.

---

Nitpick comments:
In `@src/Modules/Providers/API/Endpoints/Public/BecomeProviderEndpoint.cs`:
- Around line 64-70: Remove the dead Email field from the
RegisterProviderApiRequest record since the handler derives email from the JWT
(context.User?.FindFirst("email")?.Value) and never uses request.Email; update
the record declaration to drop the Email property and any [EmailAddress]
attribute, then search for any references to RegisterProviderApiRequest.Email
and remove or refactor them so nothing relies on the removed property (this
keeps model validation and API surface accurate).

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 58-68: The current getCachedProvider function falls back to
'http://localhost:7002' when API_URL/NEXT_PUBLIC_API_URL are missing; change
this to fail fast: inside getCachedProvider (where apiUrl is computed) detect if
neither process.env.API_URL nor process.env.NEXT_PUBLIC_API_URL is set and throw
a clear Error (or call a logger.warn + throw) that names the missing env vars,
so production doesn't silently call localhost; update any related error handling
to surface this during startup/runtime rather than using the localhost fallback.

In `@src/Web/MeAjudaAi.Web.Customer/components/ui/avatar.tsx`:
- Around line 5-10: The AvatarProps type currently extends Omit<ImageProps,
"src" | "alt"> which still allows caller-supplied width/height/fill to override
the component's size behavior; update the type to Omit<ImageProps, "src" | "alt"
| "width" | "height" | "fill"> so width, height and fill are removed from the
public props, and keep the explicit width={sizePx[size]} / height={sizePx[size]}
and fill handling inside the Avatar component (e.g., where AvatarProps and the
Image usage are defined) so callers cannot silently override dimensions or pass
fill.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/mappers.ts`:
- Around line 59-65: The businessProfile object in the mapper uses a loose cast
"as BusinessProfileDto" which can hide missing required fields (specifically
contactInfo). Replace the inline contactInfo literal with a value explicitly
typed to the actual ContactInfoDto shape (e.g., declare contactInfo:
ContactInfoDto = { ... } or annotate the property as ContactInfoDto) so
TypeScript validates required fields, or if you intentionally want a partial
stub, change the cast to "as unknown as BusinessProfileDto" and add a clear
comment on businessProfile / contactInfo noting this is an intentional partial
mock; update the businessProfile assignment in mappers.ts (the businessProfile /
contactInfo entries) accordingly.
- Line 135: The expressions using the nullish coalescing fallback to undefined
are redundant; remove the "?? undefined" occurrences so fields like the phone
mapping (phone: contactInfo?.phoneNumber ?? undefined) and file URL mapping
(q.fileUrl ?? undefined) simply use the optional chain result
(contactInfo?.phoneNumber, q.fileUrl). Update the mapper code in mappers.ts
where these patterns appear (e.g., the phone mapping and the q.fileUrl mapping)
to return the left-hand expression directly for clarity.

In `@src/Web/MeAjudaAi.Web.Customer/next.config.ts`:
- Around line 6-19: The remotePatterns entries in next.config.ts (the
remotePatterns array) omit pathname which implies a ** wildcard and allows any
path to be proxied; update each object in remotePatterns to include explicit
pathname restrictions that match the actual avatar/image path prefixes used by
those hosts (e.g., narrow to the avatar or profile image path segments for
lh3.googleusercontent.com, avatars.githubusercontent.com, and
googleusercontent.com) so Next.js only optimizes intended URLs; modify the
remotePatterns objects (protocol/hostname pairs) to add appropriate pathname
strings rather than leaving them unspecified.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs`:
- Around line 226-237: Update the test
IsProviderRole_WithNonProviderRole_ShouldReturnFalse so it also asserts that the
new admin-tier roles are not considered provider roles: add InlineData entries
for "provider-manager", "document-reviewer", "catalog-manager", and "operator"
and keep the assertion calling
UserRoles.IsProviderRole(role).Should().BeFalse(); to mirror the symmetry used
in IsAdminRole_WithNonAdminRole_ShouldReturnFalse.

Filipe Frigini added 2 commits February 19, 2026 14:30
…e display with supporting UI components and configurations.
…t upload, address lookup, and new UI components.
…lf-or-admin handler, auth provider enum, and user interface components.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (1)
src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx (1)

91-99: ⚠️ Potential issue | 🟠 Major

Reset social-login loading state on every non-navigation outcome.

isLoading is set on Line 93 but only cleared in the catch block (Line 98). While OAuth redirects are typically implemented as thrown errors and will trigger the catch handler, defensive programming requires the finally block to ensure loading state clears in all code paths—particularly if the redirect mechanism fails to trigger. This pattern is already correctly applied to the credentials login handler (lines 86–88); apply the same pattern here for consistency.

Suggested fix
     const handleSocialLogin = async (provider: string) => {
         if (isLoading) return;
+        setError("");
         setIsLoading(true);
         try {
             await signIn("keycloak", { callbackUrl }, { kc_idp_hint: provider.toLowerCase() });
         } catch {
             setError("Ocorreu um erro. Tente novamente.");
-            setIsLoading(false);
+        } finally {
+            setIsLoading(false);
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx` around lines
91 - 99, The handleSocialLogin function sets isLoading but only clears it in the
catch branch; modify handleSocialLogin (the async function invoking signIn) to
ensure setIsLoading(false) runs for every outcome by moving the loading-clear
into a finally block so that isLoading is reset whether signIn redirects,
throws, or returns; keep the existing catch logic that sets setError(...) and
maintain the early-return when isLoading is true.
🧹 Nitpick comments (3)
src/Bootstrapper/MeAjudaAi.ApiService/Handlers/SelfOrAdminHandler.cs (1)

29-31: Replace hardcoded super-admin with a shared role constant.

Line 31 still uses a string literal, which can drift from shared authorization definitions over time.

♻️ Suggested refactor
-            string.Equals(r, "super-admin", StringComparison.OrdinalIgnoreCase)))
+            string.Equals(r, MeAjudaAi.Shared.Utilities.UserRoles.SuperAdmin, StringComparison.OrdinalIgnoreCase)))

Based on learnings: prefer reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Bootstrapper/MeAjudaAi.ApiService/Handlers/SelfOrAdminHandler.cs` around
lines 29 - 31, Replace the hardcoded "super-admin" string in the roles check
inside SelfOrAdminHandler with the shared role constant instead: locate the
conditional that compares roles to MeAjudaAi.Shared.Utilities.UserRoles.Admin
and the literal "super-admin", and swap the literal for the corresponding shared
constant (e.g., MeAjudaAi.Shared.Utilities.UserRoles.SuperAdmin or the
Shared.Contracts enum/constant that defines SuperAdmin) while keeping the
StringComparison.OrdinalIgnoreCase comparison; ensure both comparisons use the
same constant source to avoid drift.
src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts (2)

4-4: Consider sourcing HttpMethod from shared contracts instead of redefining it locally.

This avoids frontend/backend drift for allowed verbs as auth/onboarding APIs evolve.

Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts` at line 4, Replace
the local HttpMethod type definition with the shared contract enum/type from
Shared.Contracts to avoid verb drift: remove the local "type HttpMethod" in
fetch-client.ts and import the shared HttpMethod symbol (e.g.,
Shared.Contracts.HttpMethod or the project’s Shared.Contracts export that
exposes HTTP verbs), then update any usages in this file (references to
HttpMethod) to use the imported symbol so the client stays aligned with
backend/shared definitions.

70-80: Use ApiError consistently for non-HTTP failure paths.

These branches currently throw plain Error, while HTTP failures throw ApiError, which fragments caller-side error handling.

♻️ Proposed fix
-        if (value === null || value === undefined) {
-            throw new Error("Response contained null/undefined value for expected Result<T>");
-        }
+        if (value === null || value === undefined) {
+            throw new ApiError("Response contained null/undefined value for expected Result<T>");
+        }
@@
-        if (apiRes.data === null || apiRes.data === undefined) {
-            throw new Error(apiRes.message || "API interaction failed");
-        }
+        if (apiRes.data === null || apiRes.data === undefined) {
+            throw new ApiError(apiRes.message || "API interaction failed", response.status);
+        }
@@
-    if (requireAuth && !token) {
-        throw new Error("Missing access token");
-    }
+    if (requireAuth && !token) {
+        throw new ApiError("Missing access token", 401);
+    }

Also applies to: 96-98

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts` around lines 70 - 80,
Replace plain Error throws in the non-HTTP failure paths with ApiError so
callers can uniformly handle API errors: where the code currently throws new
Error("Response contained null/undefined value for expected Result<T>") and new
Error(apiRes.message || "API interaction failed") (and the similar throw at the
96-98 branch), construct and throw an ApiError instead (include the original
message and any available metadata like status or body if available), and ensure
ApiError is imported/used consistently alongside ApiResponse and Result<T>.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/roadmap.md`:
- Around line 48-49: Update the timeline entry that currently reads "🔮 **Março
2026+**: Fase 3 - Reviews, Assinaturas (Stripe), Agendamentos" so it does not
overlap the MVP date; replace it with a post-MVP phrasing such as "🔮 **Pós-MVP
(Abril 2026+)**: Fase 3 - Reviews, Assinaturas (Stripe), Agendamentos" (locate
the exact string "🔮 **Março 2026+**: Fase 3 - Reviews, Assinaturas (Stripe),
Agendamentos" in docs/roadmap.md and update that line accordingly).

In `@src/Modules/Users/API/Endpoints/Public/GetAuthProvidersEndpoint.cs`:
- Around line 15-16: O endpoint GetAuthProvidersEndpoint está retornando
Enum.GetValues<EAuthProvider>() (providers) que pode ser serializado como
inteiros; atualize o retorno para expor nomes de enum legíveis e estáveis (por
exemplo usando Enum.GetNames<EAuthProvider>() ou mapeando cada valor para sua
string) para evitar exposição de serialização numérica no contrato público;
altere a variável providers e o Results.Ok(...) para devolver explicitamente
strings dos nomes do enum.

In `@src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs`:
- Around line 69-76: Replace the non-existent DeleteUserAsync call with the
interface method DeactivateUserAsync on keycloakService (i.e., call
keycloakService.DeactivateUserAsync(keycloakResult.Value, cancellationToken)
inside KeycloakUserDomainService) and in the catch block log the caught
exception instead of swallowing it—use the class's logger instance (e.g.,
_logger or existing ILogger field) to emit an error/debug message including the
exception and context so compensation failures are recorded for diagnosis.

In `@src/Modules/Users/Tests/Builders/UserBuilder.cs`:
- Around line 24-36: The code declares a local "user" but then checks
"userResult" and later assigns "userResult.Value", causing a name mismatch and
also calls User.Create without the required keycloakId parameter; fix by
changing the call to User.Create to capture its result into a variable named
"userResult" (e.g., var userResult = User.Create(...)) and include the missing
keycloakId argument when calling User.Create (use the builder field or a newly
generated keycloakId), then use userResult.IsFailure and var user =
userResult.Value as currently intended.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx`:
- Around line 54-56: Normalize provider IDs immediately after fetching in the
setProviders flow: when you receive data in the block that calls setProviders,
map/transform each provider id to a canonical lowercase/known token (e.g.,
"google", "facebook" or use the Shared.Contracts enum/constant values) so later
checks like providers.includes("Google") and the kc_idp_hint construction use
the same normalized values; update the rendering code and sign-in hint logic
(the array used by providers.includes and wherever kc_idp_hint is built) to
expect and compare against those canonical tokens rather than display names, and
prefer importing the Shared.Contracts provider enum/constants to drive both
normalization and comparisons.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts`:
- Around line 106-113: The current truthy checks around the request body in
fetch-client (the `if (body)` that sets requestHeaders["Content-Type"] and the
`body: body ? JSON.stringify(body) : undefined` in the fetch call) drop valid
falsy JSON payloads (false, 0, ""). Update both checks to use explicit undefined
comparison (e.g., `body !== undefined`) so Content-Type is set and the body is
JSON-stringified when body is intentionally provided but falsy; keep the
fallback to undefined when body is truly absent.

---

Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx`:
- Around line 91-99: The handleSocialLogin function sets isLoading but only
clears it in the catch branch; modify handleSocialLogin (the async function
invoking signIn) to ensure setIsLoading(false) runs for every outcome by moving
the loading-clear into a finally block so that isLoading is reset whether signIn
redirects, throws, or returns; keep the existing catch logic that sets
setError(...) and maintain the early-return when isLoading is true.

---

Nitpick comments:
In `@src/Bootstrapper/MeAjudaAi.ApiService/Handlers/SelfOrAdminHandler.cs`:
- Around line 29-31: Replace the hardcoded "super-admin" string in the roles
check inside SelfOrAdminHandler with the shared role constant instead: locate
the conditional that compares roles to
MeAjudaAi.Shared.Utilities.UserRoles.Admin and the literal "super-admin", and
swap the literal for the corresponding shared constant (e.g.,
MeAjudaAi.Shared.Utilities.UserRoles.SuperAdmin or the Shared.Contracts
enum/constant that defines SuperAdmin) while keeping the
StringComparison.OrdinalIgnoreCase comparison; ensure both comparisons use the
same constant source to avoid drift.

In `@src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts`:
- Line 4: Replace the local HttpMethod type definition with the shared contract
enum/type from Shared.Contracts to avoid verb drift: remove the local "type
HttpMethod" in fetch-client.ts and import the shared HttpMethod symbol (e.g.,
Shared.Contracts.HttpMethod or the project’s Shared.Contracts export that
exposes HTTP verbs), then update any usages in this file (references to
HttpMethod) to use the imported symbol so the client stays aligned with
backend/shared definitions.
- Around line 70-80: Replace plain Error throws in the non-HTTP failure paths
with ApiError so callers can uniformly handle API errors: where the code
currently throws new Error("Response contained null/undefined value for expected
Result<T>") and new Error(apiRes.message || "API interaction failed") (and the
similar throw at the 96-98 branch), construct and throw an ApiError instead
(include the original message and any available metadata like status or body if
available), and ensure ApiError is imported/used consistently alongside
ApiResponse and Result<T>.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2489634 and 4d95fe7.

📒 Files selected for processing (16)
  • docs/roadmap.md
  • src/Bootstrapper/MeAjudaAi.ApiService/Handlers/SelfOrAdminHandler.cs
  • src/Contracts/Identity/Enums/EAuthProvider.cs
  • src/Modules/Users/API/Endpoints/Public/GetAuthProvidersEndpoint.cs
  • src/Modules/Users/API/Endpoints/UsersModuleEndpoints.cs
  • src/Modules/Users/Domain/Entities/User.cs
  • src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs
  • src/Modules/Users/Tests/Builders/UserBuilder.cs
  • src/Modules/Users/Tests/Infrastructure/Mocks/MockUserDomainService.cs
  • src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/layout/user-menu.tsx
  • src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts
  • src/Web/MeAjudaAi.Web.Customer/lib/api/response-utils.ts
  • tests/MeAjudaAi.Integration.Tests/Database/DbContextConcurrencyTests.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/Users/UserRepositoryIntegrationTests.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/Users/UsersIntegrationTests.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Web/MeAjudaAi.Web.Customer/lib/api/response-utils.ts

…handlers, and new customer/provider UI components.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Bootstrapper/MeAjudaAi.ApiService/Handlers/SelfOrAdminHandler.cs (1)

29-35: ⚠️ Potential issue | 🔴 Critical

Build failure: UserRoles.SuperAdmin does not exist.

The code at lines 29-31 references MeAjudaAi.Shared.Utilities.UserRoles.SuperAdmin, which is not defined in the UserRoles class. This causes a CS0117 compilation error and prevents the build from succeeding.

Add the missing constant to src/Shared/Utilities/UserRoles.cs:

🐛 Add missing SuperAdmin constant
 public static class UserRoles
 {
     /// <summary>
     /// Administrador com permissões elevadas - acesso total ao Admin Portal
     /// </summary>
     public const string Admin = "admin";
+
+    /// <summary>
+    /// Super administrador com permissões máximas do sistema
+    /// </summary>
+    public const string SuperAdmin = "super-admin";
 
     /// <summary>
     /// Gerente de provedores - pode criar, editar e deletar provedores
     /// </summary>
     public const string ProviderManager = "provider-manager";

Also update the AdminRoles array to include the new constant:

 public static readonly string[] AdminRoles =
 [
     Admin,
+    SuperAdmin,
     ProviderManager,
     DocumentReviewer,
     CatalogManager,
     Operator
 ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Bootstrapper/MeAjudaAi.ApiService/Handlers/SelfOrAdminHandler.cs` around
lines 29 - 35, The build fails because SelfOrAdminHandler.cs references
MeAjudaAi.Shared.Utilities.UserRoles.SuperAdmin which doesn't exist; fix by
adding a SuperAdmin constant in the UserRoles class (e.g., public const string
SuperAdmin = "SuperAdmin") and update the UserRoles.AdminRoles array to include
UserRoles.SuperAdmin so AdminRoles contains the new constant; ensure the symbol
names match exactly (UserRoles.SuperAdmin and AdminRoles) so
SelfOrAdminHandler's role checks compile.
♻️ Duplicate comments (2)
docs/roadmap.md (1)

2210-2211: ⚠️ Potential issue | 🟡 Minor

Ajustar referência “Post-MVP” que ainda aponta para Fevereiro/2026.

Com o alvo de MVP em 28 de Março de 2026 (Line 2210), a menção próxima de Fase 3+ em “Fevereiro 2026+” fica cronologicamente inválida. Atualize para um período pós-MVP (ex.: Abril 2026+).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap.md` around lines 2210 - 2211, Update the post-MVP timeline text
that still reads "Fevereiro 2026+" to a correct post-launch period (e.g., "Abril
2026+") so it aligns with the MVP Launch Target "MVP Launch Target: 28 de Março
de 2026" and the nearby "Fase 3+" mention; ensure the phrase "Fevereiro 2026+"
is replaced wherever it appears in the same section and adjust any adjacent date
references to remain consistent with the new post-MVP timeline.
src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs (1)

76-76: ⚠️ Potential issue | 🟡 Minor

Keep inline C# comments in Portuguese for consistency.

At Line [76], the comment is in English. Please translate it to Portuguese to match the project language policy for comments/XML docs.

✏️ Suggested wording
-                // Silence compensation failures to prevent masking the original validation error
+                // Silencia falhas de compensação para não mascarar o erro original de validação

Based on learnings: Comments and XML documentation (/// <summary>, <param>, etc.) should be in Portuguese across the C# codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs` at
line 76, Replace the English inline comment "// Silence compensation failures to
prevent masking the original validation error" with its Portuguese equivalent to
comply with project policy; locate the comment inside the
KeycloakUserDomainService class (in KeycloakUserDomainService.cs) near the
compensation/error handling block and change it to "// Silenciar falhas de
compensação para evitar mascarar o erro de validação original".
🧹 Nitpick comments (1)
src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx (1)

48-64: Provider fetch addresses previous sync concerns.

Fetching providers from the backend eliminates the frontend/backend drift risk previously flagged. Normalization to lowercase on line 55 ensures consistent matching.

Minor inconsistency: The fallback array includes "microsoft" and "apple", but the UI only renders buttons for Google and Facebook. Consider aligning the fallback with supported providers:

Optional fix
-                setProviders(["google", "facebook", "microsoft", "apple"]);
+                setProviders(["google", "facebook"]);

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx` around lines
48 - 64, The fallback provider list in the fetchProviders useEffect includes
"microsoft" and "apple" but the UI only renders Google and Facebook buttons;
update the fallback in fetchProviders (where setProviders is called) to match
supported providers (e.g., ["google","facebook"]) or alternatively add UI
support for "microsoft" and "apple" so providers array and rendered buttons stay
in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs`:
- Around line 69-72: The call to keycloakService.DeactivateUserAsync currently
ignores its Result and uses the incoming cancellationToken which can cancel the
compensating action; update the compensation block in KeycloakUserDomainService
to perform a best-effort deactivation by awaiting
DeactivateUserAsync(keycloakResult.Value, CancellationToken.None) (or a short
linked timeout token) and inspect the returned Result/response: if it indicates
failure, log an explicit error via the existing logger including
keycloakResult.Value and the failure details; apply the same change to the other
compensating call at the later block (lines 78-79) so non-exception failures are
logged rather than swallowed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/perfil/editar/page.tsx:
- Around line 22-25: The page currently calls authenticatedFetch using
session.accessToken without guarding for a missing token or auth error; add a
pre-check in the page component (where session and authenticatedFetch are used)
to verify session?.accessToken and absence of session.error and, if
missing/invalid, perform a redirect to the sign-in route (same behavior as the
profile page) instead of calling
authenticatedFetch(`/api/v1/users/${session.user.id}`, ...); ensure you
reference the existing session variable and authenticatedFetch call so the fetch
is only invoked when the token exists.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/perfil/page.tsx:
- Around line 24-27: The page currently calls authenticatedFetch using
session.accessToken without verifying the token; add a guard before calling
authenticatedFetch to check session?.accessToken (and session?.user?.id) and
handle the missing/invalid token by redirecting to the sign-in flow or returning
an authentication-specific response instead of performing the API call; update
the logic around the authenticatedFetch call so that when session.accessToken is
falsy you call the app's sign-in/redirect helper (or throw an auth redirect) and
only call authenticatedFetch(`/api/v1/users/${session.user.id}`, { token: token
}) when a valid token exists.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx`:
- Around line 65-66: The form currently hardcodes acceptedPrivacyPolicy: true
(symbol: acceptedPrivacyPolicy) which falsely records consent; remove the
hardcoded true and wire acceptedPrivacyPolicy to the checkbox/form field used
for Terms (the checkbox state/field rendered around line 225), ensuring the
form's initial values reflect false/undefined and the submit/validation logic
(the form submit handler and any validation rules) reads the actual checkbox
value instead of assuming consent; update any references in the component (e.g.,
initialValues, submit payload, and validation schema) to use the checkbox-bound
field name so consent is only recorded when the user explicitly checks it.

---

Outside diff comments:
In `@src/Bootstrapper/MeAjudaAi.ApiService/Handlers/SelfOrAdminHandler.cs`:
- Around line 29-35: The build fails because SelfOrAdminHandler.cs references
MeAjudaAi.Shared.Utilities.UserRoles.SuperAdmin which doesn't exist; fix by
adding a SuperAdmin constant in the UserRoles class (e.g., public const string
SuperAdmin = "SuperAdmin") and update the UserRoles.AdminRoles array to include
UserRoles.SuperAdmin so AdminRoles contains the new constant; ensure the symbol
names match exactly (UserRoles.SuperAdmin and AdminRoles) so
SelfOrAdminHandler's role checks compile.

---

Duplicate comments:
In `@docs/roadmap.md`:
- Around line 2210-2211: Update the post-MVP timeline text that still reads
"Fevereiro 2026+" to a correct post-launch period (e.g., "Abril 2026+") so it
aligns with the MVP Launch Target "MVP Launch Target: 28 de Março de 2026" and
the nearby "Fase 3+" mention; ensure the phrase "Fevereiro 2026+" is replaced
wherever it appears in the same section and adjust any adjacent date references
to remain consistent with the new post-MVP timeline.

In `@src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs`:
- Line 76: Replace the English inline comment "// Silence compensation failures
to prevent masking the original validation error" with its Portuguese equivalent
to comply with project policy; locate the comment inside the
KeycloakUserDomainService class (in KeycloakUserDomainService.cs) near the
compensation/error handling block and change it to "// Silenciar falhas de
compensação para evitar mascarar o erro de validação original".

---

Nitpick comments:
In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx`:
- Around line 48-64: The fallback provider list in the fetchProviders useEffect
includes "microsoft" and "apple" but the UI only renders Google and Facebook
buttons; update the fallback in fetchProviders (where setProviders is called) to
match supported providers (e.g., ["google","facebook"]) or alternatively add UI
support for "microsoft" and "apple" so providers array and rendered buttons stay
in sync.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d95fe7 and 78d7839.

📒 Files selected for processing (15)
  • docs/roadmap.md
  • src/Bootstrapper/MeAjudaAi.ApiService/Handlers/SelfOrAdminHandler.cs
  • src/Modules/Users/API/Endpoints/Public/GetAuthProvidersEndpoint.cs
  • src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs
  • src/Modules/Users/Tests/Builders/UserBuilder.cs
  • src/Web/MeAjudaAi.Web.Admin/packages.lock.json
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/editar/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/providers/dashboard-client.tsx
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-document-upload.ts
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts
  • src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts
💤 Files with no reviewable changes (1)
  • src/Web/MeAjudaAi.Web.Admin/packages.lock.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-document-upload.ts
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts
  • src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (4)
src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs (1)

69-72: ⚠️ Potential issue | 🟠 Major

Handle non-exception compensation failures explicitly and decouple rollback from request cancellation.

At Line [71], DeactivateUserAsync can fail via Result.IsFailure without throwing, but that failure is currently ignored. Also, using the incoming cancellationToken can cancel the compensation itself, leaving orphan users.

🔧 Suggested fix
         if (userResult.IsFailure)
         {
             try
             {
-                await keycloakService.DeactivateUserAsync(keycloakResult.Value, cancellationToken);
+                var deactivateResult = await keycloakService.DeactivateUserAsync(
+                    keycloakResult.Value,
+                    CancellationToken.None);
+
+                if (deactivateResult.IsFailure)
+                {
+                    logger.LogWarning(
+                        "Failed to deactivate Keycloak user {KeycloakId} during compensation: {Error}",
+                        keycloakResult.Value,
+                        deactivateResult.Error);
+                }
             }
             catch (Exception ex)
             {
                 logger.LogWarning(ex, "Failed to deactivate Keycloak user {KeycloakId} during compensation for local user creation failure.", keycloakResult.Value);
                 // Silence compensation failures to prevent masking the original validation error
             }
             return Result<User>.Failure(userResult.Error);
         }

Also applies to: 75-78

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs`
around lines 69 - 72, The DeactivateUserAsync call currently ignores
non-exception failures and uses the incoming cancellationToken which may cancel
the compensation; update the rollback logic in KeycloakUserDomainService so that
after calling keycloakService.DeactivateUserAsync(keycloakResult.Value, ...) you
check the returned Result/response for IsFailure and handle it (log the failure
and/or throw a specific exception or return a failed Result) instead of ignoring
it, and invoke the compensation with a non-cancelable or dedicated cancellation
token (e.g., CancellationToken.None or a new token sourced from a linked token
with its own timeout) so the rollback is not cancelled by the original request;
apply the same fix pattern to the similar block around the later
DeactivateUserAsync usage (the 75-78 area).
src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx (1)

91-99: ⚠️ Potential issue | 🟡 Minor

Clear stale auth errors before social login attempts.

A previous error remains visible while a new social auth attempt is in progress.

Suggested fix
     const handleSocialLogin = async (provider: string) => {
         if (isLoading) return;
+        setError("");
         setIsLoading(true);
         try {
             await signIn("keycloak", { callbackUrl }, { kc_idp_hint: provider });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx` around lines
91 - 99, In handleSocialLogin, clear any stale auth errors before starting a
social sign-in so previous error messages don't persist: when entering
handleSocialLogin (before early return and before setIsLoading(true)), call
setError(undefined) or setError("") to reset error state, then proceed with
setIsLoading(true) and the existing signIn("keycloak", { callbackUrl }, {
kc_idp_hint: provider })/try-catch/finally flow; ensure setError is used in the
catch to set the new error as currently implemented.
src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/page.tsx (1)

24-27: ⚠️ Potential issue | 🟠 Major

Protect the fetch with token/session-error guard.

Line 24-Line 27 still call the protected endpoint without guarding session.accessToken and session.error. Redirect to sign-in before fetch to avoid auth failures degrading into generic error UI.

🔧 Suggested fix
+    if (!session.accessToken || session.error) {
+        redirect("/auth/signin");
+    }
+
-        const token = session.accessToken;
-
         const data = await authenticatedFetch<MeAjudaAiModulesUsersApplicationDtosUserDto>(`/api/v1/users/${session.user.id}`, {
-            token: token
+            token: session.accessToken
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/perfil/page.tsx around lines 24 -
27, The code in page.tsx calls
authenticatedFetch(`/api/v1/users/${session.user.id}`) using session.accessToken
without guarding for session.accessToken or session.error; update the logic in
the component (where session is used) to check for session?.accessToken and
absence of session?.error before performing authenticatedFetch, and if missing
or errored, trigger a redirect to sign-in (or return the appropriate redirect
response) instead of calling authenticatedFetch so the protected endpoint is
never called with an invalid session.
src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/editar/page.tsx (1)

22-27: ⚠️ Potential issue | 🟠 Major

Guard auth state before protected fetch.

Line 22 uses session.accessToken without checking missing token or session.error. This still falls into a generic error state instead of re-auth flow; redirect before calling authenticatedFetch.

🔧 Suggested fix
+    if (!session.accessToken || session.error) {
+        redirect("/auth/signin");
+    }
+
-        const token = session.accessToken;
-
         const data = await authenticatedFetch<MeAjudaAiModulesUsersApplicationDtosUserDto>(`/api/v1/users/${session.user.id}`, {
-            token: token
+            token: session.accessToken
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/perfil/editar/page.tsx around
lines 22 - 27, The code reads session.accessToken and calls authenticatedFetch
without guarding auth state; update the logic around session, checking for
session?.accessToken and session?.error (or equivalent auth flags) before
calling authenticatedFetch(`/api/v1/users/${session.user.id}`, ...); if the
token is missing or session.error set, perform the re-auth/redirect flow (e.g.,
call your sign-in redirect helper or router push to login) and return early so
authenticatedFetch is never invoked with an invalid session; ensure you
reference the same symbols (session, session.accessToken, session.user.id,
authenticatedFetch) when modifying the function.
🧹 Nitpick comments (1)
docs/roadmap.md (1)

10-10: Sugestão: reduzir risco de nova divergência de status/datas.

Como o arquivo replica “status geral” em vários blocos, vale manter um bloco canônico (ex.: tabela de sprints) e referenciar os demais para minimizar drift futuro.

Based on learnings: For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups.

Also applies to: 103-103, 5088-5090

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap.md` at line 10, O bloco "Status Geral" está replicado e pode
divergir; consolidate num único bloco canônico (ex.: uma "tabela de sprints"
contendo colunas Sprint, Período, Status, Target) e altere as outras ocorrências
no documento para referências/redirecionamentos a esse bloco canônico (ex.: link
interno ou nota "ver Tabela de Sprints"); atualize as entradas atuais (ex.:
Sprint 8A/8B, MVP Target 28/Março/2026) para a nova tabela e remova duplicatas;
também alinhe o processo de revisão mencionado (commit-by-commit newest-first
com veredicto conciso e follow-ups) num breve parágrafo de procedimentos para
serem seguidos em futuras atualizações.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/SecurityHeadersMiddleware.cs`:
- Around line 43-53: SecurityHeadersMiddleware currently unconditionally assigns
headers using headers[header.Key] = header.Value and sets
headers.StrictTransportSecurity = HstsHeader, which overwrites headers set by
downstream middleware; change the loop to check headers.ContainsKey(header.Key)
and call headers.Append(header.Key, header.Value) only when the key is missing,
and for HSTS guard with if (ctx.Request.IsHttps && !_isDevelopment &&
!headers.ContainsKey("Strict-Transport-Security")) then call
headers.Append("Strict-Transport-Security", HstsHeader) so existing downstream
values are preserved (refer to StaticHeaders, headers, ctx.Request.IsHttps,
_isDevelopment, HstsHeader and the middleware's OnStarting callback).

In `@src/Shared/Utilities/UserRoles.cs`:
- Around line 8-11: The UserRoles.SuperAdmin constant is declared but not
included in the role registries, causing IsValidRole and IsAdminRole to return
false for "super-admin"; update the role collections (AllRoles and AdminRoles)
inside the UserRoles class to include UserRoles.SuperAdmin so that
IsValidRole("super-admin") and IsAdminRole("super-admin") behave correctly and
consistently across consumers.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx`:
- Around line 57-61: The catch block in the login form currently unconditionally
sets a hardcoded providers list, which should be restricted to non-production;
update the catch inside the fetch/providers useEffect (or function that calls
setProviders in the LoginForm component) to only call
setProviders(["google","facebook","microsoft","apple"]) when running in a
non-production environment (e.g., process.env.NODE_ENV !== "production" or a
local/DEV feature flag), otherwise leave providers empty (or setProviders([]))
and log the error so production does not render unavailable auth buttons.
- Line 155: The code currently falls back to a hardcoded localhost URL when
building the reset-password href; remove the unsafe default by using
process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER without the "http://localhost:8080/..."
fallback and ensure the code handles a missing issuer (e.g. fail-fast or render
a warning) before constructing the URL; update the href expression that
references process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER and
process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID (and the encodeURIComponent usage) so
it does not silently use localhost in production.

---

Duplicate comments:
In `@src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs`:
- Around line 69-72: The DeactivateUserAsync call currently ignores
non-exception failures and uses the incoming cancellationToken which may cancel
the compensation; update the rollback logic in KeycloakUserDomainService so that
after calling keycloakService.DeactivateUserAsync(keycloakResult.Value, ...) you
check the returned Result/response for IsFailure and handle it (log the failure
and/or throw a specific exception or return a failed Result) instead of ignoring
it, and invoke the compensation with a non-cancelable or dedicated cancellation
token (e.g., CancellationToken.None or a new token sourced from a linked token
with its own timeout) so the rollback is not cancelled by the original request;
apply the same fix pattern to the similar block around the later
DeactivateUserAsync usage (the 75-78 area).

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/perfil/editar/page.tsx:
- Around line 22-27: The code reads session.accessToken and calls
authenticatedFetch without guarding auth state; update the logic around session,
checking for session?.accessToken and session?.error (or equivalent auth flags)
before calling authenticatedFetch(`/api/v1/users/${session.user.id}`, ...); if
the token is missing or session.error set, perform the re-auth/redirect flow
(e.g., call your sign-in redirect helper or router push to login) and return
early so authenticatedFetch is never invoked with an invalid session; ensure you
reference the same symbols (session, session.accessToken, session.user.id,
authenticatedFetch) when modifying the function.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/perfil/page.tsx:
- Around line 24-27: The code in page.tsx calls
authenticatedFetch(`/api/v1/users/${session.user.id}`) using session.accessToken
without guarding for session.accessToken or session.error; update the logic in
the component (where session is used) to check for session?.accessToken and
absence of session?.error before performing authenticatedFetch, and if missing
or errored, trigger a redirect to sign-in (or return the appropriate redirect
response) instead of calling authenticatedFetch so the protected endpoint is
never called with an invalid session.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx`:
- Around line 91-99: In handleSocialLogin, clear any stale auth errors before
starting a social sign-in so previous error messages don't persist: when
entering handleSocialLogin (before early return and before setIsLoading(true)),
call setError(undefined) or setError("") to reset error state, then proceed with
setIsLoading(true) and the existing signIn("keycloak", { callbackUrl }, {
kc_idp_hint: provider })/try-catch/finally flow; ensure setError is used in the
catch to set the new error as currently implemented.

---

Nitpick comments:
In `@docs/roadmap.md`:
- Line 10: O bloco "Status Geral" está replicado e pode divergir; consolidate
num único bloco canônico (ex.: uma "tabela de sprints" contendo colunas Sprint,
Período, Status, Target) e altere as outras ocorrências no documento para
referências/redirecionamentos a esse bloco canônico (ex.: link interno ou nota
"ver Tabela de Sprints"); atualize as entradas atuais (ex.: Sprint 8A/8B, MVP
Target 28/Março/2026) para a nova tabela e remova duplicatas; também alinhe o
processo de revisão mencionado (commit-by-commit newest-first com veredicto
conciso e follow-ups) num breve parágrafo de procedimentos para serem seguidos
em futuras atualizações.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d95fe7 and 00becd1.

📒 Files selected for processing (17)
  • docs/roadmap.md
  • src/Bootstrapper/MeAjudaAi.ApiService/Handlers/SelfOrAdminHandler.cs
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/SecurityHeadersMiddleware.cs
  • src/Modules/Users/API/Endpoints/Public/GetAuthProvidersEndpoint.cs
  • src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs
  • src/Modules/Users/Tests/Builders/UserBuilder.cs
  • src/Shared/Utilities/UserRoles.cs
  • src/Web/MeAjudaAi.Web.Admin/packages.lock.json
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/editar/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/providers/dashboard-client.tsx
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-document-upload.ts
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts
  • src/Web/MeAjudaAi.Web.Customer/lib/api/fetch-client.ts
💤 Files with no reviewable changes (1)
  • src/Web/MeAjudaAi.Web.Admin/packages.lock.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-update-provider-profile.ts
  • src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx

Filipe Frigini added 2 commits March 2, 2026 17:21
…tion forms, and core security infrastructure, alongside project roadmap documentation.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx (1)

159-163: ⚠️ Potential issue | 🟡 Minor

Avoid dead "#" reset-password links when issuer is missing.

Using "#" creates a clickable control that does nothing meaningful. Prefer not rendering the link (or render disabled/help text) when config is absent.

Suggested fix
+    const resetPasswordHref = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER
+        ? `${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER}/login-actions/reset-credentials?client_id=${encodeURIComponent(process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID || "customer-app")}`
+        : null
...
                 <div className="text-center pt-1">
-                    <Link
-                        href={process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER
-                            ? `${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER}/login-actions/reset-credentials?client_id=${encodeURIComponent(process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID || "customer-app")}`
-                            : "#"}
-                        className="text-sm font-medium underline underline-offset-4 hover:text-primary"
-                    >
-                        Esqueci minha senha
-                    </Link>
+                    {resetPasswordHref && (
+                        <Link
+                            href={resetPasswordHref}
+                            className="text-sm font-medium underline underline-offset-4 hover:text-primary"
+                        >
+                            Esqueci minha senha
+                        </Link>
+                    )}
                 </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx` around lines
159 - 163, The reset-password Link in login-form.tsx currently renders as a dead
"#" when NEXT_PUBLIC_KEYCLOAK_ISSUER is missing; change to conditional rendering
so the clickable <Link> is only rendered when
process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER is present (building the URL with
encodeURIComponent(process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID ||
"customer-app")), otherwise render a non-clickable element (e.g., a <span> with
the same styling but disabled/low-opacity and/or helper text) so users aren't
presented with a misleading, inert link.
🧹 Nitpick comments (5)
src/Modules/Users/Tests/Unit/Infrastructure/Persistence/UserRepositoryTests.cs (1)

58-75: Consider asserting the actual KeycloakId value for consistency.

The assertion at line 75 only checks .NotBeNull(), which is weaker than asserting the actual value. Since the mock returns the same user object, you can verify the complete round-trip. This would be consistent with the pattern used in GetByKeycloakIdAsync_WithExistingKeycloakId_ShouldReturnUser (line 200).

💡 Suggested improvement
-        result.KeycloakId.Should().NotBeNull();
+        result.KeycloakId.Should().Be(user.KeycloakId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Users/Tests/Unit/Infrastructure/Persistence/UserRepositoryTests.cs`
around lines 58 - 75, Update the assertion to verify the exact KeycloakId value
returned by the mock instead of only checking NotBeNull: after arranging the
mock to return user, assert that result.KeycloakId equals user.KeycloakId (or
the specific Guid string used when building user) so the GetByIdAsync round-trip
is fully verified; keep references to _mockUserRepository, GetByIdAsync and the
user builder to locate the code to change.
src/Shared/Utilities/UserRoles.cs (1)

48-71: Consider adding a mapping utility or enum-to-role conversion helper.

The provider tier role strings (provider-standard, provider-silver, etc.) are logically aligned with EProviderTier enum values (Standard, Silver, Gold, Platinum), but there is no explicit conversion or validation mechanism linking them. While the current implementation maintains alignment through consistent naming conventions, adding a utility method to map between enum values and role strings would prevent future drift and improve maintainability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Utilities/UserRoles.cs` around lines 48 - 71, Add a small mapping
helper to centralize conversions between the EProviderTier enum and the role
constants defined in UserRoles (ProviderStandard, ProviderSilver, ProviderGold,
ProviderPlatinum); implement a method (e.g.,
UserRoles.FromProviderTier(EProviderTier) and/or
UserRoles.TryParseProviderRole(string, out EProviderTier)) that returns the
correct role string for a given EProviderTier and validates/parses role strings
back to enum values so callers use the helper instead of hardcoding
strings—update call sites to use these helpers to avoid drift between the enum
and the role constants.
src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/SecurityHeadersMiddleware.cs (1)

53-55: Use a constant for the Strict-Transport-Security header name.

This avoids duplicated string literals and reduces typo risk in future edits.

♻️ Suggested refactor
     private const string HstsHeader = "max-age=31536000; includeSubDomains";
+    private const string StrictTransportSecurityHeaderName = "Strict-Transport-Security";
...
-            if (ctx.Request.IsHttps && !_isDevelopment && !headers.ContainsKey("Strict-Transport-Security"))
+            if (ctx.Request.IsHttps && !_isDevelopment && !headers.ContainsKey(StrictTransportSecurityHeaderName))
             {
-                headers.Append("Strict-Transport-Security", HstsHeader);
+                headers.Append(StrictTransportSecurityHeaderName, HstsHeader);
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/SecurityHeadersMiddleware.cs`
around lines 53 - 55, The code uses the literal "Strict-Transport-Security" when
appending HSTS; introduce a single constant (e.g., a private static readonly
string HstsHeaderName or const string HstsHeaderName) in the
SecurityHeadersMiddleware class and replace the literal with that constant where
headers.Append("Strict-Transport-Security", HstsHeader) is called (and anywhere
else the header name is used) to avoid duplicated literals and typos while
keeping the existing HstsHeader value for the header value.
src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/editar/page.tsx (1)

22-25: Inline the temporary token variable.

token is single-use here, so inlining reduces noise.

♻️ Proposed cleanup
-        const token = session.accessToken;
-
         const data = await authenticatedFetch<MeAjudaAiModulesUsersApplicationDtosUserDto>(`/api/v1/users/${session.user.id}`, {
-            token: token
+            token: session.accessToken
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/perfil/editar/page.tsx around
lines 22 - 25, The local variable token is only used once; remove the temporary
const token and inline session.accessToken directly into the authenticatedFetch
call (the call that invokes
authenticatedFetch<MeAjudaAiModulesUsersApplicationDtosUserDto>(`/api/v1/users/${session.user.id}`,
{ token: token })). Update that call to pass token: session.accessToken (or
equivalent) and delete the unused const token declaration.
src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx (1)

53-55: Guard against rapid re-submit before disabled state applies.

A quick double-submit can trigger onSubmit twice before disabled={isLoading} rerenders. Add an early return guard at Line 54.

💡 Proposed fix
     async function onSubmit(data: RegisterCustomerSchema) {
+        if (isLoading) return;
         setIsLoading(true);
         try {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx`
around lines 53 - 55, The onSubmit handler (async function onSubmit) can be
invoked twice before the UI re-renders disabled={isLoading}; add an immediate
guard at the start of onSubmit that checks the isLoading flag and returns early
if true to prevent duplicate submissions, e.g., inside onSubmit check if
(isLoading) return; then proceed to setIsLoading(true) and the existing
try/catch/finally logic; reference the onSubmit function and the
isLoading/setIsLoading state to locate where to add the guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/roadmap.md`:
- Line 10: Add a stable explicit anchor for the sprint schedule and update the
two internal references to point to it: insert an HTML anchor with id
"cronograma-de-sprints" immediately before the section header "## 📅 Cronograma
de Sprints (Novembro 2025-Março 2026)" (or equivalent header) and change both
link fragments that currently target the failing anchors to use
"#cronograma-de-sprints" so MD051/link-fragments no longer triggers and
navigation works reliably.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx`:
- Around line 135-140: The phone input onChange handler currently always applies
a 5+4 grouping (v = v.replace(/(\d{5})(\d)/, "$1-$2")), which misformats
10-digit numbers; update the mask logic in the onChange handler (the function
using field.onChange) to branch on the raw digit count: if digits length >= 11
use the 5+4 pattern, otherwise use a 4+4 pattern for 10-digit/landline numbers,
then apply the existing area code formatting and call field.onChange with the
capped substring as before.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx`:
- Around line 112-151: Add explicit, programmatic labels for the email and
password inputs instead of relying on placeholders/icons: add a visually-hidden
label element (e.g., using a "sr-only" class) for the email input and another
for the password input, give each Input a unique id (e.g., id="email" and
id="password") and reference them from the corresponding label via htmlFor,
ensure the Mail and Lock icons are aria-hidden or decorative so they don't
duplicate label text, and keep the existing aria-label on the password toggle
Button (toggle controlled by showPassword) intact.

---

Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx`:
- Around line 159-163: The reset-password Link in login-form.tsx currently
renders as a dead "#" when NEXT_PUBLIC_KEYCLOAK_ISSUER is missing; change to
conditional rendering so the clickable <Link> is only rendered when
process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER is present (building the URL with
encodeURIComponent(process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID ||
"customer-app")), otherwise render a non-clickable element (e.g., a <span> with
the same styling but disabled/low-opacity and/or helper text) so users aren't
presented with a misleading, inert link.

---

Nitpick comments:
In
`@src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/SecurityHeadersMiddleware.cs`:
- Around line 53-55: The code uses the literal "Strict-Transport-Security" when
appending HSTS; introduce a single constant (e.g., a private static readonly
string HstsHeaderName or const string HstsHeaderName) in the
SecurityHeadersMiddleware class and replace the literal with that constant where
headers.Append("Strict-Transport-Security", HstsHeader) is called (and anywhere
else the header name is used) to avoid duplicated literals and typos while
keeping the existing HstsHeader value for the header value.

In
`@src/Modules/Users/Tests/Unit/Infrastructure/Persistence/UserRepositoryTests.cs`:
- Around line 58-75: Update the assertion to verify the exact KeycloakId value
returned by the mock instead of only checking NotBeNull: after arranging the
mock to return user, assert that result.KeycloakId equals user.KeycloakId (or
the specific Guid string used when building user) so the GetByIdAsync round-trip
is fully verified; keep references to _mockUserRepository, GetByIdAsync and the
user builder to locate the code to change.

In `@src/Shared/Utilities/UserRoles.cs`:
- Around line 48-71: Add a small mapping helper to centralize conversions
between the EProviderTier enum and the role constants defined in UserRoles
(ProviderStandard, ProviderSilver, ProviderGold, ProviderPlatinum); implement a
method (e.g., UserRoles.FromProviderTier(EProviderTier) and/or
UserRoles.TryParseProviderRole(string, out EProviderTier)) that returns the
correct role string for a given EProviderTier and validates/parses role strings
back to enum values so callers use the helper instead of hardcoding
strings—update call sites to use these helpers to avoid drift between the enum
and the role constants.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/perfil/editar/page.tsx:
- Around line 22-25: The local variable token is only used once; remove the
temporary const token and inline session.accessToken directly into the
authenticatedFetch call (the call that invokes
authenticatedFetch<MeAjudaAiModulesUsersApplicationDtosUserDto>(`/api/v1/users/${session.user.id}`,
{ token: token })). Update that call to pass token: session.accessToken (or
equivalent) and delete the unused const token declaration.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx`:
- Around line 53-55: The onSubmit handler (async function onSubmit) can be
invoked twice before the UI re-renders disabled={isLoading}; add an immediate
guard at the start of onSubmit that checks the isLoading flag and returns early
if true to prevent duplicate submissions, e.g., inside onSubmit check if
(isLoading) return; then proceed to setIsLoading(true) and the existing
try/catch/finally logic; reference the onSubmit function and the
isLoading/setIsLoading state to locate where to add the guard.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00becd1 and 36b5b1d.

📒 Files selected for processing (17)
  • docs/roadmap.md
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/SecurityHeadersMiddleware.cs
  • src/Modules/Users/Infrastructure/Services/KeycloakUserDomainService.cs
  • src/Modules/Users/Tests/Infrastructure/Mocks/MockKeycloakService.cs
  • src/Modules/Users/Tests/Integration/LocalDevelopmentServicesIntegrationTests.cs
  • src/Modules/Users/Tests/Integration/UserRepositoryIntegrationTests.cs
  • src/Modules/Users/Tests/Unit/Application/Mappers/UserMappersTests.cs
  • src/Modules/Users/Tests/Unit/Application/Queries/GetUserByIdQueryHandlerTests.cs
  • src/Modules/Users/Tests/Unit/Infrastructure/Events/Handlers/UserRegisteredDomainEventHandlerTests.cs
  • src/Modules/Users/Tests/Unit/Infrastructure/Persistence/UserRepositoryTests.cs
  • src/Modules/Users/Tests/Unit/Infrastructure/Services/KeycloakUserDomainServiceTests.cs
  • src/Modules/Users/Tests/Unit/Mocks/Services/MockKeycloakServiceTests.cs
  • src/Shared/Utilities/UserRoles.cs
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/editar/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Modules/Users/Tests/Unit/Infrastructure/Services/KeycloakUserDomainServiceTests.cs
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/page.tsx

…eaders middleware, and introduce new customer authentication UI components.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs (1)

147-160: ⚠️ Potential issue | 🟡 Minor

Add missing SuperAdmin test case to IsAdminRole_WithAdminRole_ShouldReturnTrue.

The AdminRoles collection includes UserRoles.SuperAdmin (verified as "super-admin"), but this test method doesn't include a test case for it.

🧪 Proposed fix
 [Theory]
+[InlineData("super-admin")]
 [InlineData("admin")]
 [InlineData("provider-manager")]
 [InlineData("document-reviewer")]
 [InlineData("catalog-manager")]
 [InlineData("operator")]
 public void IsAdminRole_WithAdminRole_ShouldReturnTrue(string role)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
147 - 160, The test IsAdminRole_WithAdminRole_ShouldReturnTrue is missing
coverage for the SuperAdmin role; update the Theory's InlineData attributes to
include UserRoles.SuperAdmin (i.e. add an InlineData("super-admin")) so the test
calls UserRoles.IsAdminRole with the super-admin value and asserts true,
ensuring AdminRoles (and specifically UserRoles.SuperAdmin) are validated by
this test.
🧹 Nitpick comments (3)
src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs (2)

57-60: Keep inline comments in Portuguese for consistency.

Line 57 and Line 60 comments are in English, while this codebase policy expects comments/XML docs in Portuguese.

Based on learnings: comments and XML documentation should be written in Portuguese across C# files, while logs remain in English.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs`
around lines 57 - 60, Troque os comentários em inglês nessa lógica de parsing do
nome para português: substitua as duas linhas de comentário acima de "var
nameParts = request.Name.Trim().Split(' ', 2);" e do comentário ao lado de "var
lastName = ..." por textos em português (por exemplo explicando que se divide o
nome em até duas partes e que, na ausência de sobrenome, usa-se o primeiro nome
como fallback). Altere apenas os comentários relacionados a request.Name,
nameParts, firstName e lastName para manter a consistência da base.

36-36: Replace hardcoded rate-limit policy string with a shared constant.

On Line 36, using a string literal ("provider-registration") can drift from central policy definitions. Prefer the shared constant to keep API and security config aligned.

Suggested fix
-            .RequireRateLimiting("provider-registration");
+            .RequireRateLimiting(RateLimitPolicies.ProviderRegistration);

Based on learnings: prefer reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs`
at line 36, Replace the hardcoded policy name in the
ProviderRegistrationEndpoints routing call by referencing the shared constant
from Shared.Contracts instead of the string literal; specifically, in
ProviderRegistrationEndpoints change
.RequireRateLimiting("provider-registration") to use the shared enum/constant
(e.g., Shared.Contracts.RateLimitPolicies.ProviderRegistration or the equivalent
constant name provided by Shared.Contracts) so the endpoint uses the centralized
policy identifier.
tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs (1)

60-72: Consider adding SuperAdmin constant value verification.

Since UserRoles.SuperAdmin is now included in the AllRoles (line 18) and AdminRoles (line 40) assertions, consider adding its expected string value verification here for completeness.

📝 Suggested addition
     // Assert
+    UserRoles.SuperAdmin.Should().Be("super-admin");
     UserRoles.Admin.Should().Be("admin");
     UserRoles.ProviderManager.Should().Be("provider-manager");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
60 - 72, Add an assertion verifying the string value of UserRoles.SuperAdmin to
the test; update the assertions block in UserRolesTests (near the existing
checks like UserRoles.Admin and UserRoles.ProviderManager) to include
UserRoles.SuperAdmin.Should().Be("super-admin") so the SuperAdmin constant is
explicitly validated alongside AllRoles and AdminRoles coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs`:
- Around line 115-117: The compensation currently only catches exceptions when
sending DeleteUserCommand, but it must also check the SendAsync result for
failures; after creating DeleteUserCommand(userResult.Value!.Id) and awaiting
commandDispatcher.SendAsync<DeleteUserCommand, Result>(...), inspect the
returned Result and if Result.IsFailure log an error and handle the failure
(e.g., retry, escalate, or record the orphaned identity for later cleanup)
instead of silently continuing; update the compensation block around
DeleteUserCommand to treat non-exceptional failure results the same way as
exceptions by logging the Result.Error (or similar) and taking the chosen
remediation path.
- Around line 54-55: The sanitized phone string (variable phone) can be empty
causing username = "provider_" collisions; in ProviderRegistrationEndpoints
(where request.PhoneNumber is sanitized) check that phone is not empty after
Regex.Replace and handle it explicitly: either return a BadRequest/validation
error referencing request.PhoneNumber or generate a safe fallback username
(e.g., include a GUID like $"provider_{Guid.NewGuid():N}") to avoid collisions;
update the code paths that use username to rely on this validated/fallback
value.

In `@src/Modules/Providers/Domain/Enums/ProviderTierExtensions.cs`:
- Around line 26-45: The TryParseRole method currently calls
role?.ToLowerInvariant() and compares to UserRoles constants which couples
parsing to casing and fails for surrounding whitespace; change it to trim the
input (role?.Trim()) and perform comparisons using case-insensitive string
equality (String.Equals with OrdinalIgnoreCase) against the UserRoles constants
so parsing is robust to casing changes and extra whitespace; update the switch
(or replace with if/else using String.Equals) in TryParseRole to use the
trimmed, un-normalized input and set the appropriate EProviderTier values
(EProviderTier.Standard, Silver, Gold, Platinum) or default as before.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx`:
- Around line 48-68: The async effect in useEffect calls fetchProviders which
awaits and then calls setProviders; guard against updating state after unmount
by adding a mounted flag (e.g., let isMounted = true) inside the effect, check
isMounted before any setProviders calls in fetchProviders (including the
fallback in the catch), and return a cleanup function that sets isMounted =
false to prevent stale updates; update references to setProviders within
fetchProviders and the catch block accordingly to only run when isMounted is
true.
- Around line 70-74: handleSubmit currently lacks a re-entry guard and can fire
multiple credential requests; in the handleSubmit function add an early return
that checks the isLoading state (e.g., if (isLoading) return) before proceeding
to prevent duplicate submissions, keeping the rest of the logic (setError,
setIsLoading(true), credential handling) unchanged.

---

Outside diff comments:
In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs`:
- Around line 147-160: The test IsAdminRole_WithAdminRole_ShouldReturnTrue is
missing coverage for the SuperAdmin role; update the Theory's InlineData
attributes to include UserRoles.SuperAdmin (i.e. add an
InlineData("super-admin")) so the test calls UserRoles.IsAdminRole with the
super-admin value and asserts true, ensuring AdminRoles (and specifically
UserRoles.SuperAdmin) are validated by this test.

---

Nitpick comments:
In
`@src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs`:
- Around line 57-60: Troque os comentários em inglês nessa lógica de parsing do
nome para português: substitua as duas linhas de comentário acima de "var
nameParts = request.Name.Trim().Split(' ', 2);" e do comentário ao lado de "var
lastName = ..." por textos em português (por exemplo explicando que se divide o
nome em até duas partes e que, na ausência de sobrenome, usa-se o primeiro nome
como fallback). Altere apenas os comentários relacionados a request.Name,
nameParts, firstName e lastName para manter a consistência da base.
- Line 36: Replace the hardcoded policy name in the
ProviderRegistrationEndpoints routing call by referencing the shared constant
from Shared.Contracts instead of the string literal; specifically, in
ProviderRegistrationEndpoints change
.RequireRateLimiting("provider-registration") to use the shared enum/constant
(e.g., Shared.Contracts.RateLimitPolicies.ProviderRegistration or the equivalent
constant name provided by Shared.Contracts) so the endpoint uses the centralized
policy identifier.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs`:
- Around line 60-72: Add an assertion verifying the string value of
UserRoles.SuperAdmin to the test; update the assertions block in UserRolesTests
(near the existing checks like UserRoles.Admin and UserRoles.ProviderManager) to
include UserRoles.SuperAdmin.Should().Be("super-admin") so the SuperAdmin
constant is explicitly validated alongside AllRoles and AdminRoles coverage.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36b5b1d and 9624d5c.

📒 Files selected for processing (9)
  • docs/roadmap.md
  • src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/SecurityHeadersMiddleware.cs
  • src/Modules/Providers/Domain/Enums/ProviderTierExtensions.cs
  • src/Modules/Users/Tests/Unit/Infrastructure/Persistence/UserRepositoryTests.cs
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/editar/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx
  • tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/perfil/editar/page.tsx
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/SecurityHeadersMiddleware.cs
  • src/Modules/Users/Tests/Unit/Infrastructure/Persistence/UserRepositoryTests.cs
  • src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx

…, provider registration endpoints, and user role unit tests.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs (1)

79-91: Add super-admin to IsValidRole theory inputs for full parity.

AllRoles now includes super-admin, but valid-role theory data still doesn’t exercise it (including mixed-case). Add both to close the gap.

♻️ Proposed patch
     [Theory]
+    [InlineData("super-admin")]
     [InlineData("admin")]
     [InlineData("provider-manager")]
@@
     [Theory]
+    [InlineData("SUPER-ADMIN")]
     [InlineData("ADMIN")]
     [InlineData("Provider-Manager")]

Also applies to: 100-109

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
79 - 91, The theory for IsValidRole_WithValidRole_ShouldReturnTrue is missing
the "super-admin" cases now present in AllRoles; update the test by adding
InlineData("super-admin") and InlineData("Super-Admin") to the list of inputs
(also mirror the same additions in the corresponding test block around lines
100-109), so the IsValidRole_WithValidRole_ShouldReturnTrue method exercises
both the lowercase and mixed-case super-admin variants.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs`:
- Around line 79-91: The theory for IsValidRole_WithValidRole_ShouldReturnTrue
is missing the "super-admin" cases now present in AllRoles; update the test by
adding InlineData("super-admin") and InlineData("Super-Admin") to the list of
inputs (also mirror the same additions in the corresponding test block around
lines 100-109), so the IsValidRole_WithValidRole_ShouldReturnTrue method
exercises both the lowercase and mixed-case super-admin variants.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9624d5c and 3296856.

📒 Files selected for processing (4)
  • src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs
  • src/Modules/Providers/Domain/Enums/ProviderTierExtensions.cs
  • src/Web/MeAjudaAi.Web.Customer/components/auth/login-form.tsx
  • tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Modules/Providers/Domain/Enums/ProviderTierExtensions.cs
  • src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ProviderRegistrationEndpoints.cs

… UI, Keycloak realm, and development environment script.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
src/Bootstrapper/MeAjudaAi.ApiService/MigrationExtensions.cs (1)

33-37: Make migration ordering fully deterministic.

Line 33 currently sorts only by priority. Add a stable tie-breaker (e.g., module name/full type name) so fallback modules (99) and equal priorities always execute in predictable order.

Deterministic ordering refactor
-        dbContextTypes = dbContextTypes.OrderBy(t => 
-        {
-            var moduleName = ExtractModuleName(t);
-            return modulePriority.TryGetValue(moduleName, out var p) ? p : 99;
-        }).ToList();
+        dbContextTypes = dbContextTypes
+            .OrderBy(t =>
+            {
+                var moduleName = ExtractModuleName(t);
+                return modulePriority.TryGetValue(moduleName, out var p) ? p : 99;
+            })
+            .ThenBy(t => ExtractModuleName(t), StringComparer.Ordinal)
+            .ThenBy(t => t.FullName, StringComparer.Ordinal)
+            .ToList();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Bootstrapper/MeAjudaAi.ApiService/MigrationExtensions.cs` around lines 33
- 37, The current dbContextTypes ordering uses only modulePriority which can
produce non-deterministic ties; update the sorting in the code that builds
dbContextTypes (the OrderBy block that calls ExtractModuleName and
modulePriority) to include a stable tie-breaker by chaining a secondary comparer
(e.g., ThenBy) on moduleName or the type's full name (t.FullName) so that items
with equal priority (including fallback 99) are ordered predictably; keep the
existing priority lookup (modulePriority.TryGetValue) and then apply
ThenBy(moduleName) or ThenBy(t => t.FullName) before ToList().
src/Aspire/MeAjudaAi.AppHost/Program.cs (1)

140-140: Remove debug logging or make it conditional.

This debug statement logs configuration values to console, which may expose configuration details in logs and provides limited value in production. Consider removing it or guarding with a debug flag.

🧹 Proposed fix
-        Console.WriteLine($"[Aspire Debug] GOOGLE_CLIENT_ID = '{builder.Configuration["GOOGLE_CLIENT_ID"]}'");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Aspire/MeAjudaAi.AppHost/Program.cs` at line 140, The Console.WriteLine
that prints builder.Configuration["GOOGLE_CLIENT_ID"] in Program.cs should be
removed or made conditional; locate the Console.WriteLine($"[Aspire Debug]
GOOGLE_CLIENT_ID = '{builder.Configuration["GOOGLE_CLIENT_ID"]}'") statement in
Program.cs (Program main/bootstrap) and either delete it or wrap it behind a
development-only check (e.g., builder.Environment.IsDevelopment() or a specific
debug configuration flag) so it does not run in production; if you need
visibility keep it as a logger.Debug/Trace call (not Console) without printing
secrets directly.
tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs (1)

236-251: Consider adding "super-admin" to non-provider role test data for completeness.

The test covers most admin roles but omits "super-admin". While not critical (the logic is sound), adding it would make the test exhaustive for all non-provider roles.

♻️ Optional: Add super-admin to test data
     [Theory]
+    [InlineData("super-admin")]
     [InlineData("admin")]
     [InlineData("provider-manager")]
     [InlineData("document-reviewer")]
     [InlineData("catalog-manager")]
     [InlineData("operator")]
     [InlineData("customer")]
     [InlineData("viewer")]
     public void IsProviderRole_WithNonProviderRole_ShouldReturnFalse(string role)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs` around lines
236 - 251, Add "super-admin" to the non-provider role test data for the
IsProviderRole unit test: update the theory's InlineData attributes used by the
IsProviderRole_WithNonProviderRole_ShouldReturnFalse test so one of the
InlineData entries is "super-admin", ensuring UserRoles.IsProviderRole is
exercised against that non-provider role as well.
src/Web/MeAjudaAi.Web.Customer/layout.css (1)

1-4: Prefer source CSS over committed generated Tailwind bundle

Lines 1-4 show this file is generated from ./app/globals.css. Keeping generated output in source creates noisy diffs and recurring lint errors that are not meaningful for code review.

Consider keeping globals.css as the reviewed source-of-truth and either:

  1. generating this file in build artifacts only, or
  2. excluding this file from stylelint/biome checks if it must be committed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/layout.css` around lines 1 - 4, This committed
file is a generated Tailwind bundle (layout.css) produced from ./app/globals.css
and should not be treated as source; remove the generated layout.css from the
repo (or stop committing it), add it to .gitignore, and update your build
pipeline to generate it only as a build artifact, and/or exclude the generated
file from style linting by adding an ignore pattern for the generated
header/filename in your linter config (e.g., stylelint/biome ignorePatterns for
layout.css or the Tailwind bundle comment) so that ./app/globals.css remains the
single source-of-truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/dev.ps1`:
- Around line 30-32: The current loop in scripts/dev.ps1 reads .env lines into
$name and $value and calls Set-Item with $value.Trim(), but it doesn't remove
surrounding quotes so values like KEY="value" keep the quotes; update the
ForEach-Object block (the Get-Content ... ForEach-Object pipeline that assigns
$name, $value) to strip matching leading and trailing single or double quotes
from $value after Trim() before calling Set-Item (handle empty values and
preserve inner quotes). Ensure you still split with Split('=', 2) and use the
same Set-Item path ("env:$name") but assign the cleaned/unquoted value instead
of the raw trimmed string.

In `@src/Aspire/MeAjudaAi.AppHost/Program.cs`:
- Around line 141-147: The Keycloak environment wiring uses
builder.Configuration[...] ?? "" which masks missing OAuth credentials and
causes silent runtime failures; instead, read each credential into local vars
from builder.Configuration (e.g., googleClientId, googleClientSecret,
facebookAppId, etc.), validate with string.IsNullOrWhiteSpace, and for any
missing credential either log a clear warning (including the provider name) or
fail fast; then only call keycloak.Keycloak.WithEnvironment for providers that
have valid non-empty values (or explicitly disable the provider in the Keycloak
config) so WithEnvironment isn't invoked with empty strings.

In `@src/Bootstrapper/MeAjudaAi.ApiService/MigrationExtensions.cs`:
- Around line 22-23: A linha de comentário acima da variável modulePriority em
MigrationExtensions.cs está em inglês ("Ensure ServiceCatalogs runs before
Providers (cross-module SQL dependency in migrations)"); substitua esse
comentário por um equivalente em português, por exemplo explicando que o módulo
ServiceCatalogs deve rodar antes de Providers devido à dependência SQL entre
módulos, mantendo o contexto entre parênteses; altere apenas o texto do
comentário e não o código ao redor (procure a variável modulePriority para
localizar o local).

In `@src/Web/MeAjudaAi.Web.Customer/layout.css`:
- Around line 979-1002: Several border utility classes (.border-border,
.border-destructive, .border-gray-100/200/300, .border-green-100/200,
.border-input) hardcode light-mode colors and bypass theme tokens; replace their
literal hex values with the corresponding CSS custom properties (e.g., use
var(--color-border), var(--color-destructive), var(--color-gray-100) etc.) so
they respect the dark-mode token overrides defined in the theme, and do the same
audit/fix for nearby utilities mentioned (.bg-surface, .bg-popover,
.text-muted-foreground) to ensure all semantic utilities reference theme tokens
instead of fixed colors.
- Around line 2157-2162: Remove the duplicate malformed utility block for the
class .\[\\u0026_svg\]\:size-5 that uses the invalid selector &:is(\u0026 svg);
locate the duplicate definition (the one containing &:is(\u0026 svg)) and delete
it, keeping the correct existing rule that uses the valid selector & svg so the
size-5 SVG utility remains defined only once.

---

Nitpick comments:
In `@src/Aspire/MeAjudaAi.AppHost/Program.cs`:
- Line 140: The Console.WriteLine that prints
builder.Configuration["GOOGLE_CLIENT_ID"] in Program.cs should be removed or
made conditional; locate the Console.WriteLine($"[Aspire Debug] GOOGLE_CLIENT_ID
= '{builder.Configuration["GOOGLE_CLIENT_ID"]}'") statement in Program.cs
(Program main/bootstrap) and either delete it or wrap it behind a
development-only check (e.g., builder.Environment.IsDevelopment() or a specific
debug configuration flag) so it does not run in production; if you need
visibility keep it as a logger.Debug/Trace call (not Console) without printing
secrets directly.

In `@src/Bootstrapper/MeAjudaAi.ApiService/MigrationExtensions.cs`:
- Around line 33-37: The current dbContextTypes ordering uses only
modulePriority which can produce non-deterministic ties; update the sorting in
the code that builds dbContextTypes (the OrderBy block that calls
ExtractModuleName and modulePriority) to include a stable tie-breaker by
chaining a secondary comparer (e.g., ThenBy) on moduleName or the type's full
name (t.FullName) so that items with equal priority (including fallback 99) are
ordered predictably; keep the existing priority lookup
(modulePriority.TryGetValue) and then apply ThenBy(moduleName) or ThenBy(t =>
t.FullName) before ToList().

In `@src/Web/MeAjudaAi.Web.Customer/layout.css`:
- Around line 1-4: This committed file is a generated Tailwind bundle
(layout.css) produced from ./app/globals.css and should not be treated as
source; remove the generated layout.css from the repo (or stop committing it),
add it to .gitignore, and update your build pipeline to generate it only as a
build artifact, and/or exclude the generated file from style linting by adding
an ignore pattern for the generated header/filename in your linter config (e.g.,
stylelint/biome ignorePatterns for layout.css or the Tailwind bundle comment) so
that ./app/globals.css remains the single source-of-truth.

In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs`:
- Around line 236-251: Add "super-admin" to the non-provider role test data for
the IsProviderRole unit test: update the theory's InlineData attributes used by
the IsProviderRole_WithNonProviderRole_ShouldReturnFalse test so one of the
InlineData entries is "super-admin", ensuring UserRoles.IsProviderRole is
exercised against that non-provider role as well.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3dbf1cd5-e960-46a8-99a9-6dd303ad1c2b

📥 Commits

Reviewing files that changed from the base of the PR and between 3296856 and 22a65e6.

📒 Files selected for processing (10)
  • infrastructure/keycloak/realms/meajudaai-realm.dev.json
  • scripts/dev.ps1
  • src/Aspire/MeAjudaAi.AppHost/Program.cs
  • src/Bootstrapper/MeAjudaAi.ApiService/MigrationExtensions.cs
  • src/Modules/Documents/API/Extensions.cs
  • src/Modules/Providers/API/Extensions.cs
  • src/Modules/ServiceCatalogs/API/Extensions.cs
  • src/Modules/Users/API/Extensions.cs
  • src/Web/MeAjudaAi.Web.Customer/layout.css
  • tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs
💤 Files with no reviewable changes (4)
  • src/Modules/Documents/API/Extensions.cs
  • src/Modules/Users/API/Extensions.cs
  • src/Modules/Providers/API/Extensions.cs
  • src/Modules/ServiceCatalogs/API/Extensions.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • infrastructure/keycloak/realms/meajudaai-realm.dev.json

Filipe Frigini added 2 commits March 5, 2026 15:31
…vider registration pages, establish initial Aspire AppHost, and configure CI workflow.
…ustomer web app, alongside Aspire AppHost and development environment setup.
@frigini
Copy link
Owner Author

frigini commented Mar 5, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
src/Web/MeAjudaAi.Web.Customer/app/globals.css (1)

34-41: Minor: Dark mode foreground tokens use hardcoded values instead of var(--foreground).

In light mode, --popover-foreground, --card-foreground, and --accent-foreground reference var(--foreground), but their dark mode counterparts use the hardcoded value #ededed. While functionally equivalent, this inconsistency could cause divergence if --foreground is later changed.

♻️ Suggested fix for consistency
     --popover: `#1a1a1a`;
-    --popover-foreground: `#ededed`;
+    --popover-foreground: var(--foreground);
     --card: `#1a1a1a`;
-    --card-foreground: `#ededed`;
+    --card-foreground: var(--foreground);
     --muted: `#262626`;
     --muted-foreground: `#a3a3a3`;
     --accent: `#262626`;
-    --accent-foreground: `#ededed`;
+    --accent-foreground: var(--foreground);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/globals.css` around lines 34 - 41, Dark
mode CSS variables --popover-foreground, --card-foreground, and
--accent-foreground are hardcoded to `#ededed`; replace those hardcoded values
with var(--foreground) so they mirror the light-mode definitions and remain
consistent if --foreground changes (update the dark-mode rule that currently
sets --popover-foreground, --card-foreground, and --accent-foreground).
infrastructure/keycloak/themes/meajudaai/login/resources/css/login.css (2)

196-208: Add explicit keyboard focus style for #updateProfile.

Line 197-208 define default/hover states, but there is no explicit :focus-visible treatment for keyboard navigation.

Suggested patch
 `#updateProfile` {
     background: transparent !important;
     border: 2px solid var(--primary-blue) !important;
     color: var(--primary-blue) !important;
     box-shadow: none !important;
+    transition: all 0.2s ease;
 }
 
 `#updateProfile`:hover {
     background: rgba(57, 88, 115, 0.05) !important;
     border-color: var(--primary-blue-dark) !important;
     color: var(--primary-blue-dark) !important;
 }
+
+#updateProfile:focus-visible {
+    outline: 2px solid var(--primary-blue-dark);
+    outline-offset: 2px;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/keycloak/themes/meajudaai/login/resources/css/login.css`
around lines 196 - 208, The `#updateProfile` button lacks an explicit keyboard
focus style; add a :focus-visible rule for `#updateProfile` that applies a clear
accessible focus indicator (e.g., a visible outline or ring using
var(--primary-blue-dark) and optionally a subtle box-shadow) while preserving
existing transparent background and border; ensure the rule uses :focus-visible
(not just :focus) so mouse interactions are unaffected and include !important
only if necessary to override existing styles in login.css.

205-205: Prefer tokenized color over hard-coded RGB in hover background.

Line 205 hard-codes rgba(57, 88, 115, 0.05), which can drift from theme tokens later. Consider deriving this from a variable to keep palette changes centralized.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/keycloak/themes/meajudaai/login/resources/css/login.css` at
line 205, The hard-coded background rgba(57, 88, 115, 0.05) should be replaced
with a theme token: create or reuse a CSS variable (e.g. --ma-login-hover-bg) in
your theme root/variables and then update the rule that currently has
background: rgba(57, 88, 115, 0.05) !important; to background:
var(--ma-login-hover-bg, rgba(57,88,115,0.05)) !important; so palette changes
stay centralized (define the variable once and reference it in the login.css
rule).
src/Aspire/MeAjudaAi.AppHost/Program.cs (1)

140-177: Extract social provider credential wiring into one helper.

The three blocks are structurally identical; consolidating them will reduce drift and keep warning behavior consistent across providers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Aspire/MeAjudaAi.AppHost/Program.cs` around lines 140 - 177, The three
repeated blocks wiring OAuth credentials should be extracted into a single
helper (e.g., ConfigureSocialProvider or AddSocialProviderEnv) that takes the
provider name and configuration keys, reads builder.Configuration for the client
id/secret, calls keycloak.Keycloak.WithEnvironment for the two variables when
present, and logs the consistent warning ("⚠️ WARNING: {Provider} OAuth
credentials are missing. {Provider} Login may fail.") when either value is
missing; replace the inline Google/Facebook/Instagram blocks with three calls to
this helper so the WithEnvironment usage and warning behavior are centralized
and identical.
scripts/dev.ps1 (1)

30-31: Consider relaxing the .env key parser for spacing variants.

Current filter ^[\w-]+= skips lines like KEY = value or lines with leading whitespace. If you want broader .env compatibility, trim $name and allow optional whitespace around =.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/dev.ps1` around lines 30 - 31, The current line-filter regex in the
Get-Content pipeline is too strict and skips keys with leading/trailing
whitespace around the name or the '='; update the Where-Object match to allow
optional leading whitespace and optional spaces around the '=' (e.g., use a
pattern like '^\s*[\w-]+\s*=') and after splitting with $_.Split('=',2) trim
both $name and $value (call $name.Trim() and $value.Trim()) so keys like " KEY =
value " are accepted; refer to $envFilePath, Get-Content, Where-Object, the
Split call, and the $name/$value variables to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Aspire/MeAjudaAi.AppHost/Program.cs`:
- Around line 166-177: Program.cs currently injects INSTAGRAM_CLIENT_ID and
INSTAGRAM_CLIENT_SECRET via variables instagramClientId and
instagramClientSecret and calls keycloak.Keycloak.WithEnvironment(...), but the
dev realm JSON has no Instagram IdP so credentials are unused; either add an
Instagram identity provider entry to the realm import
(infrastructure/keycloak/realms/meajudaai-realm.dev.json) matching those
environment keys, or remove the instagramClientId/instagramClientSecret block
(the keycloak.Keycloak.WithEnvironment calls) until an Instagram IdP is added so
you don’t inject unused credentials.

In
`@src/Web/MeAjudaAi.Web.Customer/app/`(main)/cadastro/prestador/perfil/endereco/page.tsx:
- Around line 64-67: In handleCepBlur, normalize the CEP by stripping non-digit
chars from e.target.value and only call fetchAddress when the normalized CEP has
exactly 8 digits; pass the normalized value to fetchAddress (and update any
dependent state/input value if necessary) so malformed or formatted CEPs (with
dashes/spaces) do not trigger lookup and ViaCEP always receives an 8-digit
string.
- Around line 259-279: The city and state FormField inputs (name="city" and
name="state") are marked readOnly which prevents users from correcting required
fields in addressSchema if the CEP lookup fails; update the render logic in the
FormField for "city" and "state" to allow manual editing when the lookup didn't
populate values (e.g., conditionally set readOnly based on a lookup success flag
or the presence of form.getValues('city')/form.getValues('state')) or remove the
readOnly attribute entirely so users can input values; keep existing validation
(addressSchema) and UI classes, but ensure any visual “readonly” styling is only
applied when the lookup truly succeeded.

In
`@src/Web/MeAjudaAi.Web.Customer/app/`(main)/cadastro/prestador/perfil/page.tsx:
- Around line 72-77: Step 2/3 are being marked complete too easily because
hasAddress and hasDocuments only check superficial truthiness
(profile.businessProfile?.primaryAddress?.street and documents.length), so
update those checks to validate real, non-placeholder data: change hasAddress to
require the primaryAddress object and required fields (e.g., street, number,
city, postalCode) be non-empty and not placeholder values (trimmed strings like
"-", "N/A", "unknown"), and change hasDocuments to require at least one document
entry in profile.documents with a real type and an uploaded/valid file indicator
(e.g., fileUrl present or status === "uploaded") rather than just length; keep
step2Completed/step3Completed semantics but combine these stricter
hasAddress/hasDocuments checks with isPendingVerification only (do not rely on
placeholder fields or isVerified/isSuspended to mark steps complete).

---

Nitpick comments:
In `@infrastructure/keycloak/themes/meajudaai/login/resources/css/login.css`:
- Around line 196-208: The `#updateProfile` button lacks an explicit keyboard
focus style; add a :focus-visible rule for `#updateProfile` that applies a clear
accessible focus indicator (e.g., a visible outline or ring using
var(--primary-blue-dark) and optionally a subtle box-shadow) while preserving
existing transparent background and border; ensure the rule uses :focus-visible
(not just :focus) so mouse interactions are unaffected and include !important
only if necessary to override existing styles in login.css.
- Line 205: The hard-coded background rgba(57, 88, 115, 0.05) should be replaced
with a theme token: create or reuse a CSS variable (e.g. --ma-login-hover-bg) in
your theme root/variables and then update the rule that currently has
background: rgba(57, 88, 115, 0.05) !important; to background:
var(--ma-login-hover-bg, rgba(57,88,115,0.05)) !important; so palette changes
stay centralized (define the variable once and reference it in the login.css
rule).

In `@scripts/dev.ps1`:
- Around line 30-31: The current line-filter regex in the Get-Content pipeline
is too strict and skips keys with leading/trailing whitespace around the name or
the '='; update the Where-Object match to allow optional leading whitespace and
optional spaces around the '=' (e.g., use a pattern like '^\s*[\w-]+\s*=') and
after splitting with $_.Split('=',2) trim both $name and $value (call
$name.Trim() and $value.Trim()) so keys like " KEY = value " are accepted; refer
to $envFilePath, Get-Content, Where-Object, the Split call, and the $name/$value
variables to locate the change.

In `@src/Aspire/MeAjudaAi.AppHost/Program.cs`:
- Around line 140-177: The three repeated blocks wiring OAuth credentials should
be extracted into a single helper (e.g., ConfigureSocialProvider or
AddSocialProviderEnv) that takes the provider name and configuration keys, reads
builder.Configuration for the client id/secret, calls
keycloak.Keycloak.WithEnvironment for the two variables when present, and logs
the consistent warning ("⚠️ WARNING: {Provider} OAuth credentials are missing.
{Provider} Login may fail.") when either value is missing; replace the inline
Google/Facebook/Instagram blocks with three calls to this helper so the
WithEnvironment usage and warning behavior are centralized and identical.

In `@src/Web/MeAjudaAi.Web.Customer/app/globals.css`:
- Around line 34-41: Dark mode CSS variables --popover-foreground,
--card-foreground, and --accent-foreground are hardcoded to `#ededed`; replace
those hardcoded values with var(--foreground) so they mirror the light-mode
definitions and remain consistent if --foreground changes (update the dark-mode
rule that currently sets --popover-foreground, --card-foreground, and
--accent-foreground).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 716e608d-1a8f-4a74-a543-7e8c2dca0433

📥 Commits

Reviewing files that changed from the base of the PR and between 22a65e6 and fa7702a.

📒 Files selected for processing (12)
  • .github/workflows/pr-validation.yml
  • .gitignore
  • infrastructure/keycloak/themes/meajudaai/login/resources/css/login.css
  • scripts/dev.ps1
  • src/Aspire/MeAjudaAi.AppHost/Program.cs
  • src/Bootstrapper/MeAjudaAi.ApiService/MigrationExtensions.cs
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/perfil/documentos/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/perfil/endereco/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/perfil/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/app/globals.css
  • tests/MeAjudaAi.Shared.Tests/Unit/Utilities/UserRolesTests.cs
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Bootstrapper/MeAjudaAi.ApiService/MigrationExtensions.cs

…ion and implement the initial provider registration flow in the customer web application.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/perfil/page.tsx (1)

71-79: ⚠️ Potential issue | 🟡 Minor

Step completion checks are still slightly permissive.

At Line 71-79, hasAddress does not validate state, and hasDocuments treats any truthy fileUrl as valid (including whitespace). This can still mark steps complete prematurely.

Suggested tightening
-    const hasAddress = primaryAddress &&
+    const hasAddress = !!(primaryAddress &&
         primaryAddress.street?.trim() && primaryAddress.street !== "-" && primaryAddress.street !== "N/A" && primaryAddress.street !== "unknown" &&
         primaryAddress.number?.trim() && primaryAddress.number !== "-" && primaryAddress.number !== "N/A" &&
         primaryAddress.city?.trim() && primaryAddress.city !== "-" && primaryAddress.city !== "N/A" &&
-        primaryAddress.zipCode?.trim() && primaryAddress.zipCode !== "-" && primaryAddress.zipCode !== "N/A";
+        primaryAddress.state?.trim() && primaryAddress.state !== "-" && primaryAddress.state !== "N/A" &&
+        primaryAddress.zipCode?.trim() && primaryAddress.zipCode !== "-" && primaryAddress.zipCode !== "N/A");
 
-    const hasDocuments = profile.documents?.length > 0 &&
-        profile.documents.some(doc => doc.documentType && (doc.status === EDocumentStatus.Uploaded || doc.fileUrl));
+    const hasDocuments = (profile.documents?.length ?? 0) > 0 &&
+        profile.documents.some(doc =>
+            !!doc.documentType &&
+            (doc.status === EDocumentStatus.Uploaded || !!doc.fileUrl?.trim())
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/cadastro/prestador/perfil/page.tsx
around lines 71 - 79, hasAddress is missing validation for primaryAddress.state
and hasDocuments treats any truthy fileUrl (including whitespace) as valid;
update the hasAddress check to include primaryAddress.state?.trim() and ensure
state is not "-" / "N/A" / "unknown", and tighten hasDocuments by trimming
doc.fileUrl (e.g., Boolean(doc.fileUrl?.trim())) when deciding validity
alongside checking doc.status === EDocumentStatus.Uploaded; locate the
expressions named hasAddress and hasDocuments in page.tsx and apply these
stricter checks to avoid marking steps complete prematurely.
🧹 Nitpick comments (1)
scripts/dev.ps1 (1)

33-34: Redundant Trim() call.

Line 33 already trims $parts[1] into $value, so line 34's $value.Trim() is redundant.

♻️ Suggested simplification
     $name = $parts[0].Trim()
-    $value = $parts[1].Trim()
-    $cleanValue = $value.Trim()
+    $cleanValue = $parts[1].Trim()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/dev.ps1` around lines 33 - 34, The assignment unnecessarily calls
Trim() twice: $parts[1] is already trimmed into $value, so change the code that
sets $cleanValue (or simplify both lines) to avoid the redundant $value.Trim()
call — either set $cleanValue = $value or inline the trim as $cleanValue =
$parts[1].Trim(); update the symbols $parts, $value, and $cleanValue
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/cadastro/prestador/page.tsx:
- Around line 229-230: The page component contains inconsistent legal route
usages in multiple Link hrefs (some use "/termos-de-uso" and
"/politica-de-privacidade", others use "/termos" and "/privacidade"); pick a
single canonical pair (e.g., "/termos-de-uso" and "/politica-de-privacidade")
and update every Link in this component (the FormLabel/Link elements that render
the "Aceito os ..." and similar consent lines) to use those canonical hrefs
while preserving target, rel and className attributes so all user flows point to
the same legal pages.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts`:
- Around line 11-20: The hook's mutationFn currently forwards the full
RegisterProviderRequest (in use-register-provider mutationFn and the
authenticatedFetch call) but the backend derives email from the token and only
accepts optional phone; update the payload to match the backend contract by
mapping the incoming RegisterProviderRequest to a narrower request shape (e.g.,
Omit<RegisterProviderRequest, 'email'> or an explicit CreateProvider payload
containing only the allowed fields such as phone and other permitted properties)
before calling authenticatedFetch("/api/v1/providers/become"). Replace the
direct use of RegisterProviderRequest as the fetch body, update the mutationFn
signature/type accordingly, and import/reuse the shared enums/constants from
Shared.Contracts where applicable to keep Web aligned with backend/shared types.

---

Duplicate comments:
In
`@src/Web/MeAjudaAi.Web.Customer/app/`(main)/cadastro/prestador/perfil/page.tsx:
- Around line 71-79: hasAddress is missing validation for primaryAddress.state
and hasDocuments treats any truthy fileUrl (including whitespace) as valid;
update the hasAddress check to include primaryAddress.state?.trim() and ensure
state is not "-" / "N/A" / "unknown", and tighten hasDocuments by trimming
doc.fileUrl (e.g., Boolean(doc.fileUrl?.trim())) when deciding validity
alongside checking doc.status === EDocumentStatus.Uploaded; locate the
expressions named hasAddress and hasDocuments in page.tsx and apply these
stricter checks to avoid marking steps complete prematurely.

---

Nitpick comments:
In `@scripts/dev.ps1`:
- Around line 33-34: The assignment unnecessarily calls Trim() twice: $parts[1]
is already trimmed into $value, so change the code that sets $cleanValue (or
simplify both lines) to avoid the redundant $value.Trim() call — either set
$cleanValue = $value or inline the trim as $cleanValue = $parts[1].Trim();
update the symbols $parts, $value, and $cleanValue accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9958b486-d3ad-47f7-8017-639f48ca00b8

📥 Commits

Reviewing files that changed from the base of the PR and between fa7702a and 9b083be.

📒 Files selected for processing (9)
  • infrastructure/keycloak/themes/meajudaai/login/resources/css/login.css
  • scripts/dev.ps1
  • src/Aspire/MeAjudaAi.AppHost/Program.cs
  • src/Modules/Providers/Application/Extensions.cs
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/perfil/endereco/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/perfil/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/app/globals.css
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • infrastructure/keycloak/themes/meajudaai/login/resources/css/login.css

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/perfil/page.tsx (1)

68-83: ⚠️ Potential issue | 🟠 Major

Step completion logic doesn't account for Active or Suspended providers.

If a provider has Active or Suspended status but their address/document data is incomplete due to a backend issue or lazy loading, step2Completed and step3Completed will be false. This causes verified providers to incorrectly see "Preencher Endereço" or "Enviar Documentos" buttons.

🔧 Proposed fix
 const isPendingVerification = profile.status === EProviderStatus.PendingDocumentVerification;
+const isVerified = profile.status === EProviderStatus.Active;
+const isSuspended = profile.status === EProviderStatus.Suspended;

 const primaryAddress = profile.businessProfile?.primaryAddress;
 const hasAddress = primaryAddress &&
     primaryAddress.street?.trim() && primaryAddress.street !== "-" && primaryAddress.street !== "N/A" && primaryAddress.street !== "unknown" &&
     primaryAddress.number?.trim() && primaryAddress.number !== "-" && primaryAddress.number !== "N/A" &&
     primaryAddress.city?.trim() && primaryAddress.city !== "-" && primaryAddress.city !== "N/A" &&
     primaryAddress.state?.trim() && primaryAddress.state !== "-" && primaryAddress.state !== "N/A" && primaryAddress.state !== "unknown" &&
     primaryAddress.zipCode?.trim() && primaryAddress.zipCode !== "-" && primaryAddress.zipCode !== "N/A";

 const hasDocuments = profile.documents?.length > 0 &&
     profile.documents.some(doc => doc.documentType && (doc.status === EDocumentStatus.Uploaded || doc.fileUrl?.trim()));

 // Estados derivados para etapas concluídas com base na solicitação do usuário
-const step2Completed = !!hasAddress || isPendingVerification;
-const step3Completed = !!hasDocuments || isPendingVerification;
+const step2Completed = !!hasAddress || isPendingVerification || isVerified || isSuspended;
+const step3Completed = !!hasDocuments || isPendingVerification || isVerified || isSuspended;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/cadastro/prestador/perfil/page.tsx
around lines 68 - 83, The step completion logic currently uses
isPendingVerification to short-circuit step2Completed and step3Completed but
misses cases where profile.status is Active or Suspended; update the check so
that step2Completed and step3Completed also evaluate to true when profile.status
=== EProviderStatus.Active or profile.status === EProviderStatus.Suspended (you
can extend isPendingVerification or create a new flag like
isVerifiedOrSuspended), ensuring step2Completed and step3Completed compute as
!!hasAddress || isVerifiedOrSuspended and !!hasDocuments ||
isVerifiedOrSuspended; adjust references to EProviderStatus,
isPendingVerification (or the new flag), step2Completed and step3Completed
accordingly.
🧹 Nitpick comments (1)
scripts/dev.ps1 (1)

26-43: Consider saving this script as UTF-8 with BOM.

This block contains non-ASCII text, and static analysis reported PSUseBOMForUnicodeEncodedFile; adding BOM avoids mojibake/parsing issues in older PowerShell hosts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/dev.ps1` around lines 26 - 43, The script contains non-ASCII
characters (emoji and Portuguese text) which triggers
PSUseBOMForUnicodeEncodedFile; to fix, re-save the file with UTF-8 with BOM
encoding so PowerShell hosts correctly recognize the encoding; locate the file
that uses envFilePath and the block that reads and sets env variables
(Test-Path, Get-Content, Set-Item) and re-encode the file in your editor/IDE (or
use a tool/command to write UTF-8 with BOM) so the BOM is present when the
script runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/roadmap.md`:
- Line 73: Update the sprint status text that currently reads "**📅 Sprint 8B em
andamento**: Fevereiro/Março de 2026" to reflect the correct state now that the
period ended on 4 March 2026 (e.g., change "em andamento" to "concluído" or
"atrasado"), update any corresponding date ranges if needed, and update the
"Última atualização" field mentioned elsewhere in the document; apply the same
correction to the other occurrences noted (the lines containing the same sprint
status/labels at the referenced locations).

In `@scripts/dev.ps1`:
- Around line 30-40: The loader currently keeps inline comments in values;
modify the parsing inside the Get-Content loop (where $parts, $value,
$cleanValue are set and before Set-Item) to strip inline comments: detect the
first # that is not inside matching quotes and truncate the value at that
position, or if simpler, trim trailing whitespace then remove " #..." only when
the # is outside quotes; then trim again and only afterwards remove surrounding
quotes (the existing StartsWith/EndsWith logic) and assign to Set-Item. Use the
existing $value/$cleanValue variables and update that block so quoted values
containing # remain intact while inline comments are dropped.

---

Duplicate comments:
In
`@src/Web/MeAjudaAi.Web.Customer/app/`(main)/cadastro/prestador/perfil/page.tsx:
- Around line 68-83: The step completion logic currently uses
isPendingVerification to short-circuit step2Completed and step3Completed but
misses cases where profile.status is Active or Suspended; update the check so
that step2Completed and step3Completed also evaluate to true when profile.status
=== EProviderStatus.Active or profile.status === EProviderStatus.Suspended (you
can extend isPendingVerification or create a new flag like
isVerifiedOrSuspended), ensuring step2Completed and step3Completed compute as
!!hasAddress || isVerifiedOrSuspended and !!hasDocuments ||
isVerifiedOrSuspended; adjust references to EProviderStatus,
isPendingVerification (or the new flag), step2Completed and step3Completed
accordingly.

---

Nitpick comments:
In `@scripts/dev.ps1`:
- Around line 26-43: The script contains non-ASCII characters (emoji and
Portuguese text) which triggers PSUseBOMForUnicodeEncodedFile; to fix, re-save
the file with UTF-8 with BOM encoding so PowerShell hosts correctly recognize
the encoding; locate the file that uses envFilePath and the block that reads and
sets env variables (Test-Path, Get-Content, Set-Item) and re-encode the file in
your editor/IDE (or use a tool/command to write UTF-8 with BOM) so the BOM is
present when the script runs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: afc04a44-defd-4bba-a9a2-e7e73d2c3a36

📥 Commits

Reviewing files that changed from the base of the PR and between 9b083be and 40ce4fb.

📒 Files selected for processing (5)
  • docs/roadmap.md
  • scripts/dev.ps1
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/perfil/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-register-provider.ts
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/page.tsx

…omprehensive project roadmap, and add a new development script.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
docs/roadmap.md (1)

1533-1536: ⚠️ Potential issue | 🟡 Minor

Update remaining Sprint 8B statuses to avoid contradictions.

These lines still show “EM ANDAMENTO”, but other updated sections now state Sprint 8B was finalized on 4 de Março de 2026 (e.g., Line 73 and Line 5081). Since today is 5 de Março de 2026, this is stale/inconsistent.

Please normalize all Sprint 8B references to the same final status (e.g., ✅ CONCLUÍDO).

Also applies to: 2154-2155, 2196-2196

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap.md` around lines 1533 - 1536, Update all Sprint 8B status
annotations that still read "EM ANDAMENTO" to the normalized final status (e.g.,
"✅ CONCLUÍDO"); specifically locate and replace the heading "### 🔄 Sprint 8B -
Authentication & Onboarding Flow (EM ANDAMENTO)" and any other occurrences
referenced (around the headings/lines noted) so they consistently reflect the
concluded status across the document (also fix the other occurrences mentioned
near lines 2154-2155 and 2196). Ensure the text and any nearby date ranges
remain unchanged except for the status label so all Sprint 8B references match
the finalized state.
src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/perfil/page.tsx (1)

70-76: ⚠️ Potential issue | 🟡 Minor

Normalize address fields before placeholder comparison.

hasAddress trims for emptiness but compares raw values for placeholders. Inputs like " - " or " Unknown " can still pass and mark steps as completed.

🔧 Suggested fix
-    const hasAddress = primaryAddress &&
-        primaryAddress.street?.trim() && primaryAddress.street !== "-" && primaryAddress.street !== "N/A" && primaryAddress.street !== "unknown" &&
-        primaryAddress.number?.trim() && primaryAddress.number !== "-" && primaryAddress.number !== "N/A" &&
-        primaryAddress.city?.trim() && primaryAddress.city !== "-" && primaryAddress.city !== "N/A" &&
-        primaryAddress.state?.trim() && primaryAddress.state !== "-" && primaryAddress.state !== "N/A" && primaryAddress.state !== "unknown" &&
-        primaryAddress.zipCode?.trim() && primaryAddress.zipCode !== "-" && primaryAddress.zipCode !== "N/A";
+    const normalize = (value?: string | null) => value?.trim().toLowerCase() ?? "";
+    const isPlaceholder = (value: string) => value === "-" || value === "n/a" || value === "unknown";
+
+    const hasAddress = !!primaryAddress && (() => {
+        const street = normalize(primaryAddress.street);
+        const number = normalize(primaryAddress.number);
+        const city = normalize(primaryAddress.city);
+        const state = normalize(primaryAddress.state);
+        const zipCode = normalize(primaryAddress.zipCode);
+
+        return (
+            !!street && !isPlaceholder(street) &&
+            !!number && !isPlaceholder(number) &&
+            !!city && !isPlaceholder(city) &&
+            !!state && !isPlaceholder(state) &&
+            !!zipCode && !isPlaceholder(zipCode)
+        );
+    })();

Also applies to: 83-84

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/cadastro/prestador/perfil/page.tsx
around lines 70 - 76, The hasAddress check uses raw values for placeholder
comparisons; normalize each address field first (trim and toLowerCase) and then
compare against a set of placeholder values (e.g., "-", "n/a", "unknown", "") to
determine emptiness. Update the logic around primaryAddress and hasAddress:
derive normalized variables (e.g., streetNorm, numberNorm, cityNorm, stateNorm,
zipCodeNorm) from primaryAddress.* using ?.trim()?.toLowerCase() and then check
that each normalized value is not in the placeholder set; apply the same
normalization pattern to the other similar address checks referenced (lines
83-84) so all placeholder variants with whitespace or case differences are
handled consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docs/roadmap.md`:
- Around line 1533-1536: Update all Sprint 8B status annotations that still read
"EM ANDAMENTO" to the normalized final status (e.g., "✅ CONCLUÍDO");
specifically locate and replace the heading "### 🔄 Sprint 8B - Authentication &
Onboarding Flow (EM ANDAMENTO)" and any other occurrences referenced (around the
headings/lines noted) so they consistently reflect the concluded status across
the document (also fix the other occurrences mentioned near lines 2154-2155 and
2196). Ensure the text and any nearby date ranges remain unchanged except for
the status label so all Sprint 8B references match the finalized state.

In
`@src/Web/MeAjudaAi.Web.Customer/app/`(main)/cadastro/prestador/perfil/page.tsx:
- Around line 70-76: The hasAddress check uses raw values for placeholder
comparisons; normalize each address field first (trim and toLowerCase) and then
compare against a set of placeholder values (e.g., "-", "n/a", "unknown", "") to
determine emptiness. Update the logic around primaryAddress and hasAddress:
derive normalized variables (e.g., streetNorm, numberNorm, cityNorm, stateNorm,
zipCodeNorm) from primaryAddress.* using ?.trim()?.toLowerCase() and then check
that each normalized value is not in the placeholder set; apply the same
normalization pattern to the other similar address checks referenced (lines
83-84) so all placeholder variants with whitespace or case differences are
handled consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f7e075e-b826-4a07-90d4-f53d23dbab7a

📥 Commits

Reviewing files that changed from the base of the PR and between 40ce4fb and ca2d9c1.

📒 Files selected for processing (3)
  • docs/roadmap.md
  • scripts/dev.ps1
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/cadastro/prestador/perfil/page.tsx

…r registration page in the customer web app.
@github-actions
Copy link

github-actions bot commented Mar 5, 2026

📊 Code Coverage Report

Coverage: 90.90% (extracted from Cobertura (Aggregated Direct))

📈 Coverage Details

  • Coverage badges: Coverage
  • Minimum threshold: 80% (warning) / 90% (good)
  • Report format: Auto-detected from OpenCover/Cobertura XML files
  • Coverage source: Cobertura (Aggregated Direct)

📋 Coverage Analysis

  • Line Coverage: Shows percentage of code lines executed during tests
  • Branch Coverage: Shows percentage of code branches/conditions tested
  • Complexity: Code complexity metrics for maintainability

🎯 Quality Gates

  • Pass: Coverage ≥ 90%
  • ⚠️ Warning: Coverage 80-89%
  • Fail: Coverage < 80%

📁 Artifacts

  • Coverage reports: Available in workflow artifacts
  • Test results: TRX files with detailed test execution data

This comment is updated automatically on each push to track coverage trends.

@frigini frigini merged commit 810f348 into master Mar 5, 2026
11 checks passed
@frigini frigini deleted the feature/sprint-8b-auth-onboarding branch March 5, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant