Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0a1f6a3
feat: implement Providers and Search Providers modules, including ent…
Mar 13, 2026
4b77f32
feat: Introduce Provider entity, SearchProviders module, and slug gen…
Mar 17, 2026
9cf226a
feat: Implement public provider retrieval by ID or slug, including ne…
Mar 17, 2026
848fda8
feat: Implement public provider profiles accessible by ID or slug, su…
Mar 17, 2026
8fdf42c
feat: implement provider search page with filtering, geocoding, and r…
Mar 17, 2026
04b8b9a
feat: Introduce `SearchableProvider` entity for optimized search and …
Mar 18, 2026
a6285c6
feat: Add SearchProviders integration test base with Testcontainers a…
Mar 18, 2026
850f2a6
feat: Add SearchProviders integration test base with Testcontainers P…
Mar 18, 2026
71d3640
feat: Introduce `SearchableProvider` entity for search optimization a…
Mar 18, 2026
3658caf
feat: Introduce `SearchableProvider` entity for search optimization a…
Mar 18, 2026
e127974
Merge from master and use master versions for conflicts
Mar 18, 2026
d7a0a8a
feat: Implement user management module with Keycloak integration and …
Mar 18, 2026
6647285
feat: Implement customer registration with Keycloak integration, add …
Mar 18, 2026
b3337f1
feat: Add unit tests for GetPublicProviderByIdOrSlugQueryHandler, int…
Mar 18, 2026
76d4ae9
feat: Add slug field to Provider and SearchableProvider entities.
Mar 18, 2026
0dea388
feat: Introduce `SearchableProvider` entity and enhance public provid…
Mar 18, 2026
22aecb7
test: add unit tests for GetPublicProviderByIdOrSlugQueryHandler cove…
Mar 18, 2026
0830eaa
feat: Introduce SearchableProvider entity and add comprehensive unit …
Mar 18, 2026
6dc9cb0
test: add ProviderBuilder and unit tests for GetPublicProviderByIdOrS…
Mar 18, 2026
5abf621
test: Add unit and end-to-end tests for Users, Providers, and SearchP…
Mar 19, 2026
85406d7
feat: add unit tests for SearchableProvider entity, API extensions, a…
Mar 19, 2026
a49c3dc
feat: add unit tests for the Users module API extensions and SearchPr…
Mar 19, 2026
f5199f2
test(Users/API): add unit tests for AddUsersModule extension method
Mar 19, 2026
c26cd53
test: add unit tests for Users module API extension methods
Mar 19, 2026
6777bee
feat: Initialize the MeAjudaAi.Web.Provider Next.js application with …
Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/roadmap-current.md
Original file line number Diff line number Diff line change
Expand Up @@ -589,8 +589,8 @@ Durante o processo de atualização automática de dependências pelo Dependabot

---

### Sprint 8C - Provider Web App (React + NX) (19 Mar - 1 Abr 2026)
- Create `apps/provider-web`.
### 🔄 Sprint 8C - Provider Web App (React + NX) (19 Mar - 1 Abr 2026)
- **Scaffolding `apps/provider-web`** (Em andamento)
- Implement registration steps (Upload, Dashboard).
- **Slug Implementation**: Replace IDs with Slugs for SEO/Security.

Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -1589,7 +1589,7 @@ Get-ChildItem -Recurse -Include *.cs | Select-String "record "

---

### ⏳ Sprint 8C - Provider Web App (React + NX)
### ▶️ Sprint 8C - Provider Web App (React + NX) - ACTIVE

**Periodo Estimado**: 19 Mar - 1 Abr 2026
**Foco**: App de Administração de Perfil para Prestadores
Expand Down
262 changes: 262 additions & 0 deletions prompts/design-react-project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
# Conversão de Design para Componentes React

Analise o design anexado (screenshot ou frame do Figma) e converta para componentes React seguindo os padrões abaixo.

---

## Stack

- **React 19** (sem `forwardRef`)
- **TypeScript** strict
- **Tailwind CSS v4** com `@theme` e CSS variables
- **Base UI React** (`@base-ui/react`) para componentes headless
- **Tailwind Variants** (`tailwind-variants`) para variantes
- **Tailwind Merge** (`tailwind-merge`) para merge de classes
- **Lucide React** ou **Phosphor Icons** para ícones

---

## Nomenclatura

- Arquivos: **lowercase com hífens** → `user-card.tsx`, `use-modal.ts`
- **Sempre named exports**, nunca default export
- Não criar barrel files (`index.ts`) para pastas internas

---

## Estrutura de Componente

```tsx
import { tv, type VariantProps } from "tailwind-variants";
import { twMerge } from "tailwind-merge";
import type { ComponentProps } from "react";

export const buttonVariants = tv({
base: [
"inline-flex cursor-pointer items-center justify-center font-medium rounded-lg border transition-colors",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
],
variants: {
variant: {
primary:
"border-primary bg-primary text-primary-foreground hover:bg-primary-hover",
secondary:
"border-border bg-secondary text-secondary-foreground hover:bg-muted",
ghost:
"border-transparent bg-transparent text-muted-foreground hover:text-foreground",
destructive:
"border-destructive bg-destructive text-destructive-foreground hover:bg-destructive/90",
},
size: {
sm: "h-6 px-2 gap-1.5 text-xs [&_svg]:size-3",
md: "h-7 px-3 gap-2 text-sm [&_svg]:size-3.5",
lg: "h-9 px-4 gap-2.5 text-base [&_svg]:size-4",
},
},
defaultVariants: { variant: "primary", size: "md" },
});

export interface ButtonProps
extends ComponentProps<"button">, VariantProps<typeof buttonVariants> {}

export function Button({
className,
variant,
size,
disabled,
children,
...props
}: ButtonProps) {
return (
<button
type="button"
data-slot="button"
data-disabled={disabled ? "" : undefined}
className={twMerge(buttonVariants({ variant, size }), className)}
disabled={disabled}
{...props}
>
{children}
</button>
);
}
```

---

## Compound Components

```tsx
import { twMerge } from "tailwind-merge";
import type { ComponentProps } from "react";

export interface CardProps extends ComponentProps<"div"> {}

export function Card({ className, ...props }: CardProps) {
return (
<div
data-slot="card"
className={twMerge(
"bg-surface flex flex-col gap-6 rounded-xl border border-border p-6 shadow-sm",
className,
)}
{...props}
/>
);
}

export function CardHeader({ className, ...props }: ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={twMerge("flex flex-col gap-1.5", className)}
{...props}
/>
);
}

export function CardTitle({ className, ...props }: ComponentProps<"h3">) {
return (
<h3
data-slot="card-title"
className={twMerge("text-lg font-semibold", className)}
{...props}
/>
);
}

export function CardContent({ className, ...props }: ComponentProps<"div">) {
return <div data-slot="card-content" className={className} {...props} />;
}
```

---

## Cores (CSS Variables)

```css
bg-surface, bg-surface-raised → fundos
bg-primary, bg-secondary, bg-muted → ações/estados
bg-destructive → erros/danger

text-foreground → texto principal
text-foreground-subtle → texto secundário
text-muted-foreground → texto desabilitado
text-primary-foreground → texto em bg primary

border-border, border-input → bordas padrão
border-primary, border-destructive → bordas de destaque

ring-ring → focus ring
```

---

## TypeScript

```tsx
// ✅ Estender ComponentProps + VariantProps
export interface ButtonProps
extends ComponentProps<"button">, VariantProps<typeof buttonVariants> {}

// ✅ Import type para tipos
import type { ComponentProps } from "react";
import type { VariantProps } from "tailwind-variants";

// ❌ Não usar React.FC nem any
```

---

## Padrões Importantes

```tsx
// Sempre usar twMerge
className={twMerge('classes-base', className)}

// Sempre usar data-slot
<div data-slot="card">

// Estados com data-attributes
data-disabled={disabled ? '' : undefined}
className="data-[disabled]:opacity-50 data-[selected]:bg-primary"

// Focus visible
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring'

// Ícones com tamanho
<Check className="size-4" />
'[&_svg]:size-3.5' // em variantes

// Botões de ícone precisam de aria-label
<button aria-label="Fechar"><X className="size-4" /></button>

// Props spread no final
{...props}
```

---

## Base UI (componentes headless)

```tsx
// Dialog
import * as Dialog from "@base-ui/react/dialog";
<Dialog.Root>
<Dialog.Portal>
<Dialog.Backdrop />
<Dialog.Popup />
</Dialog.Portal>
</Dialog.Root>;

// Tabs
import * as Tabs from "@base-ui/react/tabs";
<Tabs.Root>
<Tabs.List>
<Tabs.Tab />
</Tabs.List>
<Tabs.Panel />
</Tabs.Root>;

// Select
import * as Select from "@base-ui/react/select";
<Select.Root>
<Select.Trigger />
<Select.Portal>
<Select.Popup>
<Select.Item />
</Select.Popup>
</Select.Portal>
</Select.Root>;

// Menu
import * as Menu from "@base-ui/react/menu";
<Menu.Root>
<Menu.Trigger />
<Menu.Portal>
<Menu.Popup>
<Menu.Item />
</Menu.Popup>
</Menu.Portal>
</Menu.Root>;
```

---

## Checklist

- [ ] Arquivo lowercase com hífens
- [ ] Named export
- [ ] `ComponentProps<'elemento'>` + `VariantProps`
- [ ] Variantes com `tv()`, classes com `twMerge()`
- [ ] `data-slot` para identificação
- [ ] Estados via `data-[state]:`
- [ ] Cores do tema (não hardcoded)
- [ ] Focus visible em interativos
- [ ] `aria-label` em botões de ícone
- [ ] `{...props}` no final

---

Agora analise o design anexado e gere o código do componente.
7 changes: 7 additions & 0 deletions src/Aspire/MeAjudaAi.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ private static void ConfigureTestingEnvironment(IDistributedApplicationBuilder b
Console.Error.WriteLine("Please set MEAJUDAAI_DB_PASS to the database password in your CI environment.");
Environment.Exit(1);
}
// Suppress S2068: intentional hardcoded dev/test default credential, only used in local/dev scenarios; CI requires env var
#pragma warning disable S2068
testDbPassword = "test123";
#pragma warning restore S2068
}

var postgresql = builder.AddMeAjudaAiPostgreSQL(options =>
Expand Down Expand Up @@ -109,7 +112,9 @@ private static void ConfigureDevelopmentEnvironment(IDistributedApplicationBuild
Console.Error.WriteLine("Please set DB_PASSWORD to the database password in your CI environment.");
Environment.Exit(1);
}
#pragma warning disable S2068
dbPassword = "test123";
#pragma warning restore S2068
}
var includePgAdminStr = Environment.GetEnvironmentVariable("INCLUDE_PGADMIN") ?? "true";
var includePgAdmin = !bool.TryParse(includePgAdminStr, out var pgAdminResult) || pgAdminResult;
Expand All @@ -133,7 +138,9 @@ private static void ConfigureDevelopmentEnvironment(IDistributedApplicationBuild
var keycloakSettings = new MeAjudaAi.AppHost.Options.MeAjudaAiKeycloakOptions
{
AdminUsername = "admin",
#pragma warning disable S2068
AdminPassword = "admin123",
#pragma warning restore S2068
DatabaseHost = "postgres-local",
DatabasePort = "5432",
DatabaseName = mainDatabase,
Expand Down
8 changes: 4 additions & 4 deletions src/Aspire/MeAjudaAi.AppHost/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"version": 2,
"dependencies": {
"net10.0": {
"Aspire.Dashboard.Sdk.linux-x64": {
"Aspire.Dashboard.Sdk.win-x64": {
"type": "Direct",
"requested": "[13.1.0, )",
"resolved": "13.1.0",
"contentHash": "gIvMR7NdVGw+mUpK9qZsGuuYfDp4CHvBDLlykSSP9pCh5XGlMgDNR3uOMfvKVlqH1hAsOuecvxEcfQ9kjufrWw=="
"contentHash": "rrcsI8cankYCiUlj4Ev+os9uKcutUCv+9kvHQt85RiUX/ewXsloFZy0/depKWrzdJkdJuoTbYFRlSe43TKq6aQ=="
},
"Aspire.Hosting.AppHost": {
"type": "Direct",
Expand Down Expand Up @@ -199,11 +199,11 @@
"System.IO.Hashing": "9.0.10"
}
},
"Aspire.Hosting.Orchestration.linux-x64": {
"Aspire.Hosting.Orchestration.win-x64": {
"type": "Direct",
"requested": "[13.1.0, )",
"resolved": "13.1.0",
"contentHash": "V8u8ukncoflImciXeHG03x1pWbOiR4z1bIl2lwOKgdy7/JshFrlEaabaNuGmAIZFdWEM/fsc/hVDjecQyHF2aQ=="
"contentHash": "3w2UahEauTq719LPJ/BCySh31kz26sfjuOkRF5E4VYy1Q3xLRV43+OIGI3C5sy8feUHjrYz+DDq3DQn/2fu+4g=="
},
"Aspire.Hosting.PostgreSQL": {
"type": "Direct",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Security.Claims;
using MeAjudaAi.ApiService.Endpoints;
using MeAjudaAi.ApiService.Middleware;
using MeAjudaAi.ApiService.Middlewares;
using MeAjudaAi.ApiService.Options;
using MeAjudaAi.ApiService.Services.Authentication;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace MeAjudaAi.ApiService.Middleware;
namespace MeAjudaAi.ApiService.Middlewares;

/// <summary>
/// Middleware para adicionar Content Security Policy headers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace MeAjudaAi.ApiService.Middleware;
namespace MeAjudaAi.ApiService.Middlewares;


/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ public Task InvokeAsync(HttpContext context)
var headers = ctx.Response.Headers;

// Adiciona cabeçalhos de segurança estáticos eficientemente
#pragma warning disable S3267 // Loops should be simplified with "Where" LINQ method - avoiding LINQ allocations on hot path as requested
foreach (var header in StaticHeaders)
{
if (!headers.ContainsKey(header.Key))
{
headers.Append(header.Key, header.Value);
}
}
#pragma warning restore S3267

// HSTS apenas em produção e HTTPS - usando verificação de ambiente em cache
if (ctx.Request.IsHttps && !_isDevelopment && !headers.ContainsKey(HstsHeaderName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace MeAjudaAi.Contracts.Modules.Providers.DTOs;
public sealed record ModuleProviderBasicDto(
Guid Id,
string Name,
string Slug,
string Email,
string ProviderType,
string VerificationStatus,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace MeAjudaAi.Contracts.Modules.Providers.DTOs;
public sealed record ModuleProviderDto(
Guid Id,
string Name,
string Slug,
string Email,
string Document,
[property: JsonPropertyName("type")]
Expand Down
Loading