@hed-hog/billing 0.0.2 → 0.0.285

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 (51) hide show
  1. package/README.md +420 -0
  2. package/dist/billing-contracts.controller.d.ts +85 -4
  3. package/dist/billing-contracts.controller.d.ts.map +1 -1
  4. package/dist/billing-coupons.controller.d.ts +60 -4
  5. package/dist/billing-coupons.controller.d.ts.map +1 -1
  6. package/dist/billing-dashboard.controller.d.ts +20 -5
  7. package/dist/billing-dashboard.controller.d.ts.map +1 -1
  8. package/dist/billing-entitlements.controller.d.ts +28 -2
  9. package/dist/billing-entitlements.controller.d.ts.map +1 -1
  10. package/dist/billing-gateways.controller.d.ts +18 -2
  11. package/dist/billing-gateways.controller.d.ts.map +1 -1
  12. package/dist/billing-invoices.controller.d.ts +56 -2
  13. package/dist/billing-invoices.controller.d.ts.map +1 -1
  14. package/dist/billing-offers.controller.d.ts +61 -4
  15. package/dist/billing-offers.controller.d.ts.map +1 -1
  16. package/dist/billing-orders.controller.d.ts +39 -2
  17. package/dist/billing-orders.controller.d.ts.map +1 -1
  18. package/dist/billing-payments.controller.d.ts +16 -1
  19. package/dist/billing-payments.controller.d.ts.map +1 -1
  20. package/dist/billing-prices.controller.d.ts +80 -4
  21. package/dist/billing-prices.controller.d.ts.map +1 -1
  22. package/dist/billing-products.controller.d.ts +62 -4
  23. package/dist/billing-products.controller.d.ts.map +1 -1
  24. package/dist/billing-subscriptions.controller.d.ts +138 -5
  25. package/dist/billing-subscriptions.controller.d.ts.map +1 -1
  26. package/dist/billing.service.d.ts +663 -39
  27. package/dist/billing.service.d.ts.map +1 -1
  28. package/dist/billing.service.js +135 -16
  29. package/dist/billing.service.js.map +1 -1
  30. package/hedhog/data/menu.yaml +1 -1
  31. package/hedhog/frontend/app/contracts/page.tsx.ejs +68 -64
  32. package/hedhog/frontend/app/coupons/page.tsx.ejs +81 -77
  33. package/hedhog/frontend/app/entitlements/page.tsx.ejs +59 -58
  34. package/hedhog/frontend/app/gateways/page.tsx.ejs +125 -57
  35. package/hedhog/frontend/app/invoices/page.tsx.ejs +49 -43
  36. package/hedhog/frontend/app/offers/page.tsx.ejs +68 -64
  37. package/hedhog/frontend/app/orders/page.tsx.ejs +47 -46
  38. package/hedhog/frontend/app/page.tsx.ejs +186 -186
  39. package/hedhog/frontend/app/payments/page.tsx.ejs +51 -45
  40. package/hedhog/frontend/app/prices/page.tsx.ejs +81 -75
  41. package/hedhog/frontend/app/products/page.tsx.ejs +79 -73
  42. package/hedhog/frontend/app/refunds/page.tsx.ejs +50 -44
  43. package/hedhog/frontend/app/reports/page.tsx.ejs +1 -1
  44. package/hedhog/frontend/app/seats/page.tsx.ejs +826 -0
  45. package/hedhog/frontend/app/subscriptions/page.tsx.ejs +95 -90
  46. package/hedhog/frontend/app/webhooks/page.tsx.ejs +47 -39
  47. package/hedhog/frontend/messages/en.json +640 -551
  48. package/hedhog/frontend/messages/pt.json +652 -563
  49. package/hedhog/table/billing_payment_method.yaml +1 -1
  50. package/package.json +4 -3
  51. package/src/billing.service.ts +299 -17
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import {
4
+ EmptyState,
4
5
  Page,
5
6
  PageHeader,
6
7
  PaginationFooter,
@@ -53,7 +54,7 @@ import {
53
54
  } from '@/components/ui/table';
54
55
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
55
56
  import { zodResolver } from '@hookform/resolvers/zod';
56
- import { Pencil, Plus, Trash2 } from 'lucide-react';
57
+ import { FileText, Pencil, Plus, Trash2 } from 'lucide-react';
57
58
  import { useTranslations } from 'next-intl';
58
59
  import { useEffect, useState } from 'react';
59
60
  import { useForm } from 'react-hook-form';
@@ -128,7 +129,7 @@ const mapBillingTypeToApi = (value: PriceFormValues['billing_type']) => {
128
129
  };
129
130
 
130
131
  export default function BillingPricesPage() {
131
- const t = useTranslations('BillingPricesPage');
132
+ const t = useTranslations('billing.BillingPricesPage');
132
133
  const { request, showToastHandler, currentLocaleCode } = useApp();
133
134
  const [search, setSearch] = useState('');
134
135
  const [billingFilter, setBillingFilter] = useState('all');
@@ -355,82 +356,87 @@ export default function BillingPricesPage() {
355
356
  }}
356
357
  />
357
358
 
358
- <div className="overflow-x-auto rounded-md border">
359
- <Table>
360
- <TableHeader>
361
- <TableRow>
362
- <TableHead>{t('table.columns.name')}</TableHead>
363
- <TableHead>{t('table.columns.product')}</TableHead>
364
- <TableHead>{t('table.columns.billingType')}</TableHead>
365
- <TableHead>{t('table.columns.amount')}</TableHead>
366
- <TableHead>{t('table.columns.intervalUnit')}</TableHead>
367
- <TableHead>{t('table.columns.isActive')}</TableHead>
368
- <TableHead className="w-[120px] text-right">
369
- {t('table.columns.actions')}
370
- </TableHead>
371
- </TableRow>
372
- </TableHeader>
373
- <TableBody>
374
- {items.length === 0 && (
359
+ {items.length > 0 ? (
360
+ <div className="overflow-x-auto rounded-md border">
361
+ <Table>
362
+ <TableHeader>
375
363
  <TableRow>
376
- <TableCell
377
- colSpan={7}
378
- className="text-center text-muted-foreground"
379
- >
380
- {t('table.empty')}
381
- </TableCell>
364
+ <TableHead>{t('table.columns.name')}</TableHead>
365
+ <TableHead>{t('table.columns.product')}</TableHead>
366
+ <TableHead>{t('table.columns.billingType')}</TableHead>
367
+ <TableHead>{t('table.columns.amount')}</TableHead>
368
+ <TableHead>{t('table.columns.intervalUnit')}</TableHead>
369
+ <TableHead>{t('table.columns.isActive')}</TableHead>
370
+ <TableHead className="w-[120px] text-right">
371
+ {t('table.columns.actions')}
372
+ </TableHead>
382
373
  </TableRow>
383
- )}
384
- {items.map((item) => (
385
- <TableRow key={item.id}>
386
- <TableCell className="font-medium">{item.name}</TableCell>
387
- <TableCell>
388
- {item.product_name ?? item.product?.name ?? '-'}
389
- </TableCell>
390
- <TableCell>
391
- <Badge className={badgeClass(item.billing_type)}>
392
- {item.billing_type}
393
- </Badge>
394
- </TableCell>
395
- <TableCell>
396
- {formatCurrency(item.amount_cents, item.currency)}
397
- </TableCell>
398
- <TableCell>{item.interval_unit ?? '-'}</TableCell>
399
- <TableCell>
400
- <Badge
401
- className={badgeClass(
402
- item.is_active ? 'active' : 'inactive'
403
- )}
404
- >
405
- {item.is_active ? t('status.active') : t('status.inactive')}
406
- </Badge>
407
- </TableCell>
408
- <TableCell>
409
- <div className="flex justify-end gap-2">
410
- <Button
411
- variant="outline"
412
- size="icon"
413
- onClick={() => {
414
- setEditingPrice(item);
415
- setSheetOpen(true);
416
- }}
374
+ </TableHeader>
375
+ <TableBody>
376
+ {items.map((item) => (
377
+ <TableRow key={item.id}>
378
+ <TableCell className="font-medium">{item.name}</TableCell>
379
+ <TableCell>
380
+ {item.product_name ?? item.product?.name ?? '-'}
381
+ </TableCell>
382
+ <TableCell>
383
+ <Badge className={badgeClass(item.billing_type)}>
384
+ {item.billing_type}
385
+ </Badge>
386
+ </TableCell>
387
+ <TableCell>
388
+ {formatCurrency(item.amount_cents, item.currency)}
389
+ </TableCell>
390
+ <TableCell>{item.interval_unit ?? '-'}</TableCell>
391
+ <TableCell>
392
+ <Badge
393
+ className={badgeClass(
394
+ item.is_active ? 'active' : 'inactive'
395
+ )}
417
396
  >
418
- <Pencil className="size-4" />
419
- </Button>
420
- <Button
421
- variant="destructive"
422
- size="icon"
423
- onClick={() => setDeleteId(item.id)}
424
- >
425
- <Trash2 className="size-4" />
426
- </Button>
427
- </div>
428
- </TableCell>
429
- </TableRow>
430
- ))}
431
- </TableBody>
432
- </Table>
433
- </div>
397
+ {item.is_active
398
+ ? t('status.active')
399
+ : t('status.inactive')}
400
+ </Badge>
401
+ </TableCell>
402
+ <TableCell>
403
+ <div className="flex justify-end gap-2">
404
+ <Button
405
+ variant="outline"
406
+ size="icon"
407
+ onClick={() => {
408
+ setEditingPrice(item);
409
+ setSheetOpen(true);
410
+ }}
411
+ >
412
+ <Pencil className="size-4" />
413
+ </Button>
414
+ <Button
415
+ variant="destructive"
416
+ size="icon"
417
+ onClick={() => setDeleteId(item.id)}
418
+ >
419
+ <Trash2 className="size-4" />
420
+ </Button>
421
+ </div>
422
+ </TableCell>
423
+ </TableRow>
424
+ ))}
425
+ </TableBody>
426
+ </Table>
427
+ </div>
428
+ ) : (
429
+ <EmptyState
430
+ icon={<FileText className="h-12 w-12" />}
431
+ title={t('table.empty')}
432
+ description={t('description')}
433
+ actionLabel={t('actions.create')}
434
+ onAction={() => {
435
+ setEditingPrice(null);
436
+ setSheetOpen(true);
437
+ }}
438
+ />
439
+ )}
434
440
 
435
441
  <PaginationFooter
436
442
  currentPage={page}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import {
4
+ EmptyState,
4
5
  Page,
5
6
  PageHeader,
6
7
  PaginationFooter,
@@ -53,7 +54,7 @@ import {
53
54
  import { Textarea } from '@/components/ui/textarea';
54
55
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
55
56
  import { zodResolver } from '@hookform/resolvers/zod';
56
- import { Pencil, Plus, Trash2 } from 'lucide-react';
57
+ import { FileText, Pencil, Plus, Trash2 } from 'lucide-react';
57
58
  import { useTranslations } from 'next-intl';
58
59
  import { useEffect, useMemo, useState } from 'react';
59
60
  import { useForm } from 'react-hook-form';
@@ -135,7 +136,7 @@ const statusBadgeClass = (value: string) => {
135
136
  };
136
137
 
137
138
  export default function BillingProductsPage() {
138
- const t = useTranslations('BillingProductsPage');
139
+ const t = useTranslations('billing.BillingProductsPage');
139
140
  const { request, showToastHandler, currentLocaleCode } = useApp();
140
141
  const [search, setSearch] = useState('');
141
142
  const [statusFilter, setStatusFilter] = useState('all');
@@ -328,80 +329,85 @@ export default function BillingProductsPage() {
328
329
  }}
329
330
  />
330
331
 
331
- <div className="overflow-x-auto rounded-md border">
332
- <Table>
333
- <TableHeader>
334
- <TableRow>
335
- <TableHead>{t('table.columns.name')}</TableHead>
336
- <TableHead>{t('table.columns.code')}</TableHead>
337
- <TableHead>{t('table.columns.productType')}</TableHead>
338
- <TableHead>{t('table.columns.isActive')}</TableHead>
339
- <TableHead>{t('table.columns.createdAt')}</TableHead>
340
- <TableHead className="w-[120px] text-right">
341
- {t('table.columns.actions')}
342
- </TableHead>
343
- </TableRow>
344
- </TableHeader>
345
- <TableBody>
346
- {items.length === 0 && (
332
+ {items.length > 0 ? (
333
+ <div className="overflow-x-auto rounded-md border">
334
+ <Table>
335
+ <TableHeader>
347
336
  <TableRow>
348
- <TableCell
349
- colSpan={6}
350
- className="text-center text-muted-foreground"
351
- >
352
- {t('table.empty')}
353
- </TableCell>
337
+ <TableHead>{t('table.columns.name')}</TableHead>
338
+ <TableHead>{t('table.columns.code')}</TableHead>
339
+ <TableHead>{t('table.columns.productType')}</TableHead>
340
+ <TableHead>{t('table.columns.isActive')}</TableHead>
341
+ <TableHead>{t('table.columns.createdAt')}</TableHead>
342
+ <TableHead className="w-[120px] text-right">
343
+ {t('table.columns.actions')}
344
+ </TableHead>
354
345
  </TableRow>
355
- )}
356
- {items.map((item) => (
357
- <TableRow key={item.id}>
358
- <TableCell className="font-medium">{item.name}</TableCell>
359
- <TableCell>{item.code ?? '-'}</TableCell>
360
- <TableCell>
361
- <Badge className={statusBadgeClass('draft')}>
362
- {mapProductTypeFromApi(item.product_type)}
363
- </Badge>
364
- </TableCell>
365
- <TableCell>
366
- <Badge
367
- className={statusBadgeClass(
368
- item.is_active ? 'active' : 'inactive'
369
- )}
370
- >
371
- {item.is_active ? t('status.active') : t('status.inactive')}
372
- </Badge>
373
- </TableCell>
374
- <TableCell>
375
- {new Intl.DateTimeFormat('pt-BR', {
376
- dateStyle: 'short',
377
- }).format(new Date(item.created_at))}
378
- </TableCell>
379
- <TableCell>
380
- <div className="flex justify-end gap-2">
381
- <Button
382
- variant="outline"
383
- size="icon"
384
- onClick={() => {
385
- setEditingProduct(item);
386
- setSheetOpen(true);
387
- }}
346
+ </TableHeader>
347
+ <TableBody>
348
+ {items.map((item) => (
349
+ <TableRow key={item.id}>
350
+ <TableCell className="font-medium">{item.name}</TableCell>
351
+ <TableCell>{item.code ?? '-'}</TableCell>
352
+ <TableCell>
353
+ <Badge className={statusBadgeClass('draft')}>
354
+ {mapProductTypeFromApi(item.product_type)}
355
+ </Badge>
356
+ </TableCell>
357
+ <TableCell>
358
+ <Badge
359
+ className={statusBadgeClass(
360
+ item.is_active ? 'active' : 'inactive'
361
+ )}
388
362
  >
389
- <Pencil className="size-4" />
390
- </Button>
391
- <Button
392
- variant="destructive"
393
- size="icon"
394
- onClick={() => setDeleteId(item.id)}
395
- >
396
- <Trash2 className="size-4" />
397
- </Button>
398
- </div>
399
- </TableCell>
400
- </TableRow>
401
- ))}
402
- </TableBody>
403
- </Table>
404
- </div>
363
+ {item.is_active
364
+ ? t('status.active')
365
+ : t('status.inactive')}
366
+ </Badge>
367
+ </TableCell>
368
+ <TableCell>
369
+ {new Intl.DateTimeFormat('pt-BR', {
370
+ dateStyle: 'short',
371
+ }).format(new Date(item.created_at))}
372
+ </TableCell>
373
+ <TableCell>
374
+ <div className="flex justify-end gap-2">
375
+ <Button
376
+ variant="outline"
377
+ size="icon"
378
+ onClick={() => {
379
+ setEditingProduct(item);
380
+ setSheetOpen(true);
381
+ }}
382
+ >
383
+ <Pencil className="size-4" />
384
+ </Button>
385
+ <Button
386
+ variant="destructive"
387
+ size="icon"
388
+ onClick={() => setDeleteId(item.id)}
389
+ >
390
+ <Trash2 className="size-4" />
391
+ </Button>
392
+ </div>
393
+ </TableCell>
394
+ </TableRow>
395
+ ))}
396
+ </TableBody>
397
+ </Table>
398
+ </div>
399
+ ) : (
400
+ <EmptyState
401
+ icon={<FileText className="h-12 w-12" />}
402
+ title={t('table.empty')}
403
+ description={t('description')}
404
+ actionLabel={t('actions.create')}
405
+ onAction={() => {
406
+ setEditingProduct(null);
407
+ setSheetOpen(true);
408
+ }}
409
+ />
410
+ )}
405
411
 
406
412
  <PaginationFooter
407
413
  currentPage={page}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import {
4
+ EmptyState,
4
5
  Page,
5
6
  PageHeader,
6
7
  PaginationFooter,
@@ -16,6 +17,7 @@ import {
16
17
  TableRow,
17
18
  } from '@/components/ui/table';
18
19
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
20
+ import { FileText } from 'lucide-react';
19
21
  import { useTranslations } from 'next-intl';
20
22
  import { useState } from 'react';
21
23
 
@@ -57,7 +59,7 @@ const badgeClass = (value: string) => {
57
59
  };
58
60
 
59
61
  export default function BillingRefundsPage() {
60
- const t = useTranslations('BillingRefundsPage');
62
+ const t = useTranslations('billing.BillingRefundsPage');
61
63
  const { request, currentLocaleCode } = useApp();
62
64
  const [search, setSearch] = useState('');
63
65
  const [page, setPage] = useState(1);
@@ -113,51 +115,55 @@ export default function BillingRefundsPage() {
113
115
  placeholder={t('filters.searchPlaceholder')}
114
116
  />
115
117
 
116
- <div className="overflow-x-auto rounded-md border">
117
- <Table>
118
- <TableHeader>
119
- <TableRow>
120
- <TableHead>{t('table.columns.amount')}</TableHead>
121
- <TableHead>{t('table.columns.reason')}</TableHead>
122
- <TableHead>{t('table.columns.status')}</TableHead>
123
- <TableHead>{t('table.columns.completedAt')}</TableHead>
124
- </TableRow>
125
- </TableHeader>
126
- <TableBody>
127
- {items.length === 0 && (
118
+ {items.length > 0 ? (
119
+ <div className="overflow-x-auto rounded-md border">
120
+ <Table>
121
+ <TableHeader>
128
122
  <TableRow>
129
- <TableCell
130
- colSpan={4}
131
- className="text-center text-muted-foreground"
132
- >
133
- {t('table.empty')}
134
- </TableCell>
123
+ <TableHead>{t('table.columns.amount')}</TableHead>
124
+ <TableHead>{t('table.columns.reason')}</TableHead>
125
+ <TableHead>{t('table.columns.status')}</TableHead>
126
+ <TableHead>{t('table.columns.completedAt')}</TableHead>
135
127
  </TableRow>
136
- )}
137
- {items.map((item) => (
138
- <TableRow key={item.id}>
139
- <TableCell>
140
- {formatCurrency(item.amount_cents, item.currency ?? 'BRL')}
141
- </TableCell>
142
- <TableCell>{item.reason ?? '-'}</TableCell>
143
- <TableCell>
144
- <Badge className={badgeClass(item.status)}>
145
- {item.status}
146
- </Badge>
147
- </TableCell>
148
- <TableCell>
149
- {item.completed_at
150
- ? new Intl.DateTimeFormat('pt-BR', {
151
- dateStyle: 'short',
152
- timeStyle: 'short',
153
- }).format(new Date(item.completed_at))
154
- : '-'}
155
- </TableCell>
156
- </TableRow>
157
- ))}
158
- </TableBody>
159
- </Table>
160
- </div>
128
+ </TableHeader>
129
+ <TableBody>
130
+ {items.map((item) => (
131
+ <TableRow key={item.id}>
132
+ <TableCell>
133
+ {formatCurrency(item.amount_cents, item.currency ?? 'BRL')}
134
+ </TableCell>
135
+ <TableCell>{item.reason ?? '-'}</TableCell>
136
+ <TableCell>
137
+ <Badge className={badgeClass(item.status)}>
138
+ {item.status}
139
+ </Badge>
140
+ </TableCell>
141
+ <TableCell>
142
+ {item.completed_at
143
+ ? new Intl.DateTimeFormat('pt-BR', {
144
+ dateStyle: 'short',
145
+ timeStyle: 'short',
146
+ }).format(new Date(item.completed_at))
147
+ : '-'}
148
+ </TableCell>
149
+ </TableRow>
150
+ ))}
151
+ </TableBody>
152
+ </Table>
153
+ </div>
154
+ ) : (
155
+ <EmptyState
156
+ icon={<FileText className="h-12 w-12" />}
157
+ title={t('table.empty')}
158
+ description={t('description')}
159
+ actionLabel={t('emptyAction')}
160
+ onAction={() => {
161
+ setSearch('');
162
+ setPage(1);
163
+ void refetch();
164
+ }}
165
+ />
166
+ )}
161
167
 
162
168
  <PaginationFooter
163
169
  currentPage={page}
@@ -38,7 +38,7 @@ const formatCurrency = (cents: number, currency = 'BRL') =>
38
38
  const PIE_COLORS = ['#16a34a', '#f59e0b', '#ef4444', '#0ea5e9'];
39
39
 
40
40
  export default function BillingReportsPage() {
41
- const t = useTranslations('BillingReportsPage');
41
+ const t = useTranslations('billing.BillingReportsPage');
42
42
 
43
43
  const cohortByMonth = MOCK_SUBSCRIPTIONS.reduce<Record<string, number>>(
44
44
  (acc, item) => {