@hed-hog/billing 0.0.2

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 (232) hide show
  1. package/dist/adapters/billing-gateway-registry.d.ts +8 -0
  2. package/dist/adapters/billing-gateway-registry.d.ts.map +1 -0
  3. package/dist/adapters/billing-gateway-registry.js +33 -0
  4. package/dist/adapters/billing-gateway-registry.js.map +1 -0
  5. package/dist/adapters/mercadopago.adapter.d.ts +44 -0
  6. package/dist/adapters/mercadopago.adapter.d.ts.map +1 -0
  7. package/dist/adapters/mercadopago.adapter.js +68 -0
  8. package/dist/adapters/mercadopago.adapter.js.map +1 -0
  9. package/dist/adapters/pagarme.adapter.d.ts +44 -0
  10. package/dist/adapters/pagarme.adapter.d.ts.map +1 -0
  11. package/dist/adapters/pagarme.adapter.js +68 -0
  12. package/dist/adapters/pagarme.adapter.js.map +1 -0
  13. package/dist/adapters/payment-gateway.adapter.d.ts +15 -0
  14. package/dist/adapters/payment-gateway.adapter.d.ts.map +1 -0
  15. package/dist/adapters/payment-gateway.adapter.js +3 -0
  16. package/dist/adapters/payment-gateway.adapter.js.map +1 -0
  17. package/dist/adapters/stripe.adapter.d.ts +44 -0
  18. package/dist/adapters/stripe.adapter.d.ts.map +1 -0
  19. package/dist/adapters/stripe.adapter.js +69 -0
  20. package/dist/adapters/stripe.adapter.js.map +1 -0
  21. package/dist/billing-contracts.controller.d.ts +22 -0
  22. package/dist/billing-contracts.controller.d.ts.map +1 -0
  23. package/dist/billing-contracts.controller.js +84 -0
  24. package/dist/billing-contracts.controller.js.map +1 -0
  25. package/dist/billing-coupons.controller.d.ts +22 -0
  26. package/dist/billing-coupons.controller.d.ts.map +1 -0
  27. package/dist/billing-coupons.controller.js +84 -0
  28. package/dist/billing-coupons.controller.js.map +1 -0
  29. package/dist/billing-dashboard.controller.d.ts +13 -0
  30. package/dist/billing-dashboard.controller.d.ts.map +1 -0
  31. package/dist/billing-dashboard.controller.js +36 -0
  32. package/dist/billing-dashboard.controller.js.map +1 -0
  33. package/dist/billing-entitlements.controller.d.ts +19 -0
  34. package/dist/billing-entitlements.controller.d.ts.map +1 -0
  35. package/dist/billing-entitlements.controller.js +62 -0
  36. package/dist/billing-entitlements.controller.js.map +1 -0
  37. package/dist/billing-gateways.controller.d.ts +8 -0
  38. package/dist/billing-gateways.controller.d.ts.map +1 -0
  39. package/dist/billing-gateways.controller.js +50 -0
  40. package/dist/billing-gateways.controller.js.map +1 -0
  41. package/dist/billing-invoices.controller.d.ts +18 -0
  42. package/dist/billing-invoices.controller.d.ts.map +1 -0
  43. package/dist/billing-invoices.controller.js +61 -0
  44. package/dist/billing-invoices.controller.js.map +1 -0
  45. package/dist/billing-offers.controller.d.ts +22 -0
  46. package/dist/billing-offers.controller.d.ts.map +1 -0
  47. package/dist/billing-offers.controller.js +84 -0
  48. package/dist/billing-offers.controller.js.map +1 -0
  49. package/dist/billing-orders.controller.d.ts +19 -0
  50. package/dist/billing-orders.controller.d.ts.map +1 -0
  51. package/dist/billing-orders.controller.js +62 -0
  52. package/dist/billing-orders.controller.js.map +1 -0
  53. package/dist/billing-payments.controller.d.ts +17 -0
  54. package/dist/billing-payments.controller.d.ts.map +1 -0
  55. package/dist/billing-payments.controller.js +51 -0
  56. package/dist/billing-payments.controller.js.map +1 -0
  57. package/dist/billing-prices.controller.d.ts +22 -0
  58. package/dist/billing-prices.controller.d.ts.map +1 -0
  59. package/dist/billing-prices.controller.js +84 -0
  60. package/dist/billing-prices.controller.js.map +1 -0
  61. package/dist/billing-products.controller.d.ts +22 -0
  62. package/dist/billing-products.controller.d.ts.map +1 -0
  63. package/dist/billing-products.controller.js +84 -0
  64. package/dist/billing-products.controller.js.map +1 -0
  65. package/dist/billing-refunds.controller.d.ts +16 -0
  66. package/dist/billing-refunds.controller.d.ts.map +1 -0
  67. package/dist/billing-refunds.controller.js +41 -0
  68. package/dist/billing-refunds.controller.js.map +1 -0
  69. package/dist/billing-subscriptions.controller.d.ts +22 -0
  70. package/dist/billing-subscriptions.controller.d.ts.map +1 -0
  71. package/dist/billing-subscriptions.controller.js +92 -0
  72. package/dist/billing-subscriptions.controller.js.map +1 -0
  73. package/dist/billing-webhooks.controller.d.ts +16 -0
  74. package/dist/billing-webhooks.controller.d.ts.map +1 -0
  75. package/dist/billing-webhooks.controller.js +41 -0
  76. package/dist/billing-webhooks.controller.js.map +1 -0
  77. package/dist/billing.module.d.ts +3 -0
  78. package/dist/billing.module.d.ts.map +1 -0
  79. package/dist/billing.module.js +61 -0
  80. package/dist/billing.module.js.map +1 -0
  81. package/dist/billing.service.d.ts +169 -0
  82. package/dist/billing.service.d.ts.map +1 -0
  83. package/dist/billing.service.js +290 -0
  84. package/dist/billing.service.js.map +1 -0
  85. package/dist/dto/create-contract.dto.d.ts +11 -0
  86. package/dist/dto/create-contract.dto.d.ts.map +1 -0
  87. package/dist/dto/create-contract.dto.js +52 -0
  88. package/dist/dto/create-contract.dto.js.map +1 -0
  89. package/dist/dto/create-coupon.dto.d.ts +12 -0
  90. package/dist/dto/create-coupon.dto.d.ts.map +1 -0
  91. package/dist/dto/create-coupon.dto.js +60 -0
  92. package/dist/dto/create-coupon.dto.js.map +1 -0
  93. package/dist/dto/create-entitlement.dto.d.ts +12 -0
  94. package/dist/dto/create-entitlement.dto.d.ts.map +1 -0
  95. package/dist/dto/create-entitlement.dto.js +54 -0
  96. package/dist/dto/create-entitlement.dto.js.map +1 -0
  97. package/dist/dto/create-offer.dto.d.ts +7 -0
  98. package/dist/dto/create-offer.dto.d.ts.map +1 -0
  99. package/dist/dto/create-offer.dto.js +35 -0
  100. package/dist/dto/create-offer.dto.js.map +1 -0
  101. package/dist/dto/create-order.dto.d.ts +12 -0
  102. package/dist/dto/create-order.dto.d.ts.map +1 -0
  103. package/dist/dto/create-order.dto.js +65 -0
  104. package/dist/dto/create-order.dto.js.map +1 -0
  105. package/dist/dto/create-price.dto.d.ts +15 -0
  106. package/dist/dto/create-price.dto.d.ts.map +1 -0
  107. package/dist/dto/create-price.dto.js +76 -0
  108. package/dist/dto/create-price.dto.js.map +1 -0
  109. package/dist/dto/create-product.dto.d.ts +9 -0
  110. package/dist/dto/create-product.dto.d.ts.map +1 -0
  111. package/dist/dto/create-product.dto.js +45 -0
  112. package/dist/dto/create-product.dto.js.map +1 -0
  113. package/dist/dto/create-subscription.dto.d.ts +14 -0
  114. package/dist/dto/create-subscription.dto.d.ts.map +1 -0
  115. package/dist/dto/create-subscription.dto.js +67 -0
  116. package/dist/dto/create-subscription.dto.js.map +1 -0
  117. package/dist/dto/update-contract.dto.d.ts +6 -0
  118. package/dist/dto/update-contract.dto.d.ts.map +1 -0
  119. package/dist/dto/update-contract.dto.js +9 -0
  120. package/dist/dto/update-contract.dto.js.map +1 -0
  121. package/dist/dto/update-coupon.dto.d.ts +6 -0
  122. package/dist/dto/update-coupon.dto.d.ts.map +1 -0
  123. package/dist/dto/update-coupon.dto.js +9 -0
  124. package/dist/dto/update-coupon.dto.js.map +1 -0
  125. package/dist/dto/update-offer.dto.d.ts +6 -0
  126. package/dist/dto/update-offer.dto.d.ts.map +1 -0
  127. package/dist/dto/update-offer.dto.js +9 -0
  128. package/dist/dto/update-offer.dto.js.map +1 -0
  129. package/dist/dto/update-order.dto.d.ts +6 -0
  130. package/dist/dto/update-order.dto.d.ts.map +1 -0
  131. package/dist/dto/update-order.dto.js +9 -0
  132. package/dist/dto/update-order.dto.js.map +1 -0
  133. package/dist/dto/update-price.dto.d.ts +6 -0
  134. package/dist/dto/update-price.dto.d.ts.map +1 -0
  135. package/dist/dto/update-price.dto.js +9 -0
  136. package/dist/dto/update-price.dto.js.map +1 -0
  137. package/dist/dto/update-product.dto.d.ts +6 -0
  138. package/dist/dto/update-product.dto.d.ts.map +1 -0
  139. package/dist/dto/update-product.dto.js +9 -0
  140. package/dist/dto/update-product.dto.js.map +1 -0
  141. package/dist/dto/update-subscription.dto.d.ts +6 -0
  142. package/dist/dto/update-subscription.dto.d.ts.map +1 -0
  143. package/dist/dto/update-subscription.dto.js +9 -0
  144. package/dist/dto/update-subscription.dto.js.map +1 -0
  145. package/dist/index.d.ts +32 -0
  146. package/dist/index.d.ts.map +1 -0
  147. package/dist/index.js +48 -0
  148. package/dist/index.js.map +1 -0
  149. package/hedhog/data/menu.yaml +284 -0
  150. package/hedhog/data/role.yaml +7 -0
  151. package/hedhog/data/route.yaml +422 -0
  152. package/hedhog/frontend/app/_lib/billing-mocks.ts.ejs +270 -0
  153. package/hedhog/frontend/app/contracts/page.tsx.ejs +562 -0
  154. package/hedhog/frontend/app/coupons/page.tsx.ejs +669 -0
  155. package/hedhog/frontend/app/entitlements/page.tsx.ejs +526 -0
  156. package/hedhog/frontend/app/gateways/page.tsx.ejs +308 -0
  157. package/hedhog/frontend/app/invoices/page.tsx.ejs +179 -0
  158. package/hedhog/frontend/app/offers/page.tsx.ejs +483 -0
  159. package/hedhog/frontend/app/orders/page.tsx.ejs +424 -0
  160. package/hedhog/frontend/app/page.tsx.ejs +186 -0
  161. package/hedhog/frontend/app/payments/page.tsx.ejs +187 -0
  162. package/hedhog/frontend/app/prices/page.tsx.ejs +704 -0
  163. package/hedhog/frontend/app/products/page.tsx.ejs +568 -0
  164. package/hedhog/frontend/app/refunds/page.tsx.ejs +174 -0
  165. package/hedhog/frontend/app/reports/page.tsx.ejs +177 -0
  166. package/hedhog/frontend/app/subscriptions/page.tsx.ejs +283 -0
  167. package/hedhog/frontend/app/webhooks/page.tsx.ejs +172 -0
  168. package/hedhog/frontend/messages/en.json +551 -0
  169. package/hedhog/frontend/messages/pt.json +563 -0
  170. package/hedhog/table/billing_contract.yaml +37 -0
  171. package/hedhog/table/billing_contract_seat.yaml +28 -0
  172. package/hedhog/table/billing_coupon.yaml +42 -0
  173. package/hedhog/table/billing_entitlement.yaml +41 -0
  174. package/hedhog/table/billing_entitlement_event.yaml +20 -0
  175. package/hedhog/table/billing_invoice.yaml +60 -0
  176. package/hedhog/table/billing_invoice_item.yaml +25 -0
  177. package/hedhog/table/billing_offer.yaml +22 -0
  178. package/hedhog/table/billing_offer_price.yaml +23 -0
  179. package/hedhog/table/billing_order.yaml +50 -0
  180. package/hedhog/table/billing_order_item.yaml +35 -0
  181. package/hedhog/table/billing_payment.yaml +66 -0
  182. package/hedhog/table/billing_payment_method.yaml +49 -0
  183. package/hedhog/table/billing_payment_provider.yaml +21 -0
  184. package/hedhog/table/billing_price.yaml +53 -0
  185. package/hedhog/table/billing_product.yaml +31 -0
  186. package/hedhog/table/billing_product_item.yaml +24 -0
  187. package/hedhog/table/billing_provider_event.yaml +31 -0
  188. package/hedhog/table/billing_refund.yaml +41 -0
  189. package/hedhog/table/billing_seat_allocation.yaml +42 -0
  190. package/hedhog/table/billing_subscription.yaml +66 -0
  191. package/hedhog/table/billing_subscription_event.yaml +20 -0
  192. package/hedhog/table/billing_subscription_item.yaml +29 -0
  193. package/package.json +37 -0
  194. package/src/adapters/billing-gateway-registry.ts +23 -0
  195. package/src/adapters/mercadopago.adapter.ts +66 -0
  196. package/src/adapters/pagarme.adapter.ts +66 -0
  197. package/src/adapters/payment-gateway.adapter.ts +14 -0
  198. package/src/adapters/stripe.adapter.ts +67 -0
  199. package/src/billing-contracts.controller.ts +46 -0
  200. package/src/billing-coupons.controller.ts +46 -0
  201. package/src/billing-dashboard.controller.ts +14 -0
  202. package/src/billing-entitlements.controller.ts +34 -0
  203. package/src/billing-gateways.controller.ts +19 -0
  204. package/src/billing-invoices.controller.ts +32 -0
  205. package/src/billing-offers.controller.ts +46 -0
  206. package/src/billing-orders.controller.ts +33 -0
  207. package/src/billing-payments.controller.ts +20 -0
  208. package/src/billing-prices.controller.ts +46 -0
  209. package/src/billing-products.controller.ts +46 -0
  210. package/src/billing-refunds.controller.ts +15 -0
  211. package/src/billing-subscriptions.controller.ts +49 -0
  212. package/src/billing-webhooks.controller.ts +15 -0
  213. package/src/billing.module.ts +48 -0
  214. package/src/billing.service.ts +391 -0
  215. package/src/dto/create-contract.dto.ts +30 -0
  216. package/src/dto/create-coupon.dto.ts +37 -0
  217. package/src/dto/create-entitlement.dto.ts +31 -0
  218. package/src/dto/create-offer.dto.ts +17 -0
  219. package/src/dto/create-order.dto.ts +42 -0
  220. package/src/dto/create-price.dto.ts +50 -0
  221. package/src/dto/create-product.dto.ts +25 -0
  222. package/src/dto/create-subscription.dto.ts +42 -0
  223. package/src/dto/update-contract.dto.ts +4 -0
  224. package/src/dto/update-coupon.dto.ts +4 -0
  225. package/src/dto/update-offer.dto.ts +4 -0
  226. package/src/dto/update-order.dto.ts +4 -0
  227. package/src/dto/update-price.dto.ts +4 -0
  228. package/src/dto/update-product.dto.ts +4 -0
  229. package/src/dto/update-subscription.dto.ts +4 -0
  230. package/src/index.ts +32 -0
  231. package/src/language/en.json +8 -0
  232. package/src/language/pt.json +8 -0
@@ -0,0 +1,308 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import { Badge } from '@/components/ui/badge';
5
+ import { Button } from '@/components/ui/button';
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from '@/components/ui/card';
13
+ import {
14
+ Form,
15
+ FormControl,
16
+ FormField,
17
+ FormItem,
18
+ FormLabel,
19
+ FormMessage,
20
+ } from '@/components/ui/form';
21
+ import { Input } from '@/components/ui/input';
22
+ import {
23
+ Sheet,
24
+ SheetContent,
25
+ SheetDescription,
26
+ SheetHeader,
27
+ SheetTitle,
28
+ } from '@/components/ui/sheet';
29
+ import { Switch } from '@/components/ui/switch';
30
+ import { Textarea } from '@/components/ui/textarea';
31
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
32
+ import { zodResolver } from '@hookform/resolvers/zod';
33
+ import { Settings } from 'lucide-react';
34
+ import { useTranslations } from 'next-intl';
35
+ import { useEffect, useState } from 'react';
36
+ import { useForm } from 'react-hook-form';
37
+ import { z } from 'zod';
38
+
39
+ type Gateway = {
40
+ id: number;
41
+ name: string;
42
+ slug: string;
43
+ is_active: boolean;
44
+ config_json?: Record<string, unknown> | null;
45
+ };
46
+
47
+ const schema = z.object({
48
+ name: z.string().trim().min(1),
49
+ is_active: z.boolean(),
50
+ config_json: z.string().optional(),
51
+ });
52
+
53
+ type GatewayFormValues = z.infer<typeof schema>;
54
+
55
+ const fallbackGateways: Gateway[] = [
56
+ { id: 1, name: 'Stripe', slug: 'stripe', is_active: false, config_json: {} },
57
+ {
58
+ id: 2,
59
+ name: 'Pagarme',
60
+ slug: 'pagarme',
61
+ is_active: false,
62
+ config_json: {},
63
+ },
64
+ {
65
+ id: 3,
66
+ name: 'MercadoPago',
67
+ slug: 'mercadopago',
68
+ is_active: false,
69
+ config_json: {},
70
+ },
71
+ ];
72
+
73
+ export default function BillingGatewaysPage() {
74
+ const t = useTranslations('BillingGatewaysPage');
75
+ const { request, currentLocaleCode, showToastHandler } = useApp();
76
+ const [sheetOpen, setSheetOpen] = useState(false);
77
+ const [editingGateway, setEditingGateway] = useState<Gateway | null>(null);
78
+
79
+ const form = useForm<GatewayFormValues>({
80
+ resolver: zodResolver(schema),
81
+ defaultValues: {
82
+ name: '',
83
+ is_active: false,
84
+ config_json: '{}',
85
+ },
86
+ });
87
+
88
+ const { data, refetch } = useQuery<Gateway[]>({
89
+ queryKey: ['billing-gateways', currentLocaleCode],
90
+ queryFn: async () => {
91
+ const response = await request<Gateway[]>({
92
+ url: '/billing/gateways',
93
+ method: 'GET',
94
+ });
95
+
96
+ const payload = response.data;
97
+ if (!Array.isArray(payload)) {
98
+ return fallbackGateways;
99
+ }
100
+
101
+ return payload.length > 0 ? payload : fallbackGateways;
102
+ },
103
+ placeholderData: fallbackGateways,
104
+ });
105
+
106
+ const items = data ?? fallbackGateways;
107
+
108
+ useEffect(() => {
109
+ if (!sheetOpen || !editingGateway) {
110
+ return;
111
+ }
112
+
113
+ form.reset({
114
+ name: editingGateway.name,
115
+ is_active: Boolean(editingGateway.is_active),
116
+ config_json: JSON.stringify(editingGateway.config_json ?? {}, null, 2),
117
+ });
118
+ }, [editingGateway, form, sheetOpen]);
119
+
120
+ const onSubmit = async (values: GatewayFormValues) => {
121
+ if (!editingGateway) {
122
+ return;
123
+ }
124
+
125
+ try {
126
+ let parsedConfig: Record<string, unknown> = {};
127
+ if (values.config_json?.trim()) {
128
+ parsedConfig = JSON.parse(values.config_json);
129
+ }
130
+
131
+ await request({
132
+ url: `/billing/gateways/${editingGateway.slug}`,
133
+ method: 'PATCH',
134
+ data: {
135
+ name: values.name,
136
+ is_active: values.is_active,
137
+ config_json: parsedConfig,
138
+ },
139
+ });
140
+
141
+ showToastHandler?.('success', t('messages.saveSuccess'));
142
+ setSheetOpen(false);
143
+ setEditingGateway(null);
144
+ await refetch();
145
+ } catch {
146
+ showToastHandler?.('error', t('messages.saveError'));
147
+ }
148
+ };
149
+
150
+ const quickToggle = async (gateway: Gateway, checked: boolean) => {
151
+ try {
152
+ await request({
153
+ url: `/billing/gateways/${gateway.slug}`,
154
+ method: 'PATCH',
155
+ data: {
156
+ is_active: checked,
157
+ },
158
+ });
159
+ await refetch();
160
+ } catch {
161
+ showToastHandler?.('error', t('messages.toggleError'));
162
+ }
163
+ };
164
+
165
+ return (
166
+ <Page>
167
+ <PageHeader
168
+ title={t('title')}
169
+ description={t('description')}
170
+ breadcrumbs={[
171
+ { label: t('breadcrumbs.home'), href: '/' },
172
+ { label: t('breadcrumbs.billing'), href: '/billing' },
173
+ { label: t('breadcrumbs.gateways') },
174
+ ]}
175
+ />
176
+
177
+ <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
178
+ {items.map((gateway) => (
179
+ <Card key={gateway.slug} className="shadow-none">
180
+ <CardHeader>
181
+ <CardTitle className="flex items-center justify-between text-lg">
182
+ {gateway.name}
183
+ <Badge
184
+ className={
185
+ gateway.is_active
186
+ ? 'bg-green-100 text-green-800'
187
+ : 'bg-gray-100 text-gray-800'
188
+ }
189
+ >
190
+ {gateway.is_active
191
+ ? t('status.active')
192
+ : t('status.inactive')}
193
+ </Badge>
194
+ </CardTitle>
195
+ <CardDescription>{gateway.slug}</CardDescription>
196
+ </CardHeader>
197
+ <CardContent className="space-y-4">
198
+ <div className="flex items-center justify-between">
199
+ <span className="text-sm text-muted-foreground">
200
+ {t('fields.active')}
201
+ </span>
202
+ <Switch
203
+ checked={gateway.is_active}
204
+ onCheckedChange={(checked) =>
205
+ void quickToggle(gateway, checked)
206
+ }
207
+ />
208
+ </div>
209
+
210
+ <Button
211
+ className="w-full"
212
+ variant="outline"
213
+ onClick={() => {
214
+ setEditingGateway(gateway);
215
+ setSheetOpen(true);
216
+ }}
217
+ >
218
+ <Settings className="mr-2 size-4" />
219
+ {t('actions.configure')}
220
+ </Button>
221
+ </CardContent>
222
+ </Card>
223
+ ))}
224
+ </div>
225
+
226
+ <Sheet
227
+ open={sheetOpen}
228
+ onOpenChange={(open) => {
229
+ setSheetOpen(open);
230
+ if (!open) {
231
+ setEditingGateway(null);
232
+ }
233
+ }}
234
+ >
235
+ <SheetContent className="w-full sm:max-w-xl">
236
+ <SheetHeader>
237
+ <SheetTitle>{t('sheet.title')}</SheetTitle>
238
+ <SheetDescription>{t('sheet.description')}</SheetDescription>
239
+ </SheetHeader>
240
+
241
+ <Form {...form}>
242
+ <form
243
+ className="space-y-4 p-4"
244
+ onSubmit={form.handleSubmit(onSubmit)}
245
+ >
246
+ <FormField
247
+ control={form.control}
248
+ name="name"
249
+ render={({ field }) => (
250
+ <FormItem>
251
+ <FormLabel>{t('form.name')}</FormLabel>
252
+ <FormControl>
253
+ <Input {...field} />
254
+ </FormControl>
255
+ <FormMessage />
256
+ </FormItem>
257
+ )}
258
+ />
259
+
260
+ <FormField
261
+ control={form.control}
262
+ name="is_active"
263
+ render={({ field }) => (
264
+ <FormItem className="flex items-center justify-between">
265
+ <FormLabel>{t('form.isActive')}</FormLabel>
266
+ <FormControl>
267
+ <Switch
268
+ checked={field.value}
269
+ onCheckedChange={field.onChange}
270
+ />
271
+ </FormControl>
272
+ <FormMessage />
273
+ </FormItem>
274
+ )}
275
+ />
276
+
277
+ <FormField
278
+ control={form.control}
279
+ name="config_json"
280
+ render={({ field }) => (
281
+ <FormItem>
282
+ <FormLabel>{t('form.configJson')}</FormLabel>
283
+ <FormControl>
284
+ <Textarea
285
+ rows={12}
286
+ {...field}
287
+ value={field.value ?? '{}'}
288
+ />
289
+ </FormControl>
290
+ <FormMessage />
291
+ </FormItem>
292
+ )}
293
+ />
294
+
295
+ <Button
296
+ type="submit"
297
+ className="w-full"
298
+ disabled={form.formState.isSubmitting}
299
+ >
300
+ {t('actions.save')}
301
+ </Button>
302
+ </form>
303
+ </Form>
304
+ </SheetContent>
305
+ </Sheet>
306
+ </Page>
307
+ );
308
+ }
@@ -0,0 +1,179 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Page,
5
+ PageHeader,
6
+ PaginationFooter,
7
+ SearchBar,
8
+ } from '@/components/entity-list';
9
+ import { Badge } from '@/components/ui/badge';
10
+ import {
11
+ Table,
12
+ TableBody,
13
+ TableCell,
14
+ TableHead,
15
+ TableHeader,
16
+ TableRow,
17
+ } from '@/components/ui/table';
18
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
19
+ import { useTranslations } from 'next-intl';
20
+ import { useState } from 'react';
21
+
22
+ type Invoice = {
23
+ id: number;
24
+ invoice_number: string;
25
+ status: string;
26
+ currency: string;
27
+ total_cents: number;
28
+ due_date: string | null;
29
+ paid_at: string | null;
30
+ };
31
+
32
+ type ListResponse<T> = {
33
+ data?: T[];
34
+ total?: number;
35
+ };
36
+
37
+ const formatCurrency = (cents: number, currency = 'BRL') =>
38
+ new Intl.NumberFormat('pt-BR', { style: 'currency', currency }).format(
39
+ cents / 100
40
+ );
41
+
42
+ const badgeClass = (value: string) => {
43
+ if (['active', 'paid', 'authorized', 'approved'].includes(value)) {
44
+ return 'bg-green-100 text-green-800';
45
+ }
46
+ if (['pending', 'open', 'draft'].includes(value)) {
47
+ return 'bg-yellow-100 text-yellow-800';
48
+ }
49
+ if (['failed', 'canceled', 'void', 'uncollectible'].includes(value)) {
50
+ return 'bg-red-100 text-red-800';
51
+ }
52
+ if (['paused', 'inactive'].includes(value)) {
53
+ return 'bg-gray-100 text-gray-800';
54
+ }
55
+ return 'bg-slate-100 text-slate-800';
56
+ };
57
+
58
+ const formatDate = (value: string | null) => {
59
+ if (!value) return '-';
60
+ return new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short' }).format(
61
+ new Date(value)
62
+ );
63
+ };
64
+
65
+ export default function BillingInvoicesPage() {
66
+ const t = useTranslations('BillingInvoicesPage');
67
+ const { request, currentLocaleCode } = useApp();
68
+ const [search, setSearch] = useState('');
69
+ const [page, setPage] = useState(1);
70
+ const [pageSize, setPageSize] = useState(12);
71
+
72
+ const { data, refetch } = useQuery<{ items: Invoice[]; total: number }>({
73
+ queryKey: ['billing-invoices', currentLocaleCode, page, pageSize, search],
74
+ queryFn: async () => {
75
+ const response = await request<ListResponse<Invoice>>({
76
+ url: '/billing/invoices',
77
+ method: 'GET',
78
+ params: { page, pageSize, search: search.trim() || undefined },
79
+ });
80
+
81
+ const payload = response.data as ListResponse<Invoice> | Invoice[];
82
+ if (Array.isArray(payload)) {
83
+ return { items: payload, total: payload.length };
84
+ }
85
+
86
+ return {
87
+ items: payload.data ?? [],
88
+ total: payload.total ?? payload.data?.length ?? 0,
89
+ };
90
+ },
91
+ placeholderData: (old) => old,
92
+ });
93
+
94
+ const items = data?.items ?? [];
95
+ const totalItems = data?.total ?? 0;
96
+
97
+ return (
98
+ <Page>
99
+ <PageHeader
100
+ title={t('title')}
101
+ description={t('description')}
102
+ breadcrumbs={[
103
+ { label: t('breadcrumbs.home'), href: '/' },
104
+ { label: t('breadcrumbs.billing'), href: '/billing' },
105
+ { label: t('breadcrumbs.invoices') },
106
+ ]}
107
+ />
108
+
109
+ <SearchBar
110
+ searchQuery={search}
111
+ onSearchChange={(value) => {
112
+ setSearch(value);
113
+ setPage(1);
114
+ }}
115
+ onSearch={() => {
116
+ setPage(1);
117
+ void refetch();
118
+ }}
119
+ placeholder={t('filters.searchPlaceholder')}
120
+ />
121
+
122
+ <div className="overflow-x-auto rounded-md border">
123
+ <Table>
124
+ <TableHeader>
125
+ <TableRow>
126
+ <TableHead>{t('table.columns.invoiceNumber')}</TableHead>
127
+ <TableHead>{t('table.columns.status')}</TableHead>
128
+ <TableHead>{t('table.columns.currency')}</TableHead>
129
+ <TableHead>{t('table.columns.total')}</TableHead>
130
+ <TableHead>{t('table.columns.dueDate')}</TableHead>
131
+ <TableHead>{t('table.columns.paidAt')}</TableHead>
132
+ </TableRow>
133
+ </TableHeader>
134
+ <TableBody>
135
+ {items.length === 0 && (
136
+ <TableRow>
137
+ <TableCell
138
+ colSpan={6}
139
+ className="text-center text-muted-foreground"
140
+ >
141
+ {t('table.empty')}
142
+ </TableCell>
143
+ </TableRow>
144
+ )}
145
+ {items.map((item) => (
146
+ <TableRow key={item.id}>
147
+ <TableCell className="font-medium">
148
+ {item.invoice_number}
149
+ </TableCell>
150
+ <TableCell>
151
+ <Badge className={badgeClass(item.status)}>
152
+ {item.status}
153
+ </Badge>
154
+ </TableCell>
155
+ <TableCell>{item.currency}</TableCell>
156
+ <TableCell>
157
+ {formatCurrency(item.total_cents, item.currency)}
158
+ </TableCell>
159
+ <TableCell>{formatDate(item.due_date)}</TableCell>
160
+ <TableCell>{formatDate(item.paid_at)}</TableCell>
161
+ </TableRow>
162
+ ))}
163
+ </TableBody>
164
+ </Table>
165
+ </div>
166
+
167
+ <PaginationFooter
168
+ currentPage={page}
169
+ pageSize={pageSize}
170
+ totalItems={totalItems}
171
+ onPageChange={setPage}
172
+ onPageSizeChange={(nextSize) => {
173
+ setPageSize(nextSize);
174
+ setPage(1);
175
+ }}
176
+ />
177
+ </Page>
178
+ );
179
+ }