@fayz-ai/plugin-courses 0.2.2 → 0.2.4

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 (44) hide show
  1. package/README.md +2 -0
  2. package/dist/components/CommerceUI.d.ts +39 -0
  3. package/dist/components/CommerceUI.d.ts.map +1 -0
  4. package/dist/connectors/stripe.d.ts +3 -0
  5. package/dist/connectors/stripe.d.ts.map +1 -0
  6. package/dist/dashboardWidgets.d.ts +3 -0
  7. package/dist/dashboardWidgets.d.ts.map +1 -0
  8. package/dist/index.cjs +735 -54
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +738 -57
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/format.d.ts +4 -0
  14. package/dist/lib/format.d.ts.map +1 -0
  15. package/dist/locales/en.d.ts.map +1 -1
  16. package/dist/locales/pt-BR.d.ts.map +1 -1
  17. package/dist/pages/CourseEditorPage.d.ts.map +1 -1
  18. package/dist/pages/FinancialPage.d.ts +6 -0
  19. package/dist/pages/FinancialPage.d.ts.map +1 -0
  20. package/dist/pages/MembersAreaPage.d.ts +6 -0
  21. package/dist/pages/MembersAreaPage.d.ts.map +1 -0
  22. package/dist/pages/ReportsPage.d.ts +6 -0
  23. package/dist/pages/ReportsPage.d.ts.map +1 -0
  24. package/dist/pages/SalesPage.d.ts +6 -0
  25. package/dist/pages/SalesPage.d.ts.map +1 -0
  26. package/dist/pages/SubscriptionsPage.d.ts +6 -0
  27. package/dist/pages/SubscriptionsPage.d.ts.map +1 -0
  28. package/dist/settings/CoursesSettings.d.ts +6 -0
  29. package/dist/settings/CoursesSettings.d.ts.map +1 -0
  30. package/package.json +6 -4
  31. package/src/components/CommerceUI.tsx +77 -0
  32. package/src/connectors/stripe.ts +34 -0
  33. package/src/dashboardWidgets.tsx +51 -0
  34. package/src/index.ts +51 -12
  35. package/src/lib/format.ts +22 -0
  36. package/src/locales/en.ts +109 -0
  37. package/src/locales/pt-BR.ts +109 -0
  38. package/src/pages/CourseEditorPage.tsx +133 -35
  39. package/src/pages/FinancialPage.tsx +76 -0
  40. package/src/pages/MembersAreaPage.tsx +60 -0
  41. package/src/pages/ReportsPage.tsx +59 -0
  42. package/src/pages/SalesPage.tsx +57 -0
  43. package/src/pages/SubscriptionsPage.tsx +56 -0
  44. package/src/settings/CoursesSettings.tsx +55 -0
@@ -0,0 +1,34 @@
1
+ import type { ConnectorDefinition } from '@fayz-ai/core'
2
+ import { getCoursesProvider } from '@fayz-ai/courses'
3
+
4
+ // Stripe Connect modeled as a connector so it surfaces in the plugin's unified
5
+ // "Integrations" settings tab (same pattern as Financial Settings → Integrações),
6
+ // instead of a bespoke block in the General tab. The host plugin is 'courses'.
7
+ export function createStripeConnector(): ConnectorDefinition {
8
+ return {
9
+ id: 'stripe-connect',
10
+ hostPluginId: 'courses',
11
+ name: 'Stripe',
12
+ description: 'Receba os pagamentos dos alunos; a taxa da plataforma é aplicada automaticamente.',
13
+ icon: 'CreditCard',
14
+ authKind: 'oauth',
15
+ async getStatus() {
16
+ try {
17
+ const account = await getCoursesProvider().getCreatorAccount()
18
+ const fee = (account.platformFeeBps / 100).toFixed(2)
19
+ return {
20
+ connected: !!account.stripeAccountId && account.chargesEnabled,
21
+ detail: `Taxa da plataforma: ${fee}%`,
22
+ }
23
+ } catch {
24
+ return { connected: false }
25
+ }
26
+ },
27
+ async startOAuth(_redirectTo?: string): Promise<string> {
28
+ // Stage 3 wires this to the `courses-connect-onboard` edge function and
29
+ // returns the Stripe account-link URL to redirect to. Until then, surface
30
+ // a clear message (ConnectorsHub toasts a thrown error's message).
31
+ throw new Error('O onboarding do Stripe Connect é ligado na Etapa 3.')
32
+ },
33
+ }
34
+ }
@@ -0,0 +1,51 @@
1
+ import * as React from 'react'
2
+ import type { DashboardWidgetDef } from '@fayz-ai/core'
3
+ import { useTranslation } from '@fayz-ai/core'
4
+ import { KpiCard, defineKpiWidget } from '@fayz-ai/ui'
5
+ import { getCoursesProvider, type FinancialSummary } from '@fayz-ai/courses'
6
+ import { formatMoney, formatPercent } from './lib/format'
7
+
8
+ // Course KPIs contributed to the central admin Dashboard (rendered by
9
+ // @fayz-ai/plugin-dashboard's DashboardCanvas, which auto-collects every
10
+ // plugin's dashboardWidgets for surface 'home'). Mirrors Kiwify's dashboard.
11
+
12
+ function useSummary(): FinancialSummary | null {
13
+ const [summary, setSummary] = React.useState<FinancialSummary | null>(null)
14
+ React.useEffect(() => {
15
+ void getCoursesProvider().getFinancialSummary().then(setSummary)
16
+ }, [])
17
+ return summary
18
+ }
19
+
20
+ function RevenueKpi() {
21
+ const t = useTranslation()
22
+ const s = useSummary()
23
+ return <KpiCard label={t('courses.kpi.revenue') || 'Total revenue'} icon="DollarSign" loading={!s} value={s ? formatMoney(s.totalRevenue, s.currency) : undefined} />
24
+ }
25
+
26
+ function SalesKpi() {
27
+ const t = useTranslation()
28
+ const s = useSummary()
29
+ return <KpiCard label={t('courses.kpi.sales') || 'Sales'} icon="TrendingUp" loading={!s} value={s ? String(s.salesCount) : undefined} sub={s ? formatPercent(s.refundRate, 1) + ' ' + (t('courses.kpi.refunds') || 'refunds') : undefined} />
30
+ }
31
+
32
+ function MrrKpi() {
33
+ const t = useTranslation()
34
+ const s = useSummary()
35
+ return <KpiCard label={t('courses.kpi.mrr') || 'MRR'} icon="Repeat" loading={!s} value={s ? formatMoney(s.mrr, s.currency) : undefined} sub={s ? `${s.activeSubscriptions} ${t('courses.kpi.activeSubs') || 'active'}` : undefined} />
36
+ }
37
+
38
+ function FeesKpi() {
39
+ const t = useTranslation()
40
+ const s = useSummary()
41
+ return <KpiCard label={t('courses.kpi.fees') || 'Platform fees'} icon="Percent" loading={!s} value={s ? formatMoney(s.platformFeeTotal, s.currency) : undefined} />
42
+ }
43
+
44
+ export function createCoursesDashboardWidgets(): DashboardWidgetDef[] {
45
+ return [
46
+ defineKpiWidget({ id: 'courses.kpi.revenue', title: 'courses.kpi.revenue', domain: 'courses', defaultOrder: 0, component: RevenueKpi }),
47
+ defineKpiWidget({ id: 'courses.kpi.sales', title: 'courses.kpi.sales', domain: 'courses', defaultOrder: 1, component: SalesKpi }),
48
+ defineKpiWidget({ id: 'courses.kpi.mrr', title: 'courses.kpi.mrr', domain: 'courses', defaultOrder: 2, component: MrrKpi }),
49
+ defineKpiWidget({ id: 'courses.kpi.fees', title: 'courses.kpi.fees', domain: 'courses', defaultOrder: 3, component: FeesKpi }),
50
+ ]
51
+ }
package/src/index.ts CHANGED
@@ -5,15 +5,28 @@ import {
5
5
  getCoursesProviderOptional,
6
6
  setCoursesProvider,
7
7
  createMockCoursesProvider,
8
+ setCoursesTenantResolver,
8
9
  } from '@fayz-ai/courses'
10
+ import { useOrganizationStore } from '@fayz-ai/saas'
9
11
  import { coursesLocales } from './locales'
10
12
  import { CoursesListPage } from './pages/CoursesListPage'
11
13
  import { CourseEditorPage } from './pages/CourseEditorPage'
14
+ import { SalesPage } from './pages/SalesPage'
15
+ import { SubscriptionsPage } from './pages/SubscriptionsPage'
16
+ import { FinancialPage } from './pages/FinancialPage'
17
+ import { ReportsPage } from './pages/ReportsPage'
18
+ import { MembersAreaPage } from './pages/MembersAreaPage'
19
+ import { CoursesSettingsTab } from './settings/CoursesSettings'
20
+ import { createCoursesDashboardWidgets } from './dashboardWidgets'
21
+ import { createStripeConnector } from './connectors/stripe'
12
22
 
13
23
  // ---------------------------------------------------------------------------
14
- // @fayz-ai/plugin-courses — admin management for the courses domain. Mirrors
15
- // @fayz-ai/plugin-shop: a navigation entry + routes pointing at custom admin pages
16
- // built directly on @fayz-ai/courses. Native from day one (no saas-core imports).
24
+ // @fayz-ai/plugin-courses — admin management for the courses domain. This is a
25
+ // PLUGIN, not an app: it only contributes nav + routes + settings + dashboard
26
+ // widgets to the @fayz-ai/saas admin scaffold. Login, auth, permissions, the
27
+ // sidebar/AppShell chrome and layout all come from @fayz-ai/saas — the plugin
28
+ // never rebuilds them. Its screens render inside the saas AdminShell and use
29
+ // @fayz-ai/ui primitives.
17
30
  // ---------------------------------------------------------------------------
18
31
 
19
32
  export interface CoursesPluginOptions {
@@ -24,16 +37,26 @@ export interface CoursesPluginOptions {
24
37
  verticalId?: VerticalId
25
38
  }
26
39
 
40
+ const asComp = (c: unknown) => c as React.ComponentType<unknown>
41
+ const read = (feature: string) => ({ feature, action: 'read' as const })
42
+
27
43
  export function createCoursesPlugin(options?: CoursesPluginOptions): PluginManifest {
28
44
  // Register locales globally so keys resolve even before the I18nProvider mounts.
29
45
  registerTranslations(coursesLocales)
30
46
 
47
+ // Scope Supabase queries to the active organization (the creator/tenant). The
48
+ // mock provider ignores this; the Supabase provider appends .eq('tenant_id').
49
+ setCoursesTenantResolver(() => useOrganizationStore.getState().currentOrg?.id)
50
+
31
51
  // Defensive default: if the host app didn't set a provider, fall back to a
32
52
  // mock so the admin still renders. Apps that share admin↔member set their own.
33
53
  if (!getCoursesProviderOptional()) {
34
54
  setCoursesProvider(createMockCoursesProvider())
35
55
  }
36
56
 
57
+ const base = options?.navPosition ?? 1
58
+ const section = options?.navSection ?? 'main'
59
+
37
60
  return {
38
61
  id: 'courses',
39
62
  name: options?.navLabel ?? 'Courses',
@@ -43,20 +66,36 @@ export function createCoursesPlugin(options?: CoursesPluginOptions): PluginManif
43
66
  verticalId: options?.verticalId,
44
67
  defaultEnabled: true,
45
68
  dependencies: [],
69
+ declaredFeatures: [
70
+ { id: 'courses', label: 'Courses', group: 'Courses' },
71
+ { id: 'courses.sales', label: 'Sales', group: 'Courses' },
72
+ { id: 'courses.finance', label: 'Financial', group: 'Courses' },
73
+ ],
46
74
  navigation: [
47
- {
48
- section: options?.navSection ?? 'main',
49
- position: options?.navPosition ?? 1,
50
- label: options?.navLabel ?? 'Courses',
51
- route: '/courses',
52
- icon: 'BookOpen',
53
- },
75
+ { section, position: base, label: options?.navLabel ?? 'Courses', route: '/courses', icon: 'BookOpen', permission: read('courses') },
76
+ { section, position: base + 1, label: 'Members area', route: '/courses/members', icon: 'GraduationCap', permission: read('courses') },
77
+ { section, position: base + 2, label: 'Sales', route: '/courses/sales', icon: 'TrendingUp', permission: read('courses.sales') },
78
+ { section, position: base + 3, label: 'Subscriptions', route: '/courses/subscriptions', icon: 'Repeat', permission: read('courses.sales') },
79
+ { section, position: base + 4, label: 'Financial', route: '/courses/financial', icon: 'CreditCard', permission: read('courses.finance') },
80
+ { section, position: base + 5, label: 'Reports', route: '/courses/reports', icon: 'BarChart3', permission: read('courses.sales') },
54
81
  ],
55
82
  routes: [
56
- { path: '/courses', component: CoursesListPage as React.ComponentType<unknown> },
57
- { path: '/courses/:id', component: CourseEditorPage as React.ComponentType<unknown> },
83
+ // Static segments outscore the `:id` param in the saas router, so these
84
+ // resolve before the editor route (AdminShell sorts by routeScore).
85
+ { path: '/courses', component: asComp(CoursesListPage), permission: read('courses') },
86
+ { path: '/courses/members', component: asComp(MembersAreaPage), permission: read('courses') },
87
+ { path: '/courses/sales', component: asComp(SalesPage), permission: read('courses.sales') },
88
+ { path: '/courses/subscriptions', component: asComp(SubscriptionsPage), permission: read('courses.sales') },
89
+ { path: '/courses/financial', component: asComp(FinancialPage), permission: read('courses.finance') },
90
+ { path: '/courses/reports', component: asComp(ReportsPage), permission: read('courses.sales') },
91
+ { path: '/courses/:id', component: asComp(CourseEditorPage) },
58
92
  ],
59
93
  widgets: [],
94
+ dashboardWidgets: createCoursesDashboardWidgets(),
95
+ connectors: [createStripeConnector()],
96
+ settings: [
97
+ { id: 'courses', label: 'Courses', icon: 'BookOpen', component: asComp(CoursesSettingsTab), order: 20, permission: read('courses') },
98
+ ],
60
99
  locales: coursesLocales,
61
100
  }
62
101
  }
@@ -0,0 +1,22 @@
1
+ // Small display helpers shared across the courses admin pages. Kept local so the
2
+ // plugin depends only on @fayz-ai/core + @fayz-ai/ui + @fayz-ai/courses.
3
+
4
+ export function formatMoney(value: number, currency = 'BRL'): string {
5
+ try {
6
+ return new Intl.NumberFormat('pt-BR', { style: 'currency', currency }).format(value)
7
+ } catch {
8
+ return `${currency} ${value.toFixed(2)}`
9
+ }
10
+ }
11
+
12
+ export function formatDate(iso: string): string {
13
+ try {
14
+ return new Intl.DateTimeFormat('pt-BR', { day: '2-digit', month: '2-digit', year: 'numeric' }).format(new Date(iso))
15
+ } catch {
16
+ return iso.slice(0, 10)
17
+ }
18
+ }
19
+
20
+ export function formatPercent(fraction: number, digits = 0): string {
21
+ return `${(fraction * 100).toFixed(digits)}%`
22
+ }
package/src/locales/en.ts CHANGED
@@ -30,4 +30,113 @@ export const en: Record<string, string> = {
30
30
  'courses.editor.untitledCourse': 'Untitled course',
31
31
  'courses.editor.untitledModule': 'New module',
32
32
  'courses.editor.untitledLesson': 'New lesson',
33
+ // Editor tabs
34
+ 'courses.editor.tab.details': 'General',
35
+ 'courses.editor.tab.curriculum': 'Curriculum',
36
+ 'courses.editor.tab.offers': 'Offers',
37
+ 'courses.editor.tab.members': 'Members area',
38
+ 'courses.editor.tab.settings': 'Payment',
39
+ 'courses.editor.tab.checkout': 'Checkout',
40
+ 'courses.editor.tab.links': 'Links',
41
+ 'courses.editor.membersHelp': 'Buyers of this course are added to its members area automatically. Access groups per offer:',
42
+ 'courses.editor.defaultGroup': 'Default group',
43
+ 'courses.editor.settingsHelp': 'Payment methods offered at checkout (Stripe).',
44
+ 'courses.editor.payCard': 'Credit card (up to 12x)',
45
+ 'courses.editor.payPix': 'Pix',
46
+ 'courses.editor.payBoleto': 'Boleto',
47
+ 'courses.editor.settingsNote': 'Method availability depends on Stripe in your region; wired in Stage 3.',
48
+ 'courses.editor.checkoutSoon': 'Custom checkout builder comes in a later milestone. A default checkout is generated per offer.',
49
+ 'courses.editor.linksHelp': 'Shareable checkout links, one per offer.',
50
+ // Offers
51
+ 'courses.offers.help': 'Price points a customer can buy. Mark one default; order-bumps show on other checkouts.',
52
+ 'courses.offers.name': 'Offer name',
53
+ 'courses.offers.price': 'Price',
54
+ 'courses.offers.oneTime': 'One-time',
55
+ 'courses.offers.subscription': 'Subscription',
56
+ 'courses.offers.default': 'Default',
57
+ 'courses.offers.bump': 'Bump',
58
+ 'courses.offers.remove': 'Remove',
59
+ 'courses.offers.add': 'Add offer',
60
+ 'courses.offers.newName': 'New offer',
61
+ // Sales
62
+ 'courses.sales.title': 'Sales',
63
+ 'courses.sales.subtitle': 'Every transaction across your courses.',
64
+ 'courses.sales.date': 'Date',
65
+ 'courses.sales.customer': 'Customer',
66
+ 'courses.sales.method': 'Method',
67
+ 'courses.sales.status': 'Status',
68
+ 'courses.sales.fee': 'Platform fee',
69
+ 'courses.sales.amount': 'Amount',
70
+ 'courses.sales.count': 'Sales',
71
+ 'courses.sales.revenue': 'Revenue',
72
+ 'courses.sales.empty': 'No sales yet.',
73
+ 'courses.financialStatus.paid': 'Paid',
74
+ 'courses.financialStatus.pending': 'Pending',
75
+ 'courses.financialStatus.refunded': 'Refunded',
76
+ 'courses.financialStatus.chargeback': 'Chargeback',
77
+ // Subscriptions
78
+ 'courses.subs.title': 'Subscriptions',
79
+ 'courses.subs.subtitle': 'Recurring plans and MRR.',
80
+ 'courses.subs.start': 'Start date',
81
+ 'courses.subs.customer': 'Customer',
82
+ 'courses.subs.status': 'Status',
83
+ 'courses.subs.value': 'Net value',
84
+ 'courses.subs.active': 'Active subscriptions',
85
+ 'courses.subs.mrr': 'Monthly recurring revenue',
86
+ 'courses.subs.empty': 'No subscriptions yet.',
87
+ 'courses.subStatus.active': 'Active',
88
+ 'courses.subStatus.canceled': 'Canceled',
89
+ 'courses.subStatus.past_due': 'Past due',
90
+ 'courses.interval.month': 'month',
91
+ 'courses.interval.year': 'year',
92
+ // Financial
93
+ 'courses.financial.title': 'Financial settings',
94
+ 'courses.financial.subtitle': 'Balance, payouts and your Stripe connection.',
95
+ 'courses.financial.available': 'Available balance',
96
+ 'courses.financial.pending': 'Pending balance',
97
+ 'courses.financial.revenue': 'Total revenue',
98
+ 'courses.financial.feeCollected': 'Platform fees',
99
+ 'courses.financial.refundRate': 'refund rate',
100
+ 'courses.financial.payouts': 'Payouts',
101
+ 'courses.financial.noPayouts': 'No payouts yet.',
102
+ 'courses.financial.date': 'Date',
103
+ 'courses.financial.amount': 'Amount',
104
+ 'courses.financial.status': 'Status',
105
+ 'courses.financial.connect': 'Connect Stripe',
106
+ 'courses.financial.connected': 'Stripe connected',
107
+ 'courses.financial.notConnected': 'Connect your Stripe account to receive payouts. The platform fee is applied automatically on each sale.',
108
+ 'courses.financial.platformFee': 'Platform fee',
109
+ 'courses.financial.connectSoon': 'Stripe Connect onboarding wires up in Stage 3.',
110
+ 'courses.payoutStatus.paid': 'Paid',
111
+ 'courses.payoutStatus.pending': 'Pending',
112
+ 'courses.payoutStatus.failed': 'Failed',
113
+ // Reports
114
+ 'courses.reports.title': 'Reports',
115
+ 'courses.reports.subtitle': 'Revenue by course.',
116
+ 'courses.reports.byCourse': 'Revenue by course',
117
+ 'courses.reports.course': 'Course',
118
+ 'courses.reports.sales': 'Sales',
119
+ 'courses.reports.revenue': 'Revenue',
120
+ 'courses.reports.empty': 'No revenue yet.',
121
+ // Members area
122
+ 'courses.membersArea.title': 'Members area',
123
+ 'courses.membersArea.subtitle': 'One area per course; students land here after purchase.',
124
+ 'courses.membersArea.name': 'Members area',
125
+ 'courses.membersArea.status': 'Status',
126
+ 'courses.membersArea.students': 'Students',
127
+ 'courses.membersArea.empty': 'No members areas yet.',
128
+ // Settings
129
+ 'courses.settings.title': 'Course settings',
130
+ 'courses.settings.subtitle': 'Payments, currency and checkout methods',
131
+ 'courses.settings.payments': 'Payments',
132
+ 'courses.settings.paymentsHelp': 'Connect Stripe in the Integrations tab; the platform fee applies automatically.',
133
+ 'courses.settings.currency': 'Default currency',
134
+ 'courses.settings.methods': 'Payment methods',
135
+ // Dashboard KPIs
136
+ 'courses.kpi.revenue': 'Total revenue',
137
+ 'courses.kpi.sales': 'Sales',
138
+ 'courses.kpi.refunds': 'refunds',
139
+ 'courses.kpi.mrr': 'Recurring revenue (MRR)',
140
+ 'courses.kpi.activeSubs': 'active',
141
+ 'courses.kpi.fees': 'Platform fees',
33
142
  }
@@ -30,4 +30,113 @@ export const ptBR: Record<string, string> = {
30
30
  'courses.editor.untitledCourse': 'Curso sem título',
31
31
  'courses.editor.untitledModule': 'Novo módulo',
32
32
  'courses.editor.untitledLesson': 'Nova aula',
33
+ // Editor tabs
34
+ 'courses.editor.tab.details': 'Geral',
35
+ 'courses.editor.tab.curriculum': 'Conteúdo',
36
+ 'courses.editor.tab.offers': 'Ofertas',
37
+ 'courses.editor.tab.members': 'Área de membros',
38
+ 'courses.editor.tab.settings': 'Pagamento',
39
+ 'courses.editor.tab.checkout': 'Checkout',
40
+ 'courses.editor.tab.links': 'Links',
41
+ 'courses.editor.membersHelp': 'Quem compra este curso entra automaticamente na área de membros. Grupos de acesso por oferta:',
42
+ 'courses.editor.defaultGroup': 'Grupo padrão',
43
+ 'courses.editor.settingsHelp': 'Métodos de pagamento no checkout (Stripe).',
44
+ 'courses.editor.payCard': 'Cartão de crédito (até 12x)',
45
+ 'courses.editor.payPix': 'Pix',
46
+ 'courses.editor.payBoleto': 'Boleto',
47
+ 'courses.editor.settingsNote': 'A disponibilidade de cada método depende do Stripe na sua região; ligado na Etapa 3.',
48
+ 'courses.editor.checkoutSoon': 'O construtor de checkout chega numa etapa futura. Um checkout padrão é gerado por oferta.',
49
+ 'courses.editor.linksHelp': 'Links de checkout compartilháveis, um por oferta.',
50
+ // Offers
51
+ 'courses.offers.help': 'Preços que o cliente pode comprar. Marque um padrão; order-bumps aparecem nos checkouts das outras.',
52
+ 'courses.offers.name': 'Nome da oferta',
53
+ 'courses.offers.price': 'Preço',
54
+ 'courses.offers.oneTime': 'Único',
55
+ 'courses.offers.subscription': 'Assinatura',
56
+ 'courses.offers.default': 'Padrão',
57
+ 'courses.offers.bump': 'Bump',
58
+ 'courses.offers.remove': 'Remover',
59
+ 'courses.offers.add': 'Adicionar oferta',
60
+ 'courses.offers.newName': 'Nova oferta',
61
+ // Sales
62
+ 'courses.sales.title': 'Vendas',
63
+ 'courses.sales.subtitle': 'Todas as transações dos seus cursos.',
64
+ 'courses.sales.date': 'Data',
65
+ 'courses.sales.customer': 'Cliente',
66
+ 'courses.sales.method': 'Método',
67
+ 'courses.sales.status': 'Status',
68
+ 'courses.sales.fee': 'Taxa da plataforma',
69
+ 'courses.sales.amount': 'Valor',
70
+ 'courses.sales.count': 'Vendas',
71
+ 'courses.sales.revenue': 'Receita',
72
+ 'courses.sales.empty': 'Nenhuma venda ainda.',
73
+ 'courses.financialStatus.paid': 'Pago',
74
+ 'courses.financialStatus.pending': 'Pendente',
75
+ 'courses.financialStatus.refunded': 'Reembolsado',
76
+ 'courses.financialStatus.chargeback': 'Chargeback',
77
+ // Subscriptions
78
+ 'courses.subs.title': 'Assinaturas',
79
+ 'courses.subs.subtitle': 'Planos recorrentes e MRR.',
80
+ 'courses.subs.start': 'Início',
81
+ 'courses.subs.customer': 'Cliente',
82
+ 'courses.subs.status': 'Status',
83
+ 'courses.subs.value': 'Valor líquido',
84
+ 'courses.subs.active': 'Assinaturas ativas',
85
+ 'courses.subs.mrr': 'Receita recorrente mensal',
86
+ 'courses.subs.empty': 'Nenhuma assinatura ainda.',
87
+ 'courses.subStatus.active': 'Ativa',
88
+ 'courses.subStatus.canceled': 'Cancelada',
89
+ 'courses.subStatus.past_due': 'Em atraso',
90
+ 'courses.interval.month': 'mês',
91
+ 'courses.interval.year': 'ano',
92
+ // Financial
93
+ 'courses.financial.title': 'Financeiro',
94
+ 'courses.financial.subtitle': 'Saldo, saques e sua conexão com o Stripe.',
95
+ 'courses.financial.available': 'Saldo disponível',
96
+ 'courses.financial.pending': 'Saldo pendente',
97
+ 'courses.financial.revenue': 'Receita total',
98
+ 'courses.financial.feeCollected': 'Taxas da plataforma',
99
+ 'courses.financial.refundRate': 'taxa de reembolso',
100
+ 'courses.financial.payouts': 'Saques',
101
+ 'courses.financial.noPayouts': 'Nenhum saque ainda.',
102
+ 'courses.financial.date': 'Data',
103
+ 'courses.financial.amount': 'Valor',
104
+ 'courses.financial.status': 'Status',
105
+ 'courses.financial.connect': 'Conectar Stripe',
106
+ 'courses.financial.connected': 'Stripe conectado',
107
+ 'courses.financial.notConnected': 'Conecte sua conta Stripe para receber os saques. A taxa da plataforma é aplicada automaticamente em cada venda.',
108
+ 'courses.financial.platformFee': 'Taxa da plataforma',
109
+ 'courses.financial.connectSoon': 'O onboarding do Stripe Connect é ligado na Etapa 3.',
110
+ 'courses.payoutStatus.paid': 'Concluído',
111
+ 'courses.payoutStatus.pending': 'Pendente',
112
+ 'courses.payoutStatus.failed': 'Falhou',
113
+ // Reports
114
+ 'courses.reports.title': 'Relatórios',
115
+ 'courses.reports.subtitle': 'Receita por curso.',
116
+ 'courses.reports.byCourse': 'Receita por curso',
117
+ 'courses.reports.course': 'Curso',
118
+ 'courses.reports.sales': 'Vendas',
119
+ 'courses.reports.revenue': 'Receita',
120
+ 'courses.reports.empty': 'Nenhuma receita ainda.',
121
+ // Members area
122
+ 'courses.membersArea.title': 'Área de membros',
123
+ 'courses.membersArea.subtitle': 'Uma área por curso; o aluno entra após a compra.',
124
+ 'courses.membersArea.name': 'Área de membros',
125
+ 'courses.membersArea.status': 'Status',
126
+ 'courses.membersArea.students': 'Alunos',
127
+ 'courses.membersArea.empty': 'Nenhuma área de membros ainda.',
128
+ // Settings
129
+ 'courses.settings.title': 'Configurações do curso',
130
+ 'courses.settings.subtitle': 'Pagamentos, moeda e métodos de checkout',
131
+ 'courses.settings.payments': 'Pagamentos',
132
+ 'courses.settings.paymentsHelp': 'Conecte o Stripe na aba Integrações; a taxa da plataforma é aplicada automaticamente.',
133
+ 'courses.settings.currency': 'Moeda padrão',
134
+ 'courses.settings.methods': 'Métodos de pagamento',
135
+ // Dashboard KPIs
136
+ 'courses.kpi.revenue': 'Receita total',
137
+ 'courses.kpi.sales': 'Vendas',
138
+ 'courses.kpi.refunds': 'reembolsos',
139
+ 'courses.kpi.mrr': 'Receita recorrente (MRR)',
140
+ 'courses.kpi.activeSubs': 'ativas',
141
+ 'courses.kpi.fees': 'Taxas da plataforma',
33
142
  }