@riligar/agents-kit 1.12.0 → 1.14.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.
Files changed (39) hide show
  1. package/.agent/skills/riligar-business-startup/SKILL.md +0 -1
  2. package/.agent/skills/riligar-design-system/SKILL.md +0 -1
  3. package/.agent/skills/riligar-dev-architecture/SKILL.md +7 -8
  4. package/.agent/skills/riligar-dev-auth-elysia/SKILL.md +7 -3
  5. package/.agent/skills/riligar-dev-auth-react/SKILL.md +5 -3
  6. package/.agent/skills/riligar-dev-autopilot/SKILL.md +0 -1
  7. package/.agent/skills/riligar-dev-backend/SKILL.md +0 -1
  8. package/.agent/skills/riligar-dev-clean-code/SKILL.md +0 -1
  9. package/.agent/skills/riligar-dev-code-review/SKILL.md +0 -1
  10. package/.agent/skills/riligar-dev-database/SKILL.md +8 -9
  11. package/.agent/skills/riligar-dev-frontend/SKILL.md +0 -1
  12. package/.agent/skills/riligar-dev-landing-page/SKILL.md +0 -1
  13. package/.agent/skills/riligar-dev-seo/SKILL.md +6 -3
  14. package/.agent/skills/riligar-dev-stripe/SKILL.md +196 -91
  15. package/.agent/skills/riligar-dev-stripe/assets/stripe-client.js +422 -0
  16. package/.agent/skills/riligar-dev-stripe/assets/stripe-server.js +300 -0
  17. package/.agent/skills/riligar-dev-stripe/references/stripe-database.md +369 -0
  18. package/.agent/skills/riligar-dev-stripe/references/stripe-elysia.md +342 -0
  19. package/.agent/skills/riligar-dev-stripe/references/stripe-react.md +478 -0
  20. package/.agent/skills/riligar-dev-stripe/references/stripe-webhooks.md +376 -0
  21. package/.agent/skills/riligar-infrastructure/SKILL.md +0 -1
  22. package/.agent/skills/riligar-marketing-copy/SKILL.md +0 -1
  23. package/.agent/skills/riligar-marketing-email/SKILL.md +0 -1
  24. package/.agent/skills/riligar-marketing-seo/SKILL.md +0 -1
  25. package/.agent/skills/riligar-plan-writing/SKILL.md +0 -1
  26. package/.agent/skills/riligar-tech-stack/SKILL.md +0 -1
  27. package/.agent/skills/skill-creator/SKILL.md +0 -2
  28. package/package.json +1 -1
  29. /package/.agent/skills/riligar-dev-architecture/{context-discovery.md → references/context-discovery.md} +0 -0
  30. /package/.agent/skills/riligar-dev-architecture/{examples.md → references/examples.md} +0 -0
  31. /package/.agent/skills/riligar-dev-architecture/{pattern-selection.md → references/pattern-selection.md} +0 -0
  32. /package/.agent/skills/riligar-dev-architecture/{patterns-reference.md → references/patterns-reference.md} +0 -0
  33. /package/.agent/skills/riligar-dev-architecture/{trade-off-analysis.md → references/trade-off-analysis.md} +0 -0
  34. /package/.agent/skills/riligar-dev-database/{database-selection.md → references/database-selection.md} +0 -0
  35. /package/.agent/skills/riligar-dev-database/{indexing.md → references/indexing.md} +0 -0
  36. /package/.agent/skills/riligar-dev-database/{migrations.md → references/migrations.md} +0 -0
  37. /package/.agent/skills/riligar-dev-database/{optimization.md → references/optimization.md} +0 -0
  38. /package/.agent/skills/riligar-dev-database/{orm-selection.md → references/orm-selection.md} +0 -0
  39. /package/.agent/skills/riligar-dev-database/{schema-design.md → references/schema-design.md} +0 -0
@@ -0,0 +1,478 @@
1
+ # Stripe Frontend Patterns (React)
2
+
3
+ Complete patterns for Stripe integration in React applications with Mantine UI.
4
+
5
+ ## Setup
6
+
7
+ ### Stripe Provider
8
+
9
+ ```javascript
10
+ // main.jsx
11
+ import { loadStripe } from '@stripe/stripe-js'
12
+ import { Elements } from '@stripe/react-stripe-js'
13
+
14
+ const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY)
15
+
16
+ ReactDOM.createRoot(document.getElementById('root')).render(
17
+ <Elements stripe={stripePromise}>
18
+ <App />
19
+ </Elements>
20
+ )
21
+ ```
22
+
23
+ ### Stripe Configuration
24
+
25
+ ```javascript
26
+ // services/stripe.js
27
+ import { loadStripe } from '@stripe/stripe-js'
28
+
29
+ export const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY)
30
+
31
+ // Elements appearance (matches Mantine theme)
32
+ export const elementsAppearance = {
33
+ theme: 'stripe',
34
+ variables: {
35
+ colorPrimary: '#228be6',
36
+ colorBackground: '#ffffff',
37
+ colorText: '#1a1b1e',
38
+ colorDanger: '#fa5252',
39
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
40
+ borderRadius: '4px',
41
+ },
42
+ }
43
+ ```
44
+
45
+ ## Hosted Checkout (Recommended)
46
+
47
+ ### Pricing Page
48
+
49
+ ```javascript
50
+ // pages/Pricing.jsx
51
+ import { Button, Card, Group, Stack, Text, Title } from '@mantine/core'
52
+ import { IconCheck } from '@tabler/icons-react'
53
+ import { useState } from 'react'
54
+ import ky from 'ky'
55
+
56
+ const plans = [
57
+ {
58
+ name: 'Starter',
59
+ priceId: 'price_starter_monthly',
60
+ price: 'R$ 29',
61
+ features: ['5 projetos', 'Suporte email', '1GB storage']
62
+ },
63
+ {
64
+ name: 'Pro',
65
+ priceId: 'price_pro_monthly',
66
+ price: 'R$ 99',
67
+ features: ['Projetos ilimitados', 'Suporte prioritário', '10GB storage', 'API access']
68
+ }
69
+ ]
70
+
71
+ export default function Pricing() {
72
+ const [loading, setLoading] = useState(null)
73
+
74
+ const handleCheckout = async (priceId) => {
75
+ setLoading(priceId)
76
+ try {
77
+ const { url } = await ky.post('/api/billing/checkout', {
78
+ json: { priceId, mode: 'subscription' }
79
+ }).json()
80
+
81
+ window.location.href = url
82
+ } catch (error) {
83
+ console.error('Checkout error:', error)
84
+ } finally {
85
+ setLoading(null)
86
+ }
87
+ }
88
+
89
+ return (
90
+ <Group gap="lg" justify="center">
91
+ {plans.map(plan => (
92
+ <Card key={plan.priceId} shadow="sm" padding="lg" radius="md" withBorder w={300}>
93
+ <Stack>
94
+ <Title order={3}>{plan.name}</Title>
95
+ <Text size="xl" fw={700}>{plan.price}<Text span size="sm" c="dimmed">/mês</Text></Text>
96
+
97
+ <Stack gap="xs">
98
+ {plan.features.map(feature => (
99
+ <Group key={feature} gap="xs">
100
+ <IconCheck size={16} color="green" />
101
+ <Text size="sm">{feature}</Text>
102
+ </Group>
103
+ ))}
104
+ </Stack>
105
+
106
+ <Button
107
+ fullWidth
108
+ loading={loading === plan.priceId}
109
+ onClick={() => handleCheckout(plan.priceId)}
110
+ >
111
+ Assinar {plan.name}
112
+ </Button>
113
+ </Stack>
114
+ </Card>
115
+ ))}
116
+ </Group>
117
+ )
118
+ }
119
+ ```
120
+
121
+ ### Success Page
122
+
123
+ ```javascript
124
+ // pages/Success.jsx
125
+ import { Button, Container, Stack, Text, Title } from '@mantine/core'
126
+ import { IconCircleCheck } from '@tabler/icons-react'
127
+ import { useEffect, useState } from 'react'
128
+ import { useSearchParams, useNavigate } from 'react-router-dom'
129
+ import ky from 'ky'
130
+
131
+ export default function Success() {
132
+ const [searchParams] = useSearchParams()
133
+ const navigate = useNavigate()
134
+ const [session, setSession] = useState(null)
135
+
136
+ useEffect(() => {
137
+ const sessionId = searchParams.get('session_id')
138
+ if (sessionId) {
139
+ ky.get(`/api/billing/session/${sessionId}`).json()
140
+ .then(setSession)
141
+ .catch(console.error)
142
+ }
143
+ }, [searchParams])
144
+
145
+ return (
146
+ <Container size="sm" py="xl">
147
+ <Stack align="center" gap="md">
148
+ <IconCircleCheck size={64} color="green" />
149
+ <Title order={2}>Pagamento confirmado!</Title>
150
+ <Text c="dimmed">
151
+ Obrigado por assinar. Seu acesso já está ativo.
152
+ </Text>
153
+ <Button onClick={() => navigate('/dashboard')}>
154
+ Ir para o Dashboard
155
+ </Button>
156
+ </Stack>
157
+ </Container>
158
+ )
159
+ }
160
+ ```
161
+
162
+ ## Customer Portal
163
+
164
+ ```javascript
165
+ // components/BillingSettings.jsx
166
+ import { Button, Card, Group, Stack, Text, Title, Badge } from '@mantine/core'
167
+ import { useState, useEffect } from 'react'
168
+ import ky from 'ky'
169
+
170
+ export default function BillingSettings() {
171
+ const [subscription, setSubscription] = useState(null)
172
+ const [loading, setLoading] = useState(false)
173
+
174
+ useEffect(() => {
175
+ ky.get('/api/billing/subscription').json()
176
+ .then(setSubscription)
177
+ .catch(console.error)
178
+ }, [])
179
+
180
+ const openPortal = async () => {
181
+ setLoading(true)
182
+ try {
183
+ const { url } = await ky.post('/api/billing/portal').json()
184
+ window.location.href = url
185
+ } catch (error) {
186
+ console.error('Portal error:', error)
187
+ } finally {
188
+ setLoading(false)
189
+ }
190
+ }
191
+
192
+ const getStatusBadge = (status) => {
193
+ const colors = {
194
+ active: 'green',
195
+ trialing: 'blue',
196
+ past_due: 'yellow',
197
+ canceled: 'red',
198
+ none: 'gray'
199
+ }
200
+ return <Badge color={colors[status] || 'gray'}>{status}</Badge>
201
+ }
202
+
203
+ return (
204
+ <Card shadow="sm" padding="lg" radius="md" withBorder>
205
+ <Stack>
206
+ <Title order={4}>Assinatura</Title>
207
+
208
+ {subscription ? (
209
+ <>
210
+ <Group justify="space-between">
211
+ <Text>Status</Text>
212
+ {getStatusBadge(subscription.status)}
213
+ </Group>
214
+
215
+ {subscription.status !== 'none' && (
216
+ <>
217
+ <Group justify="space-between">
218
+ <Text>Plano</Text>
219
+ <Text fw={500}>{subscription.plan}</Text>
220
+ </Group>
221
+
222
+ {subscription.cancelAtPeriodEnd && (
223
+ <Text size="sm" c="orange">
224
+ Cancela em {new Date(subscription.currentPeriodEnd * 1000).toLocaleDateString()}
225
+ </Text>
226
+ )}
227
+ </>
228
+ )}
229
+
230
+ <Button variant="outline" onClick={openPortal} loading={loading}>
231
+ Gerenciar Assinatura
232
+ </Button>
233
+ </>
234
+ ) : (
235
+ <Text c="dimmed">Carregando...</Text>
236
+ )}
237
+ </Stack>
238
+ </Card>
239
+ )
240
+ }
241
+ ```
242
+
243
+ ## Embedded Payment Form
244
+
245
+ For custom payment forms (one-time payments):
246
+
247
+ ### Payment Form Component
248
+
249
+ ```javascript
250
+ // components/PaymentForm.jsx
251
+ import { Button, Stack, Text } from '@mantine/core'
252
+ import { PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js'
253
+ import { useState } from 'react'
254
+
255
+ export default function PaymentForm({ onSuccess }) {
256
+ const stripe = useStripe()
257
+ const elements = useElements()
258
+ const [loading, setLoading] = useState(false)
259
+ const [error, setError] = useState(null)
260
+
261
+ const handleSubmit = async (e) => {
262
+ e.preventDefault()
263
+ if (!stripe || !elements) return
264
+
265
+ setLoading(true)
266
+ setError(null)
267
+
268
+ const { error: submitError, paymentIntent } = await stripe.confirmPayment({
269
+ elements,
270
+ confirmParams: {
271
+ return_url: `${window.location.origin}/payment/success`,
272
+ },
273
+ redirect: 'if_required'
274
+ })
275
+
276
+ if (submitError) {
277
+ setError(submitError.message)
278
+ setLoading(false)
279
+ return
280
+ }
281
+
282
+ if (paymentIntent?.status === 'succeeded') {
283
+ onSuccess?.(paymentIntent)
284
+ }
285
+
286
+ setLoading(false)
287
+ }
288
+
289
+ return (
290
+ <form onSubmit={handleSubmit}>
291
+ <Stack>
292
+ <PaymentElement />
293
+
294
+ {error && <Text c="red" size="sm">{error}</Text>}
295
+
296
+ <Button type="submit" loading={loading} disabled={!stripe}>
297
+ Pagar
298
+ </Button>
299
+ </Stack>
300
+ </form>
301
+ )
302
+ }
303
+ ```
304
+
305
+ ### Using Payment Form
306
+
307
+ ```javascript
308
+ // pages/Checkout.jsx
309
+ import { Container, Card, Title, Text, Stack } from '@mantine/core'
310
+ import { Elements } from '@stripe/react-stripe-js'
311
+ import { useState, useEffect } from 'react'
312
+ import { stripePromise, elementsAppearance } from '../services/stripe'
313
+ import PaymentForm from '../components/PaymentForm'
314
+ import ky from 'ky'
315
+
316
+ export default function Checkout({ productId, amount }) {
317
+ const [clientSecret, setClientSecret] = useState(null)
318
+
319
+ useEffect(() => {
320
+ ky.post('/api/billing/payment/create', {
321
+ json: { productId, amount }
322
+ }).json()
323
+ .then(({ clientSecret }) => setClientSecret(clientSecret))
324
+ .catch(console.error)
325
+ }, [productId, amount])
326
+
327
+ if (!clientSecret) {
328
+ return <Text>Carregando...</Text>
329
+ }
330
+
331
+ return (
332
+ <Container size="sm">
333
+ <Card shadow="sm" padding="lg" radius="md" withBorder>
334
+ <Stack>
335
+ <Title order={3}>Finalizar Compra</Title>
336
+ <Text c="dimmed">Total: R$ {(amount / 100).toFixed(2)}</Text>
337
+
338
+ <Elements
339
+ stripe={stripePromise}
340
+ options={{
341
+ clientSecret,
342
+ appearance: elementsAppearance
343
+ }}
344
+ >
345
+ <PaymentForm onSuccess={() => console.log('Paid!')} />
346
+ </Elements>
347
+ </Stack>
348
+ </Card>
349
+ </Container>
350
+ )
351
+ }
352
+ ```
353
+
354
+ ## Subscription Hook
355
+
356
+ ```javascript
357
+ // hooks/useSubscription.js
358
+ import { useState, useEffect } from 'react'
359
+ import ky from 'ky'
360
+
361
+ export function useSubscription() {
362
+ const [subscription, setSubscription] = useState(null)
363
+ const [loading, setLoading] = useState(true)
364
+ const [error, setError] = useState(null)
365
+
366
+ const fetchSubscription = async () => {
367
+ setLoading(true)
368
+ try {
369
+ const data = await ky.get('/api/billing/subscription').json()
370
+ setSubscription(data)
371
+ setError(null)
372
+ } catch (err) {
373
+ setError(err)
374
+ } finally {
375
+ setLoading(false)
376
+ }
377
+ }
378
+
379
+ useEffect(() => {
380
+ fetchSubscription()
381
+ }, [])
382
+
383
+ const isActive = subscription?.status === 'active' || subscription?.status === 'trialing'
384
+ const isPro = isActive && subscription?.plan?.includes('pro')
385
+
386
+ return {
387
+ subscription,
388
+ loading,
389
+ error,
390
+ isActive,
391
+ isPro,
392
+ refresh: fetchSubscription
393
+ }
394
+ }
395
+ ```
396
+
397
+ ### Using the Hook
398
+
399
+ ```javascript
400
+ // components/PremiumFeature.jsx
401
+ import { Alert } from '@mantine/core'
402
+ import { useSubscription } from '../hooks/useSubscription'
403
+
404
+ export default function PremiumFeature({ children }) {
405
+ const { isActive, loading } = useSubscription()
406
+
407
+ if (loading) return null
408
+
409
+ if (!isActive) {
410
+ return (
411
+ <Alert color="yellow" title="Recurso Premium">
412
+ Faça upgrade para acessar este recurso.
413
+ </Alert>
414
+ )
415
+ }
416
+
417
+ return children
418
+ }
419
+ ```
420
+
421
+ ## Invoices List
422
+
423
+ ```javascript
424
+ // components/InvoiceList.jsx
425
+ import { Table, Badge, ActionIcon, Text } from '@mantine/core'
426
+ import { IconDownload } from '@tabler/icons-react'
427
+ import { useState, useEffect } from 'react'
428
+ import ky from 'ky'
429
+
430
+ export default function InvoiceList() {
431
+ const [invoices, setInvoices] = useState([])
432
+
433
+ useEffect(() => {
434
+ ky.get('/api/billing/invoices').json()
435
+ .then(({ data }) => setInvoices(data))
436
+ .catch(console.error)
437
+ }, [])
438
+
439
+ const getStatusBadge = (status) => {
440
+ const colors = { paid: 'green', open: 'blue', void: 'gray' }
441
+ return <Badge color={colors[status]}>{status}</Badge>
442
+ }
443
+
444
+ return (
445
+ <Table>
446
+ <Table.Thead>
447
+ <Table.Tr>
448
+ <Table.Th>Data</Table.Th>
449
+ <Table.Th>Valor</Table.Th>
450
+ <Table.Th>Status</Table.Th>
451
+ <Table.Th></Table.Th>
452
+ </Table.Tr>
453
+ </Table.Thead>
454
+ <Table.Tbody>
455
+ {invoices.map(invoice => (
456
+ <Table.Tr key={invoice.id}>
457
+ <Table.Td>{new Date(invoice.date * 1000).toLocaleDateString()}</Table.Td>
458
+ <Table.Td>R$ {(invoice.amount / 100).toFixed(2)}</Table.Td>
459
+ <Table.Td>{getStatusBadge(invoice.status)}</Table.Td>
460
+ <Table.Td>
461
+ {invoice.pdfUrl && (
462
+ <ActionIcon
463
+ component="a"
464
+ href={invoice.pdfUrl}
465
+ target="_blank"
466
+ variant="subtle"
467
+ >
468
+ <IconDownload size={16} />
469
+ </ActionIcon>
470
+ )}
471
+ </Table.Td>
472
+ </Table.Tr>
473
+ ))}
474
+ </Table.Tbody>
475
+ </Table>
476
+ )
477
+ }
478
+ ```