Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
109 changes: 109 additions & 0 deletions .agent/rules/nuxt4-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
trigger: always_on
---

---

## applyTo: '\*\*'

# Guia de Projeto — Nuxt 4 (estrutura + padrões de código)

⚠️ **Atenção**
Este documento é um **guia baseado em boas práticas e na documentação oficial**.
**Sempre siga as orientações do desenvolvedor responsável pelo projeto.**
Não é uma regra imutável — serve como referência para manter consistência, legibilidade e escalabilidade.

---

## 1) Estrutura de pastas (Nuxt 4)

Sempre respeitar a estrutura abaixo ao criar **novos arquivos/pastas**:

my-nuxt-app/
├─ app/
│ ├─ assets/ # fontes, ícones, imagens processadas, CSS global (se necessário)
│ ├─ components/ # componentes de UI (pequenos, reusáveis, sem lógica de dados)
│ ├─ composables/ # funções reativas (useX) e estados compartilhados (useState)
│ ├─ layouts/ # layouts de páginas (header/footer etc.)
│ ├─ middleware/ # middlewares de rota (auth, guards, etc.)
│ ├─ pages/ # rotas baseadas em arquivos
│ ├─ plugins/ # registros de libs (client/server), injeções de dependência
│ ├─ utils/ # funções puras e helpers sem reatividade
│ ├─ app.vue # shell do app
│ ├─ app.config.ts # configurações do app
│ └─ error.vue # página de erro global
├─ content/ # opcional - conteúdo estático/MD
├─ public/ # arquivos estáticos servidos como raiz (/)
├─ shared/
│ ├─ types/ # tipos globais TypeScript (contratos, DTOs, entidades)
│ └─ constants/ # constantes e enums globais
├─ server/
│ ├─ api/ # rotas server (ex: server/api/users.get.ts)
│ ├─ middleware/ # middlewares server-side Nitro
│ └─ plugins/ # plugins server-side
├─ tailwind.config.ts # tema e tokens de design
├─ nuxt.config.ts
└─ .env

> **Sobre `types`**
>
> - **Projetos pequenos** → pode manter em `app/types`.
> - **Projetos médios/grandes ou com backend** → prefira `shared/types` fora do `app/` para facilitar compartilhamento.

---

## 2) Princípios de arquitetura

1. **Componentizar ao máximo**
- Componentes pequenos, coesos e reusáveis.
- Nada de lógica de dados dentro de componentes — use **composables**.
- Ao criar componentes, faça com ID fixo para evitar problemas de hidrataçao

2. **Composables para lógica de domínio**
- `/app/composables/useX.ts` → busca de dados, regras de negócio, orquestração.
- Componente apenas consome o composable.

3. **Responsabilidade única**
- Um arquivo faz **uma única coisa bem feita**. Se crescer, quebre.

4. **Tipos corretos**
- Sempre tipar props, emits, retornos, estados e contratos de API.
- Evitar `any`; preferir tipagem explícita.

5. **Sempre TypeScript**
- `lang="ts"` nos componentes Vue.
- Tipos globais no `/shared/types` ou `app/types`.

6. **Padrão de camadas**
- **UI (`components`)** → **Composables (`composables`)** → **Acesso a dados (`server/api` ou SDK)**.

---

## Regras de nomenclatura

- **Componentes Vue (`/app/components`)** → **PascalCase**
Ex.: `UserCard.vue`, `AuthButton.vue`

- **Páginas (`/app/pages`)** → **minúsculas sem traços**, usar apenas letras e, se necessário, subpastas para organizar
Ex.: `login.vue`, `profile.vue`, `settings.vue`
Se precisar separar por contexto:

/app/pages/admin/dashboard.vue
/app/pages/admin/users.vue

- **Layouts (`/app/layouts`)** → **PascalCase**
Ex.: `DefaultLayout.vue`, `AdminLayout.vue`

Atençao: Para usar o layout basta envolver o componente na tag NuxtLayout

- **Composables (`/app/composables`)** → prefixo `use` + PascalCase
Ex.: `useAuth.ts`, `useCart.ts`

- **Middlewares (`/app/middleware`)** → camelCase
Ex.: `authGuard.ts`, `isAdmin.ts`

- **Utils (`/app/utils`)** → camelCase
Ex.: `formatDate.ts`, `calculateTotal.ts`

- **Tipos (`/shared/types` ou `app/types`)** → PascalCase para nomes de interfaces ou DTOs
Ex.: `UserDTO.ts`, `ProductDTO.ts`
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"postman.settings.dotenv-detection-notification-visibility": false
}
100 changes: 86 additions & 14 deletions app/components/settings/users/AddModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,60 @@
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const { addUser, loading } = useUsers()
const toast = useToast()

const schema = z.object({
name: z.string().min(2, 'Too short'),
email: z.string().email('Invalid email')
name: z.string().min(2, 'Muito curto'),
email: z.string().email('Email inválido'),
password: z.string().min(6, 'A senha deve ter pelo menos 6 caracteres'),
role: z.enum(['user', 'admin']).default('user')
})
const open = ref(false)

type Schema = z.output<typeof schema>

const state = reactive<Partial<Schema>>({
name: undefined,
email: undefined
email: undefined,
password: undefined,
role: 'user'
})

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ title: 'Success', description: `Novo usuário ${event.data.name} adicionado`, color: 'success' })
open.value = false
try {
await addUser(event.data)
toast.add({
title: 'Sucesso',
description: `Usuário ${event.data.name} criado com sucesso`,
color: 'success'
})
open.value = false
// Reset form
state.name = undefined
state.email = undefined
state.password = undefined
state.role = 'user'
} catch {
toast.add({
title: 'Erro',
description: 'Erro ao criar usuário',
color: 'error'
})
}
}
</script>

<template>
<UModal v-model:open="open" title="Novo usuário" description="Adicione um novo usuário ao banco de dados">
<UButton label="Novo usuário" icon="i-lucide-plus" />
<UModal
v-model:open="open"
title="Novo usuário"
description="Adicione um novo usuário ao banco de dados"
>
<UButton
label="Novo usuário"
icon="i-lucide-plus"
/>

<template #body>
<UForm
Expand All @@ -33,24 +64,65 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
class="space-y-4"
@submit="onSubmit"
>
<UFormField label="Name" placeholder="John Doe" name="name">
<UInput v-model="state.name" class="w-full" />
<UFormField
label="Nome"
name="name"
>
<UInput
v-model="state.name"
class="w-full"
placeholder="John Doe"
/>
</UFormField>
<UFormField label="Email" placeholder="john.doe@example.com" name="email">
<UInput v-model="state.email" class="w-full" />

<UFormField
label="Email"
name="email"
>
<UInput
v-model="state.email"
class="w-full"
placeholder="john.doe@example.com"
/>
</UFormField>

<UFormField
label="Senha"
name="password"
>
<UInput
v-model="state.password"
type="password"
class="w-full"
placeholder="••••••••"
/>
</UFormField>

<UFormField
label="Função"
name="role"
>
<USelect
v-model="state.role"
:items="['user', 'admin']"
class="w-full"
:ui="{ value: 'capitalize', item: 'capitalize' }"
/>
</UFormField>

<div class="flex justify-end gap-2">
<UButton
label="Cancel"
label="Cancelar"
color="neutral"
variant="subtle"
@click="open = false"
/>
<UButton
label="Create"
label="Criar"
color="primary"
variant="solid"
type="submit"
:loading="loading"
/>
</div>
</UForm>
Expand Down
24 changes: 21 additions & 3 deletions app/components/settings/users/DeleteModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,27 @@ const props = defineProps<{

const open = ref(false)

const { deleteUser, loading } = useUsers()
const toast = useToast()

async function onSubmit() {
await new Promise((resolve) => setTimeout(resolve, 1000))
open.value = false
if (!props.userId) return

try {
await deleteUser(props.userId)
toast.add({
title: 'Sucesso',
description: 'Usuário excluído com sucesso',
color: 'success'
})
open.value = false
} catch {
toast.add({
title: 'Erro',
description: 'Erro ao excluir usuário',
color: 'error'
})
}
}

defineExpose({ open })
Expand All @@ -32,7 +50,7 @@ defineExpose({ open })
label="Excluir"
color="error"
variant="solid"
loading-auto
:loading="loading"
@click="onSubmit"
/>
</div>
Expand Down
Loading