@riligar/agents-kit 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/{skills/riligar-dev-clean-code/SKILL.md → rules/clean-code.md} +3 -51
- package/.agent/skills/riligar-design-system/SKILL.md +1 -0
- package/.agent/skills/riligar-dev-auth-elysia/SKILL.md +2 -2
- package/.agent/skills/riligar-dev-dashboard/SKILL.md +582 -0
- package/.agent/skills/riligar-dev-dashboard/references/dependencies.md +44 -0
- package/.agent/skills/{riligar-dev-backend → riligar-dev-manager}/SKILL.md +13 -9
- package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/SKILL.md +1 -1
- package/.agent/skills/{riligar-dev-seo → riligar-dev-website-seo}/SKILL.md +1 -1
- package/.agent/skills/riligar-infra-cloudfare/SKILL.md +437 -0
- package/.agent/skills/riligar-infra-cloudfare/references/cloudflare-api.md +139 -0
- package/.agent/skills/riligar-infra-cloudfare/references/email-routing.md +262 -0
- package/.agent/skills/riligar-infra-cloudfare/references/r2-storage.md +333 -0
- package/.agent/skills/{riligar-infrastructure → riligar-infra-fly}/SKILL.md +38 -1
- package/.agent/skills/{riligar-dev-stripe → riligar-infra-stripe}/SKILL.md +3 -4
- package/.agent/skills/skill-creator/SKILL.md +1 -1
- package/package.json +1 -1
- package/.agent/skills/riligar-dev-architecture/SKILL.md +0 -54
- package/.agent/skills/riligar-dev-architecture/references/context-discovery.md +0 -43
- package/.agent/skills/riligar-dev-architecture/references/examples.md +0 -94
- package/.agent/skills/riligar-dev-architecture/references/pattern-selection.md +0 -68
- package/.agent/skills/riligar-dev-architecture/references/patterns-reference.md +0 -50
- package/.agent/skills/riligar-dev-architecture/references/trade-off-analysis.md +0 -77
- package/.agent/skills/riligar-dev-autopilot/SKILL.md +0 -59
- package/.agent/skills/riligar-dev-code-review/SKILL.md +0 -116
- package/.agent/skills/riligar-dev-database/SKILL.md +0 -51
- package/.agent/skills/riligar-dev-database/references/database-selection.md +0 -43
- package/.agent/skills/riligar-dev-database/references/indexing.md +0 -39
- package/.agent/skills/riligar-dev-database/references/migrations.md +0 -48
- package/.agent/skills/riligar-dev-database/references/optimization.md +0 -36
- package/.agent/skills/riligar-dev-database/references/orm-selection.md +0 -30
- package/.agent/skills/riligar-dev-database/references/schema-design.md +0 -56
- package/.agent/skills/riligar-dev-database/scripts/schema_validator.py +0 -172
- package/.agent/skills/riligar-dev-frontend/SKILL.md +0 -215
- package/.agent/skills/riligar-plan-writing/SKILL.md +0 -162
- package/.agent/skills/riligar-tech-stack/SKILL.md +0 -110
- package/.agent/skills/riligar-tech-stack/references/tech-stack.md +0 -131
- /package/.agent/skills/riligar-dev-auth-elysia/assets/{server-snippets.ts → server-snippets.js} +0 -0
- /package/.agent/skills/{riligar-dev-backend → riligar-dev-manager}/references/elysia-basics.md +0 -0
- /package/.agent/skills/{riligar-dev-backend → riligar-dev-manager}/references/elysia-lifecycle.md +0 -0
- /package/.agent/skills/{riligar-dev-backend → riligar-dev-manager}/references/elysia-patterns.md +0 -0
- /package/.agent/skills/{riligar-dev-backend → riligar-dev-manager}/references/elysia-plugins.md +0 -0
- /package/.agent/skills/{riligar-dev-backend → riligar-dev-manager}/references/elysia-validation.md +0 -0
- /package/.agent/skills/{riligar-dev-backend → riligar-dev-manager}/scripts/api_validator.py +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/original-2a03320f967a884fd2ad275d788f32e5.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/original-481d7179109272dcaff2516fef62b718.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/original-56d848520060ca714456601d1a7417cd.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/original-93104cd260129cd6b76dac4119622eaf.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/original-c5d259b0497cec98c36c48fc33ebbde6.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/original-e865b2464fdf5ca567af716e1ed4fd16.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/original-f1459f5315f0045705c2ca4937204146.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/original-f67954754fdc2fc57009369fd3437205.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/screencapture-caddaddy-app-2025-11-03-20_16_14.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/screencapture-ciromaciel-click-2026-01-06-17_08_01.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/screencapture-notionsecondbrain-2026-01-06-16_07_56.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/assets/screencapture-skillsmp-2026-01-16-14_40_22.webp +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/references/conversion-framework.md +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/references/copywriting-guide.md +0 -0
- /package/.agent/skills/{riligar-dev-landing-page → riligar-dev-website}/references/section-blueprints.md +0 -0
- /package/.agent/skills/{riligar-dev-seo → riligar-dev-website-seo}/references/checklist.md +0 -0
- /package/.agent/skills/{riligar-dev-seo → riligar-dev-website-seo}/references/implementation.md +0 -0
- /package/.agent/skills/{riligar-dev-seo → riligar-dev-website-seo}/references/structured-data.md +0 -0
- /package/.agent/skills/{riligar-infrastructure → riligar-infra-fly}/references/infrastructure.md +0 -0
- /package/.agent/skills/{riligar-dev-stripe → riligar-infra-stripe}/assets/stripe-client.js +0 -0
- /package/.agent/skills/{riligar-dev-stripe → riligar-infra-stripe}/assets/stripe-server.js +0 -0
- /package/.agent/skills/{riligar-dev-stripe → riligar-infra-stripe}/references/stripe-database.md +0 -0
- /package/.agent/skills/{riligar-dev-stripe → riligar-infra-stripe}/references/stripe-elysia.md +0 -0
- /package/.agent/skills/{riligar-dev-stripe → riligar-infra-stripe}/references/stripe-react.md +0 -0
- /package/.agent/skills/{riligar-dev-stripe → riligar-infra-stripe}/references/stripe-webhooks.md +0 -0
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: riligar-dev-dashboard
|
|
3
|
+
description: Padrões React específicos do RiLiGar. Zustand, i18n, estrutura de arquivos, composição de componentes. Use quando construindo componentes, gerenciando estado ou implementando UI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Front-End — Padrões do RiLiGar
|
|
7
|
+
|
|
8
|
+
> Regras concretas baseadas na arquitetura real dos projetos. Não são genéricas — são do código que existe.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Referências obrigatórias
|
|
13
|
+
|
|
14
|
+
> [!IMPORTANT]
|
|
15
|
+
> Sempre respeite também:
|
|
16
|
+
> - @[.agent/skills/riligar-design-system] — UI exclusivo via Mantine, zero CSS
|
|
17
|
+
> - Rules em `.agent/rules/` — clean-code, naming-conventions, code-style, javascript-only
|
|
18
|
+
> - [references/dependencies.md](references/dependencies.md) — Pacotes e versões do frontend, config Vite
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Estrutura de arquivos
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
src/
|
|
26
|
+
├── components/ # Componentes reutilizáveis
|
|
27
|
+
│ ├── Sidebar.jsx # PascalCase (SEMPRE)
|
|
28
|
+
│ ├── RichEditor.jsx
|
|
29
|
+
│ └── MediaLibrary.jsx
|
|
30
|
+
├── pages/ # Uma pasta por página/feature
|
|
31
|
+
│ ├── home.jsx # Arquivo raiz da página: kebab-case
|
|
32
|
+
│ ├── editor/
|
|
33
|
+
│ │ └── index.jsx
|
|
34
|
+
│ └── feeds/
|
|
35
|
+
│ ├── index.jsx
|
|
36
|
+
│ └── FeedConfig.jsx # Sub-componentes: PascalCase
|
|
37
|
+
├── store/ # Zustand stores — um arquivo por domínio
|
|
38
|
+
│ ├── auth-store.js
|
|
39
|
+
│ ├── feed-store.js
|
|
40
|
+
│ └── post-store.js
|
|
41
|
+
├── services/ # Chamadas HTTP — um arquivo por domínio
|
|
42
|
+
│ ├── api.js # Instância base do cliente HTTP
|
|
43
|
+
│ ├── feeds.js
|
|
44
|
+
│ └── posts.js
|
|
45
|
+
├── constants/ # Constantes estáticas do app
|
|
46
|
+
├── i18n/ # Internacionalização
|
|
47
|
+
│ ├── index.js
|
|
48
|
+
│ └── locales/
|
|
49
|
+
└── hooks/ # Custom hooks compartilhados
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Regras de naming de arquivos
|
|
53
|
+
|
|
54
|
+
| Tipo | Convenção | Exemplo |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| Componentes (reutilizáveis) | PascalCase | `MediaLibrary.jsx` |
|
|
57
|
+
| Página raiz | kebab-case | `home.jsx`, `index.jsx` |
|
|
58
|
+
| Sub-componentes de página | PascalCase | `FeedConfig.jsx` |
|
|
59
|
+
| Stores | kebab-case + sufixo `-store` | `feed-store.js` |
|
|
60
|
+
| Services | kebab-case | `feeds.js` |
|
|
61
|
+
| Hooks | camelCase com prefixo `use` | `useDebounce.js` |
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 2. Estado — Zustand (não é opcional)
|
|
66
|
+
|
|
67
|
+
Estado global **sempre** Zustand. Sem Context para estado compartilhado. Sem Redux.
|
|
68
|
+
|
|
69
|
+
### Padrão de store
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
import { create } from 'zustand'
|
|
73
|
+
import { feedsService } from '../services/feeds'
|
|
74
|
+
|
|
75
|
+
const useFeedStore = create((set, get) => ({
|
|
76
|
+
feeds: [],
|
|
77
|
+
activeFeed: null,
|
|
78
|
+
isLoading: false,
|
|
79
|
+
|
|
80
|
+
fetchFeeds: async () => {
|
|
81
|
+
set({ isLoading: true })
|
|
82
|
+
const feeds = await feedsService.getAll()
|
|
83
|
+
set({ feeds, isLoading: false })
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
setActiveFeed: (feed) => set({ activeFeed: feed }),
|
|
87
|
+
}))
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Regras
|
|
91
|
+
|
|
92
|
+
- **Sem getters JavaScript** (`get isTrialing() {}`) — não são reativos no Zustand. Derive no componente ou use um selector.
|
|
93
|
+
- **Persistência:** use `persist` middleware apenas quando necessário (ex: `activeFeed`).
|
|
94
|
+
- **Sem lógica de UI** dentro do store. Stores são dados + chamadas de API.
|
|
95
|
+
- **Selectors granulares:** `useStore((state) => state.feeds)` — não `useStore()` inteiro.
|
|
96
|
+
|
|
97
|
+
### Errado vs Certo
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
// ❌ Getter não-reativo — nunca vai atualizar o componente
|
|
101
|
+
const useSubscriptionStore = create((set) => ({
|
|
102
|
+
subscription: null,
|
|
103
|
+
get isTrialing() { return this.subscription?.status === 'trialing' }
|
|
104
|
+
}))
|
|
105
|
+
|
|
106
|
+
// ✅ Derive no componente
|
|
107
|
+
const subscription = useSubscriptionStore((s) => s.subscription)
|
|
108
|
+
const isTrialing = subscription?.status === 'trialing'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 3. i18n — Todas as strings visíveis devem usar `t()`
|
|
114
|
+
|
|
115
|
+
O projeto usa **i18next** com 3 idiomas (pt-BR, en, es). Nenhuma string visível ao usuário pode ser hardcoded.
|
|
116
|
+
|
|
117
|
+
### Como usar
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
import { useTranslation } from 'react-i18next'
|
|
121
|
+
|
|
122
|
+
const MyComponent = () => {
|
|
123
|
+
const { t } = useTranslation()
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<Button onClick={handleSave}>{t('common.save')}</Button>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Regras
|
|
132
|
+
|
|
133
|
+
- **Sempre importar `useTranslation`** antes de usar `t()`. Sem isso, crash em runtime.
|
|
134
|
+
- Strings de UI: `t('namespace.key')` — nunca hardcode.
|
|
135
|
+
- Strings técnicas (logs, variável interna): podem ser em inglês, não precisam de `t()`.
|
|
136
|
+
- Chaves de namespace: domínio da feature (`feeds.`, `posts.`, `media.`) + `common.` para compartilhados.
|
|
137
|
+
- Mensagens de erro da API já vêm traduzidas do backend — não duplique.
|
|
138
|
+
|
|
139
|
+
### Errado
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
// ❌ String hardcoded visível ao usuário
|
|
143
|
+
<Button>Salvar Alterações</Button>
|
|
144
|
+
<Text>Nenhuma mídia encontrada.</Text>
|
|
145
|
+
|
|
146
|
+
// ❌ t() sem import — crash
|
|
147
|
+
const handleSave = async () => {
|
|
148
|
+
showNotification({ title: t('common.success') }) // ReferenceError
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Certo
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
// ✅
|
|
156
|
+
import { useTranslation } from 'react-i18next'
|
|
157
|
+
|
|
158
|
+
const { t } = useTranslation()
|
|
159
|
+
<Button>{t('common.save')}</Button>
|
|
160
|
+
<Text>{t('media.empty')}</Text>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 4. Services — camada de HTTP
|
|
166
|
+
|
|
167
|
+
Todas as chamadas de API vão por `services/`. Componentes e stores não chamam HTTP diretamente.
|
|
168
|
+
|
|
169
|
+
### Padrão
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
// services/feeds.js
|
|
173
|
+
import { api } from './api'
|
|
174
|
+
|
|
175
|
+
export const feedsService = {
|
|
176
|
+
getAll: () => api.get('feeds').json(),
|
|
177
|
+
getById: (id) => api.get(`feeds/${id}`).json(),
|
|
178
|
+
create: (data) => api.post('feeds', { json: data }).json(),
|
|
179
|
+
update: (id, data) => api.put(`feeds/${id}`, { json: data }).json(),
|
|
180
|
+
remove: (id) => api.delete(`feeds/${id}`).json(),
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- `api.js` tem a instância base com token de auth e tratamento de erro.
|
|
185
|
+
- **Não duplique `API_URL`** — já está no `api.js`. Nunca redefina em outro arquivo.
|
|
186
|
+
- Services são funções puras de chamada HTTP. Sem lógica de estado.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 5. Componentes — regras práticas
|
|
191
|
+
|
|
192
|
+
### Composição
|
|
193
|
+
|
|
194
|
+
- Um componente, uma responsabilidade.
|
|
195
|
+
- Se um componente ultrapassar ~100 linhas, divide.
|
|
196
|
+
- Props down, events up. Sem drilling profundo — usa store.
|
|
197
|
+
|
|
198
|
+
### Interação com usuário
|
|
199
|
+
|
|
200
|
+
- **Confirmação de exclusão:** sempre usar `HoldButton` / `ButtonDelete` do `components/Buttons.jsx`. Nunca usar `confirm()` nativo.
|
|
201
|
+
- **Hover/focus:** usar props do Mantine (`styles`, `withBorder`, hover via `&:hover` no `styles`). Nunca manipular DOM diretamente via `e.currentTarget.style`.
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
// ❌ Manipulação direta de DOM
|
|
205
|
+
onMouseEnter={(e) => e.currentTarget.style.background = '#eee'}
|
|
206
|
+
|
|
207
|
+
// ✅ Mantine styles prop
|
|
208
|
+
styles={{ root: { '&:hover': { backgroundColor: 'var(--mantine-color-dimmed)' } } }}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Não redefina componentes do Mantine
|
|
212
|
+
|
|
213
|
+
Se você precisa de um `Center`, `TextInput`, `Button` — use o do Mantine. Nunca crie um local com o mesmo nome, isso gera shadow e confusão.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 6. Constantes e valores fixos
|
|
218
|
+
|
|
219
|
+
- Strings de config (phone numbers, mensagens template, URLs externas) vão em `constants/` ou `.env`.
|
|
220
|
+
- Nunca hardcode dentro de componentes.
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
// ❌
|
|
224
|
+
const message = `Olá, preciso de ajuda com meu plano.`
|
|
225
|
+
|
|
226
|
+
// ✅ constants/whatsapp.js
|
|
227
|
+
export const WHATSAPP_SUPPORT_NUMBER = '...'
|
|
228
|
+
export const WHATSAPP_SUPPORT_MESSAGE = '...' // ou via i18n se traduzível
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 7. Anti-patterns (do código real)
|
|
234
|
+
|
|
235
|
+
| ❌ Problema real encontrado | ✅ Como resolver |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `slugify` duplicado em 2 arquivos | Extrair para `utils/slugify.js` |
|
|
238
|
+
| `stripHtml` duplicado em 2 arquivos | Extrair para `utils/stripHtml.js` |
|
|
239
|
+
| `API_URL` redefinido fora do `api.js` | Importar do `services/api.js` |
|
|
240
|
+
| `t()` usado sem `useTranslation` importado | Sempre verificar import |
|
|
241
|
+
| Componente Mantine shadowed por local | Deletar o local, usar Mantine |
|
|
242
|
+
| Código comentado espalhado | Deletar. Se precisa, usa git. |
|
|
243
|
+
| `confirm()` misturado com HoldButton | Usa HoldButton sempre |
|
|
244
|
+
| `<style>` tag com CSS raw | Usa `styles` prop do Mantine (exceção: libs externas como ProseMirror que precisam de CSS global) |
|
|
245
|
+
| Getters no Zustand store | Derive no componente |
|
|
246
|
+
| `onMouseEnter` manipulando style diretamente | Usa `styles` prop do Mantine |
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 8. Padrões reutilizáveis
|
|
251
|
+
|
|
252
|
+
Estruturas que repetem pela codebase. Copie o esqueleto, ajuste apenas o conteúdo domínio-específico.
|
|
253
|
+
|
|
254
|
+
### 8.1 Page Header
|
|
255
|
+
|
|
256
|
+
Presente em **todas** as pages. Estrutura idêntica sempre:
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
<Box py="xl">
|
|
260
|
+
<Group justify="space-between" align="flex-end" mb="xl">
|
|
261
|
+
<Stack gap={0}>
|
|
262
|
+
<Text size="xs" fw={700} c="dimmed" tt="uppercase" lts="0.1em">
|
|
263
|
+
{t('namespace.subtitle')}
|
|
264
|
+
</Text>
|
|
265
|
+
<Title order={1} style={{ letterSpacing: '-0.04em' }}>
|
|
266
|
+
{t('namespace.title')}
|
|
267
|
+
</Title>
|
|
268
|
+
</Stack>
|
|
269
|
+
{/* CTA opcional — ex: <Button leftSection={<IconPlus size={16} />}> */}
|
|
270
|
+
</Group>
|
|
271
|
+
{/* Conteúdo da página */}
|
|
272
|
+
</Box>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 8.2 Empty State
|
|
276
|
+
|
|
277
|
+
Usado quando uma lista está vazia. Card com borda dashed, icone grande, texto e CTA:
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
<Card padding="xl" radius="md" withBorder style={{ borderStyle: 'dashed', textAlign: 'center' }}>
|
|
281
|
+
<Stack align="center" py="xl">
|
|
282
|
+
<IconDominio size={48} stroke={1} color="var(--mantine-color-gray-2)" />
|
|
283
|
+
<Text c="dimmed" size="sm">{t('namespace.emptyMessage')}</Text>
|
|
284
|
+
<Button onClick={handleCreate} leftSection={<IconPlus size={16} />}>
|
|
285
|
+
{t('namespace.createFirst')}
|
|
286
|
+
</Button>
|
|
287
|
+
</Stack>
|
|
288
|
+
</Card>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 8.3 Loading Guard
|
|
292
|
+
|
|
293
|
+
Loader só aparece quando não há dados ainda (não sobrescreve lista existente):
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
{loading && data.length === 0 ? (
|
|
297
|
+
<Center style={{ height: 300 }}>
|
|
298
|
+
<Loader />
|
|
299
|
+
</Center>
|
|
300
|
+
) : (
|
|
301
|
+
/* conteúdo normal */
|
|
302
|
+
)}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### 8.4 Card Grid
|
|
306
|
+
|
|
307
|
+
Layout responsivo padrão para listas de cards:
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }}>
|
|
311
|
+
{items.map((item) => (
|
|
312
|
+
<Card key={item.id} padding="lg" radius="md" withBorder>
|
|
313
|
+
{/* conteúdo do card */}
|
|
314
|
+
</Card>
|
|
315
|
+
))}
|
|
316
|
+
</SimpleGrid>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Para galerias (mais itens pequenos): `cols={{ base: 1, sm: 2, md: 3, lg: 4 }}`
|
|
320
|
+
|
|
321
|
+
### 8.5 Modal
|
|
322
|
+
|
|
323
|
+
Sempre usa `useDisclosure`. Header com borderBottom específico:
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
const [opened, { open, close }] = useDisclosure(false)
|
|
327
|
+
|
|
328
|
+
<Modal
|
|
329
|
+
opened={opened}
|
|
330
|
+
onClose={close}
|
|
331
|
+
centered
|
|
332
|
+
radius="md"
|
|
333
|
+
padding="xl"
|
|
334
|
+
title={<Text fw={700}>{t('namespace.modalTitle')}</Text>}
|
|
335
|
+
styles={{ header: { borderBottom: '1px solid var(--mantine-color-gray-2)', marginBottom: 20 } }}
|
|
336
|
+
>
|
|
337
|
+
{/* corpo do modal */}
|
|
338
|
+
</Modal>
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Para múltiplos modais na mesma page, renomeia as funções: `{ open: openEdit, close: closeEdit }`
|
|
342
|
+
|
|
343
|
+
### 8.6 Status Badge
|
|
344
|
+
|
|
345
|
+
Mapeia status → configuração visual via função que recebe `t`:
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
const getStatusConfig = (t) => ({
|
|
349
|
+
draft: { color: 'gray', icon: <IconCircleDotted size={16} />, label: t('posts.status.draft') },
|
|
350
|
+
scheduled: { color: 'blue', icon: <IconClock size={16} />, label: t('posts.status.scheduled') },
|
|
351
|
+
published: { color: 'green', icon: <IconCircleCheck size={16} />, label: t('posts.status.published') },
|
|
352
|
+
failed: { color: 'red', icon: <IconCircleX size={16} />, label: t('posts.status.failed') },
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
// Uso
|
|
356
|
+
const config = getStatusConfig(t)[status]
|
|
357
|
+
<Badge variant="dot" color={config.color}>{config.label}</Badge>
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### 8.7 Search / Filter
|
|
361
|
+
|
|
362
|
+
Filtro local sem chamada de API — estado local + filter inline:
|
|
363
|
+
|
|
364
|
+
```javascript
|
|
365
|
+
const [search, setSearch] = useState('')
|
|
366
|
+
|
|
367
|
+
const filtered = items.filter((item) =>
|
|
368
|
+
item.name.toLowerCase().includes(search.toLowerCase())
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
<TextInput
|
|
372
|
+
placeholder={t('common.search')}
|
|
373
|
+
leftSection={<IconSearch size={16} />}
|
|
374
|
+
value={search}
|
|
375
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
376
|
+
/>
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## 9. Padrões de dados e lógica
|
|
382
|
+
|
|
383
|
+
### 9.1 Store — async action
|
|
384
|
+
|
|
385
|
+
Template exato que todas as actions seguem. Imports do service como namespace:
|
|
386
|
+
|
|
387
|
+
```javascript
|
|
388
|
+
import { create } from 'zustand'
|
|
389
|
+
import * as feedsService from '../services/feeds.js'
|
|
390
|
+
|
|
391
|
+
export const useFeedStore = create((set) => ({
|
|
392
|
+
feeds: [],
|
|
393
|
+
loading: false,
|
|
394
|
+
error: null,
|
|
395
|
+
|
|
396
|
+
fetchFeeds: async () => {
|
|
397
|
+
set({ loading: true, error: null })
|
|
398
|
+
try {
|
|
399
|
+
const feeds = await feedsService.getAll()
|
|
400
|
+
set({ feeds, loading: false })
|
|
401
|
+
} catch (error) {
|
|
402
|
+
set({ error: error.message, loading: false })
|
|
403
|
+
throw error
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
}))
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Nota: services são importados como `import * as service` (namespace), não como objeto exportado.
|
|
410
|
+
|
|
411
|
+
### 9.2 Store — mutação de listas
|
|
412
|
+
|
|
413
|
+
Atualizações imutáveis via `set(state => ...)` com spread + map/filter:
|
|
414
|
+
|
|
415
|
+
```javascript
|
|
416
|
+
// Atualizar item na lista
|
|
417
|
+
updateFeed: (id, data) => set((state) => ({
|
|
418
|
+
feeds: state.feeds.map((f) => (f.id === id ? { ...f, ...data } : f))
|
|
419
|
+
})),
|
|
420
|
+
|
|
421
|
+
// Remover item
|
|
422
|
+
removeFeed: (id) => set((state) => ({
|
|
423
|
+
feeds: state.feeds.filter((f) => f.id !== id)
|
|
424
|
+
})),
|
|
425
|
+
|
|
426
|
+
// Adicionar item
|
|
427
|
+
addFeed: (feed) => set((state) => ({
|
|
428
|
+
feeds: [...state.feeds, feed]
|
|
429
|
+
})),
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### 9.3 Notifications
|
|
433
|
+
|
|
434
|
+
Shape e convenção de cores consistente em toda a app:
|
|
435
|
+
|
|
436
|
+
```javascript
|
|
437
|
+
import { notifications } from '@mantine/notifications'
|
|
438
|
+
import { IconCheck, IconX, IconAlertCircle } from '@tabler/icons-react'
|
|
439
|
+
|
|
440
|
+
// ✅ Sucesso
|
|
441
|
+
notifications.show({
|
|
442
|
+
title: t('common.success'),
|
|
443
|
+
message: t('namespace.savedMessage'),
|
|
444
|
+
color: 'green',
|
|
445
|
+
icon: <IconCheck size={18} />,
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
// ✅ Erro
|
|
449
|
+
notifications.show({
|
|
450
|
+
title: t('common.error'),
|
|
451
|
+
message: error.message,
|
|
452
|
+
color: 'red',
|
|
453
|
+
icon: <IconX size={18} />,
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
// ✅ Warning
|
|
457
|
+
notifications.show({
|
|
458
|
+
title: t('common.warning'),
|
|
459
|
+
message: t('namespace.warningMessage'),
|
|
460
|
+
color: 'yellow',
|
|
461
|
+
icon: <IconAlertCircle size={18} />,
|
|
462
|
+
})
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Icones de notification: sempre `size={18}`. Mensagens de erro: usa `error.message` diretamente (já vem traduzido do backend).
|
|
466
|
+
|
|
467
|
+
### 9.4 dayjs + i18n
|
|
468
|
+
|
|
469
|
+
Locale do dayjs sincroniza com o idioma do i18n:
|
|
470
|
+
|
|
471
|
+
```javascript
|
|
472
|
+
import dayjs from 'dayjs'
|
|
473
|
+
import relativeTime from 'dayjs/plugin/relativeTime'
|
|
474
|
+
import { useTranslation } from 'react-i18next'
|
|
475
|
+
|
|
476
|
+
dayjs.extend(relativeTime)
|
|
477
|
+
|
|
478
|
+
const MyComponent = () => {
|
|
479
|
+
const { i18n } = useTranslation()
|
|
480
|
+
|
|
481
|
+
useEffect(() => {
|
|
482
|
+
dayjs.locale(i18n.language)
|
|
483
|
+
}, [i18n.language])
|
|
484
|
+
|
|
485
|
+
// Formatos usados no projeto:
|
|
486
|
+
// dayjs(date).format('DD MMM, HH:mm') — compacto com hora
|
|
487
|
+
// dayjs(date).format('DD/MM/YYYY [at] HH:mm') — completo
|
|
488
|
+
// dayjs(date).fromNow() — relativo ("há 2 dias")
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## 10. Padrões de fluxo
|
|
495
|
+
|
|
496
|
+
### 10.1 Route Guard (wrapper)
|
|
497
|
+
|
|
498
|
+
Componente que protege rotas verificando estado do store:
|
|
499
|
+
|
|
500
|
+
```javascript
|
|
501
|
+
import { Navigate } from 'react-router-dom'
|
|
502
|
+
import { useFeedStore } from '../store/feed-store.js'
|
|
503
|
+
|
|
504
|
+
const RequireFeed = ({ children }) => {
|
|
505
|
+
const activeFeed = useFeedStore((s) => s.activeFeed)
|
|
506
|
+
if (!activeFeed) return <Navigate to="/" />
|
|
507
|
+
return children
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Usado na definição de rotas: `<RequireFeed><EditorPage /></RequireFeed>`
|
|
512
|
+
|
|
513
|
+
### 10.2 Notificação via URL params
|
|
514
|
+
|
|
515
|
+
Após redirects externos (OAuth, Stripe checkout), status vem via query params:
|
|
516
|
+
|
|
517
|
+
```javascript
|
|
518
|
+
import { useSearchParams } from 'react-router-dom'
|
|
519
|
+
|
|
520
|
+
const SubscriptionPage = () => {
|
|
521
|
+
const [searchParams, setSearchParams] = useSearchParams()
|
|
522
|
+
const { t } = useTranslation()
|
|
523
|
+
|
|
524
|
+
useEffect(() => {
|
|
525
|
+
if (searchParams.get('success')) {
|
|
526
|
+
notifications.show({ title: t('common.success'), message: t('subscription.successMessage'), color: 'green', icon: <IconCheck size={18} /> })
|
|
527
|
+
setSearchParams({})
|
|
528
|
+
} else if (searchParams.get('canceled')) {
|
|
529
|
+
notifications.show({ title: t('common.warning'), message: t('subscription.canceledMessage'), color: 'yellow', icon: <IconAlertCircle size={18} /> })
|
|
530
|
+
setSearchParams({})
|
|
531
|
+
}
|
|
532
|
+
}, [searchParams])
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### 10.3 Autosave
|
|
537
|
+
|
|
538
|
+
Padrão usado no editor — debounce com state machine de status:
|
|
539
|
+
|
|
540
|
+
```javascript
|
|
541
|
+
const [saveStatus, setSaveStatus] = useState('idle') // 'idle' | 'saving' | 'saved'
|
|
542
|
+
|
|
543
|
+
useEffect(() => {
|
|
544
|
+
if (!content) return
|
|
545
|
+
const timeout = setTimeout(async () => {
|
|
546
|
+
setSaveStatus('saving')
|
|
547
|
+
try {
|
|
548
|
+
await postsService.update(postId, { content })
|
|
549
|
+
setSaveStatus('saved')
|
|
550
|
+
// Reset para idle após 3s
|
|
551
|
+
setTimeout(() => setSaveStatus('idle'), 3000)
|
|
552
|
+
} catch {
|
|
553
|
+
setSaveStatus('idle')
|
|
554
|
+
}
|
|
555
|
+
}, 2000) // debounce de 2s
|
|
556
|
+
|
|
557
|
+
return () => clearTimeout(timeout)
|
|
558
|
+
}, [content, postId])
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## 11. Convenção de tamanhos de icones
|
|
564
|
+
|
|
565
|
+
Hierarquia consistente — sempre de `@tabler/icons-react`:
|
|
566
|
+
|
|
567
|
+
| Contexto | Size | Exemplo |
|
|
568
|
+
|---|---|---|
|
|
569
|
+
| Menu items / nav | 14 | Sidebar links |
|
|
570
|
+
| Inline / badges | 16 | Botões, labels, leftSection |
|
|
571
|
+
| Notifications | 18 | Icons nas notifications |
|
|
572
|
+
| Card headers | 20 | Ação principal do card |
|
|
573
|
+
| Feature cards | 24 | Cards de destaque |
|
|
574
|
+
| Empty states | 48 | Icone do empty state (com `stroke={1}`) |
|
|
575
|
+
|
|
576
|
+
Empty states usam `stroke={1}` para parecer mais leve. Icones decorativos genéricos usam `stroke={1.5}`.
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## Related Skills
|
|
581
|
+
|
|
582
|
+
- @[.agent/skills/riligar-design-system]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Dependências Frontend
|
|
2
|
+
|
|
3
|
+
## Core
|
|
4
|
+
|
|
5
|
+
| Pacote | Versão | Descrição |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `react` | ^19.x | Biblioteca UI |
|
|
8
|
+
| `react-dom` | ^19.x | React DOM renderer |
|
|
9
|
+
| `react-router-dom` | ^7.x | Roteamento |
|
|
10
|
+
| `vite` | ^5.x | Build tool |
|
|
11
|
+
| `zustand` | ^5.x | Gerenciamento de estado |
|
|
12
|
+
| `ky` | ^1.x | HTTP client |
|
|
13
|
+
|
|
14
|
+
## UI
|
|
15
|
+
|
|
16
|
+
| Pacote | Versão | Descrição |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| `@mantine/core` | ^8.x | Componentes UI |
|
|
19
|
+
| `@mantine/hooks` | ^8.x | Hooks utilitários |
|
|
20
|
+
| `@mantine/form` | ^8.x | Gerenciamento de formulários |
|
|
21
|
+
| `@mantine/notifications` | ^8.x | Sistema de notificações |
|
|
22
|
+
| `@tabler/icons-react` | ^3.x | Iconografia |
|
|
23
|
+
|
|
24
|
+
> Use apenas Mantine para estilização. Sem CSS Modules, Custom CSS ou CSS In-line.
|
|
25
|
+
|
|
26
|
+
## Configuração Vite
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
import { defineConfig } from 'vite'
|
|
30
|
+
import react from '@vitejs/plugin-react'
|
|
31
|
+
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
plugins: [react()],
|
|
34
|
+
build: {
|
|
35
|
+
lib: {
|
|
36
|
+
entry: 'src/index.js',
|
|
37
|
+
formats: ['es', 'cjs'],
|
|
38
|
+
},
|
|
39
|
+
rollupOptions: {
|
|
40
|
+
external: ['react', 'react-dom', '@mantine/core'],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: riligar-dev-
|
|
2
|
+
name: riligar-dev-manager
|
|
3
3
|
description: Elysia backend development patterns for Bun. Use when building APIs, routes, plugins, validation, middleware, and error handling with Elysia framework.
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -10,10 +10,7 @@ description: Elysia backend development patterns for Bun. Use when building APIs
|
|
|
10
10
|
## Mandatory Guidelines
|
|
11
11
|
|
|
12
12
|
> [!IMPORTANT]
|
|
13
|
-
> All work in this skill MUST adhere to
|
|
14
|
-
>
|
|
15
|
-
> - @[.agent/skills/riligar-dev-clean-code] (Clean Code Standards)
|
|
16
|
-
> - @[.agent/skills/riligar-tech-stack] (Tech Stack - Bun, Elysia, SQLite, Drizzle)
|
|
13
|
+
> All work in this skill MUST adhere to rules em `.agent/rules/` — clean-code, code-style, javascript-only, naming-conventions.
|
|
17
14
|
|
|
18
15
|
## Quick Reference
|
|
19
16
|
|
|
@@ -62,6 +59,16 @@ src/
|
|
|
62
59
|
└── logger.js # Request logging
|
|
63
60
|
```
|
|
64
61
|
|
|
62
|
+
## Dependencies
|
|
63
|
+
|
|
64
|
+
| Pacote | Versão | Descrição |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `bun` | latest | Runtime |
|
|
67
|
+
| `elysia` | latest | Framework HTTP |
|
|
68
|
+
| `bun:sqlite` | builtin | SQLite driver |
|
|
69
|
+
| `drizzle-orm` | latest | ORM |
|
|
70
|
+
| `bun:s3` | latest | S3/R2 Storage |
|
|
71
|
+
|
|
65
72
|
## Core Patterns
|
|
66
73
|
|
|
67
74
|
### Route Plugin
|
|
@@ -108,10 +115,7 @@ console.log(`Server running at ${app.server?.url}`)
|
|
|
108
115
|
| Need | Skill |
|
|
109
116
|
| --- | --- |
|
|
110
117
|
| **Authentication** | @[.agent/skills/riligar-dev-auth-elysia] |
|
|
111
|
-
| **
|
|
112
|
-
| **Tech Stack** | @[.agent/skills/riligar-tech-stack] |
|
|
113
|
-
| **Clean Code** | @[.agent/skills/riligar-dev-clean-code] |
|
|
114
|
-
| **Infrastructure** | @[.agent/skills/riligar-infrastructure] |
|
|
118
|
+
| **Infrastructure** | @[.agent/skills/riligar-infra-fly] |
|
|
115
119
|
|
|
116
120
|
## Decision Checklist
|
|
117
121
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: riligar-dev-
|
|
2
|
+
name: riligar-dev-website
|
|
3
3
|
description: Specialist in High-Conversion Landing Pages using RiLiGar Design System. Use for: (1) Creating marketing/sales pages, (2) Structuring conversion flows (AIDA/PAS), (3) Implementing high-trust components (Hero, Social Proof, Pricing), (4) Writing persuasive copy.
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: riligar-dev-seo
|
|
2
|
+
name: riligar-dev-website-seo
|
|
3
3
|
description: Implementação de infraestrutura de SEO técnico seguindo a stack RiLiGar (React, Vite, Bun, Elysia). Use para configurar sitemaps, robots.txt, meta tags, OpenGraph, dados estruturados (JSON-LD) e URLs canônicas.
|
|
4
4
|
---
|
|
5
5
|
|