@magnet-cms/plugin-stripe 0.1.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/dist/backend/index.cjs +2278 -0
- package/dist/backend/index.d.cts +317 -0
- package/dist/backend/index.d.ts +317 -0
- package/dist/backend/index.js +19 -0
- package/dist/chunk-IPPZL6QM.js +2257 -0
- package/dist/frontend/bundle.iife.js +23271 -0
- package/dist/frontend/bundle.iife.js.map +1 -0
- package/dist/index.cjs +2261 -0
- package/dist/index.d.cts +139 -0
- package/dist/index.d.ts +139 -0
- package/dist/index.js +1 -0
- package/package.json +83 -0
- package/src/admin/components/recent-payments.tsx +106 -0
- package/src/admin/components/revenue-chart.tsx +56 -0
- package/src/admin/components/subscription-metrics.tsx +65 -0
- package/src/admin/index.ts +120 -0
- package/src/admin/pages/customers.tsx +209 -0
- package/src/admin/pages/payments.tsx +251 -0
- package/src/admin/pages/products.tsx +220 -0
- package/src/admin/pages/stripe-dashboard.tsx +90 -0
- package/src/admin/pages/subscriptions.tsx +251 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { EnvVarRequirement, PluginMagnetProvider } from '@magnet-cms/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for the Stripe plugin.
|
|
5
|
+
*
|
|
6
|
+
* Passed via `PluginModule.forRoot({ plugins: [{ plugin: StripePlugin, options: { ... } }] })`
|
|
7
|
+
* and injected in services via `@InjectPluginOptions('stripe')`.
|
|
8
|
+
*/
|
|
9
|
+
interface StripePluginConfig {
|
|
10
|
+
/** Stripe secret API key (sk_live_... or sk_test_...). Auto-resolved from STRIPE_SECRET_KEY env var. */
|
|
11
|
+
secretKey?: string;
|
|
12
|
+
/** Stripe webhook signing secret (whsec_...). Auto-resolved from STRIPE_WEBHOOK_SECRET env var. */
|
|
13
|
+
webhookSecret?: string;
|
|
14
|
+
/** Stripe publishable key (pk_live_... or pk_test_...) — exposed to frontend. Auto-resolved from STRIPE_PUBLISHABLE_KEY env var. */
|
|
15
|
+
publishableKey?: string;
|
|
16
|
+
/** Whether to sync products/prices from Stripe via webhooks (default: true) */
|
|
17
|
+
syncProducts?: boolean;
|
|
18
|
+
/** Whether to enable Customer Portal session creation (default: true) */
|
|
19
|
+
portalEnabled?: boolean;
|
|
20
|
+
/** Default currency for checkout sessions (default: 'usd') */
|
|
21
|
+
currency?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Feature flags per plan name.
|
|
24
|
+
* Map of product/plan name to list of feature identifiers.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* {
|
|
29
|
+
* 'pro': ['unlimited-servers', 'priority-support'],
|
|
30
|
+
* 'basic': ['5-servers'],
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
features?: Record<string, string[]>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Response from the subscription access endpoint.
|
|
38
|
+
*/
|
|
39
|
+
interface SubscriptionAccessResponse {
|
|
40
|
+
/** Whether the user has an active subscription */
|
|
41
|
+
hasActiveSubscription: boolean;
|
|
42
|
+
/** Plan/product name (null if no subscription) */
|
|
43
|
+
plan: string | null;
|
|
44
|
+
/** When the current period ends (null if no subscription) */
|
|
45
|
+
expiresAt: Date | null;
|
|
46
|
+
/** Feature flags for the user's plan */
|
|
47
|
+
features: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Request body for creating a checkout session.
|
|
51
|
+
*/
|
|
52
|
+
interface CreateCheckoutDto {
|
|
53
|
+
/** Stripe Price ID to create checkout for */
|
|
54
|
+
priceId: string;
|
|
55
|
+
/** URL to redirect to on success */
|
|
56
|
+
successUrl: string;
|
|
57
|
+
/** URL to redirect to on cancel */
|
|
58
|
+
cancelUrl: string;
|
|
59
|
+
/** Optional Magnet user ID to associate with the customer */
|
|
60
|
+
userId?: string;
|
|
61
|
+
/** Optional metadata to attach to the checkout session */
|
|
62
|
+
metadata?: Record<string, string>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Request body for creating a customer portal session.
|
|
66
|
+
*/
|
|
67
|
+
interface CreatePortalDto {
|
|
68
|
+
/** Stripe Customer ID */
|
|
69
|
+
customerId?: string;
|
|
70
|
+
/** Magnet user ID (used to look up customer if customerId not provided) */
|
|
71
|
+
userId?: string;
|
|
72
|
+
/** URL to redirect to when the user is done */
|
|
73
|
+
returnUrl?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Response from creating a checkout or portal session.
|
|
77
|
+
*/
|
|
78
|
+
interface SessionResponse {
|
|
79
|
+
/** Session ID */
|
|
80
|
+
sessionId: string;
|
|
81
|
+
/** URL to redirect the user to */
|
|
82
|
+
url: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Admin metrics response from the dashboard endpoint.
|
|
86
|
+
*/
|
|
87
|
+
interface StripeMetricsResponse {
|
|
88
|
+
/** Monthly Recurring Revenue in cents */
|
|
89
|
+
mrr: number;
|
|
90
|
+
/** Total revenue this month in cents */
|
|
91
|
+
revenueThisMonth: number;
|
|
92
|
+
/** Number of active subscriptions */
|
|
93
|
+
activeSubscriptions: number;
|
|
94
|
+
/** Churn rate as a percentage (0-100) */
|
|
95
|
+
churnRate: number;
|
|
96
|
+
/** Revenue data for the last 12 months */
|
|
97
|
+
revenueByMonth: Array<{
|
|
98
|
+
month: string;
|
|
99
|
+
revenue: number;
|
|
100
|
+
}>;
|
|
101
|
+
/** Recent payments */
|
|
102
|
+
recentPayments: Array<{
|
|
103
|
+
id: string;
|
|
104
|
+
amount: number;
|
|
105
|
+
currency: string;
|
|
106
|
+
status: string;
|
|
107
|
+
customerEmail: string;
|
|
108
|
+
createdAt: string;
|
|
109
|
+
}>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Stripe Plugin
|
|
114
|
+
*
|
|
115
|
+
* Provides Stripe payment integration for Magnet CMS:
|
|
116
|
+
* - Product, price, subscription, and customer management
|
|
117
|
+
* - Webhook processing with idempotency
|
|
118
|
+
* - Checkout and Customer Portal sessions
|
|
119
|
+
* - Admin dashboard with revenue metrics
|
|
120
|
+
* - User subscription access and feature flags
|
|
121
|
+
*/
|
|
122
|
+
declare class StripePlugin {
|
|
123
|
+
/** Environment variables used by this plugin */
|
|
124
|
+
static readonly envVars: EnvVarRequirement[];
|
|
125
|
+
/**
|
|
126
|
+
* Create a configured plugin provider for MagnetModule.forRoot().
|
|
127
|
+
* Auto-resolves secret values from environment variables if not provided.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* MagnetModule.forRoot([
|
|
132
|
+
* StripePlugin.forRoot({ currency: 'usd' }),
|
|
133
|
+
* ])
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
static forRoot(config?: Partial<StripePluginConfig>): PluginMagnetProvider;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { type CreateCheckoutDto, type CreatePortalDto, type SessionResponse, type StripeMetricsResponse, StripePlugin, type StripePluginConfig, type SubscriptionAccessResponse };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { EnvVarRequirement, PluginMagnetProvider } from '@magnet-cms/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for the Stripe plugin.
|
|
5
|
+
*
|
|
6
|
+
* Passed via `PluginModule.forRoot({ plugins: [{ plugin: StripePlugin, options: { ... } }] })`
|
|
7
|
+
* and injected in services via `@InjectPluginOptions('stripe')`.
|
|
8
|
+
*/
|
|
9
|
+
interface StripePluginConfig {
|
|
10
|
+
/** Stripe secret API key (sk_live_... or sk_test_...). Auto-resolved from STRIPE_SECRET_KEY env var. */
|
|
11
|
+
secretKey?: string;
|
|
12
|
+
/** Stripe webhook signing secret (whsec_...). Auto-resolved from STRIPE_WEBHOOK_SECRET env var. */
|
|
13
|
+
webhookSecret?: string;
|
|
14
|
+
/** Stripe publishable key (pk_live_... or pk_test_...) — exposed to frontend. Auto-resolved from STRIPE_PUBLISHABLE_KEY env var. */
|
|
15
|
+
publishableKey?: string;
|
|
16
|
+
/** Whether to sync products/prices from Stripe via webhooks (default: true) */
|
|
17
|
+
syncProducts?: boolean;
|
|
18
|
+
/** Whether to enable Customer Portal session creation (default: true) */
|
|
19
|
+
portalEnabled?: boolean;
|
|
20
|
+
/** Default currency for checkout sessions (default: 'usd') */
|
|
21
|
+
currency?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Feature flags per plan name.
|
|
24
|
+
* Map of product/plan name to list of feature identifiers.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* {
|
|
29
|
+
* 'pro': ['unlimited-servers', 'priority-support'],
|
|
30
|
+
* 'basic': ['5-servers'],
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
features?: Record<string, string[]>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Response from the subscription access endpoint.
|
|
38
|
+
*/
|
|
39
|
+
interface SubscriptionAccessResponse {
|
|
40
|
+
/** Whether the user has an active subscription */
|
|
41
|
+
hasActiveSubscription: boolean;
|
|
42
|
+
/** Plan/product name (null if no subscription) */
|
|
43
|
+
plan: string | null;
|
|
44
|
+
/** When the current period ends (null if no subscription) */
|
|
45
|
+
expiresAt: Date | null;
|
|
46
|
+
/** Feature flags for the user's plan */
|
|
47
|
+
features: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Request body for creating a checkout session.
|
|
51
|
+
*/
|
|
52
|
+
interface CreateCheckoutDto {
|
|
53
|
+
/** Stripe Price ID to create checkout for */
|
|
54
|
+
priceId: string;
|
|
55
|
+
/** URL to redirect to on success */
|
|
56
|
+
successUrl: string;
|
|
57
|
+
/** URL to redirect to on cancel */
|
|
58
|
+
cancelUrl: string;
|
|
59
|
+
/** Optional Magnet user ID to associate with the customer */
|
|
60
|
+
userId?: string;
|
|
61
|
+
/** Optional metadata to attach to the checkout session */
|
|
62
|
+
metadata?: Record<string, string>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Request body for creating a customer portal session.
|
|
66
|
+
*/
|
|
67
|
+
interface CreatePortalDto {
|
|
68
|
+
/** Stripe Customer ID */
|
|
69
|
+
customerId?: string;
|
|
70
|
+
/** Magnet user ID (used to look up customer if customerId not provided) */
|
|
71
|
+
userId?: string;
|
|
72
|
+
/** URL to redirect to when the user is done */
|
|
73
|
+
returnUrl?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Response from creating a checkout or portal session.
|
|
77
|
+
*/
|
|
78
|
+
interface SessionResponse {
|
|
79
|
+
/** Session ID */
|
|
80
|
+
sessionId: string;
|
|
81
|
+
/** URL to redirect the user to */
|
|
82
|
+
url: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Admin metrics response from the dashboard endpoint.
|
|
86
|
+
*/
|
|
87
|
+
interface StripeMetricsResponse {
|
|
88
|
+
/** Monthly Recurring Revenue in cents */
|
|
89
|
+
mrr: number;
|
|
90
|
+
/** Total revenue this month in cents */
|
|
91
|
+
revenueThisMonth: number;
|
|
92
|
+
/** Number of active subscriptions */
|
|
93
|
+
activeSubscriptions: number;
|
|
94
|
+
/** Churn rate as a percentage (0-100) */
|
|
95
|
+
churnRate: number;
|
|
96
|
+
/** Revenue data for the last 12 months */
|
|
97
|
+
revenueByMonth: Array<{
|
|
98
|
+
month: string;
|
|
99
|
+
revenue: number;
|
|
100
|
+
}>;
|
|
101
|
+
/** Recent payments */
|
|
102
|
+
recentPayments: Array<{
|
|
103
|
+
id: string;
|
|
104
|
+
amount: number;
|
|
105
|
+
currency: string;
|
|
106
|
+
status: string;
|
|
107
|
+
customerEmail: string;
|
|
108
|
+
createdAt: string;
|
|
109
|
+
}>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Stripe Plugin
|
|
114
|
+
*
|
|
115
|
+
* Provides Stripe payment integration for Magnet CMS:
|
|
116
|
+
* - Product, price, subscription, and customer management
|
|
117
|
+
* - Webhook processing with idempotency
|
|
118
|
+
* - Checkout and Customer Portal sessions
|
|
119
|
+
* - Admin dashboard with revenue metrics
|
|
120
|
+
* - User subscription access and feature flags
|
|
121
|
+
*/
|
|
122
|
+
declare class StripePlugin {
|
|
123
|
+
/** Environment variables used by this plugin */
|
|
124
|
+
static readonly envVars: EnvVarRequirement[];
|
|
125
|
+
/**
|
|
126
|
+
* Create a configured plugin provider for MagnetModule.forRoot().
|
|
127
|
+
* Auto-resolves secret values from environment variables if not provided.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* MagnetModule.forRoot([
|
|
132
|
+
* StripePlugin.forRoot({ currency: 'usd' }),
|
|
133
|
+
* ])
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
static forRoot(config?: Partial<StripePluginConfig>): PluginMagnetProvider;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { type CreateCheckoutDto, type CreatePortalDto, type SessionResponse, type StripeMetricsResponse, StripePlugin, type StripePluginConfig, type SubscriptionAccessResponse };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { StripePlugin } from './chunk-IPPZL6QM.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@magnet-cms/plugin-stripe",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Stripe payments plugin for Magnet CMS — products, subscriptions, webhooks, and admin dashboard",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"./backend": {
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/backend/index.d.ts",
|
|
22
|
+
"default": "./dist/backend/index.js"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/backend/index.d.cts",
|
|
26
|
+
"default": "./dist/backend/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"./frontend": {
|
|
30
|
+
"import": "./src/admin/index.ts",
|
|
31
|
+
"types": "./src/admin/index.ts"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"src/admin"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build:dev": "tsup --watch",
|
|
41
|
+
"build": "tsup && bun run build:frontend",
|
|
42
|
+
"build:frontend": "vite build",
|
|
43
|
+
"build:frontend:watch": "vite build --watch"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@magnet-cms/admin": "workspace:*",
|
|
47
|
+
"@magnet-cms/common": "workspace:*",
|
|
48
|
+
"@magnet-cms/core": "workspace:*",
|
|
49
|
+
"@magnet-cms/ui": "workspace:*",
|
|
50
|
+
"@magnet-cms/utils": "workspace:*",
|
|
51
|
+
"@nestjs/common": "^11.1.12",
|
|
52
|
+
"@repo/biome": "workspace:*",
|
|
53
|
+
"@repo/tsup": "workspace:*",
|
|
54
|
+
"@repo/typescript-config": "workspace:*",
|
|
55
|
+
"@types/react": "^19.0.1",
|
|
56
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
57
|
+
"lucide-react": "^0.468.0",
|
|
58
|
+
"react": "^19.0.0",
|
|
59
|
+
"react-router-dom": "^7.1.1",
|
|
60
|
+
"recharts": "^2.15.0",
|
|
61
|
+
"reflect-metadata": "0.2.2",
|
|
62
|
+
"stripe": "^17.0.0",
|
|
63
|
+
"vite": "^6.0.7"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@magnet-cms/admin": "^0.1.2",
|
|
67
|
+
"@magnet-cms/common": "^0.1.0",
|
|
68
|
+
"@magnet-cms/core": "^1.0.3",
|
|
69
|
+
"@magnet-cms/ui": "^0.1.0",
|
|
70
|
+
"@magnet-cms/utils": "^0.1.0",
|
|
71
|
+
"@nestjs/common": "^11.1.12",
|
|
72
|
+
"lucide-react": ">=0.400.0",
|
|
73
|
+
"react": ">=18.0.0",
|
|
74
|
+
"react-router-dom": ">=6.0.0",
|
|
75
|
+
"reflect-metadata": "0.2.2",
|
|
76
|
+
"stripe": "^17.0.0"
|
|
77
|
+
},
|
|
78
|
+
"magnet": {
|
|
79
|
+
"type": "plugin",
|
|
80
|
+
"backend": true,
|
|
81
|
+
"frontend": true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Badge,
|
|
3
|
+
DataTable,
|
|
4
|
+
type DataTableColumn,
|
|
5
|
+
} from '@magnet-cms/ui/components'
|
|
6
|
+
|
|
7
|
+
const contentManagerStyles = `
|
|
8
|
+
.table-row-hover:hover td {
|
|
9
|
+
background-color: #F9FAFB;
|
|
10
|
+
}
|
|
11
|
+
.table-row-hover.group:hover td {
|
|
12
|
+
background-color: #F9FAFB;
|
|
13
|
+
}
|
|
14
|
+
`
|
|
15
|
+
|
|
16
|
+
interface Payment {
|
|
17
|
+
id: string
|
|
18
|
+
amount: number
|
|
19
|
+
currency: string
|
|
20
|
+
status: string
|
|
21
|
+
customerEmail: string
|
|
22
|
+
createdAt: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface RecentPaymentsProps {
|
|
26
|
+
payments: Payment[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatCurrency(cents: number, currency: string): string {
|
|
30
|
+
return `${currency.toUpperCase()} ${(cents / 100).toFixed(2)}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getStatusVariant(
|
|
34
|
+
status: string,
|
|
35
|
+
): 'default' | 'secondary' | 'destructive' | 'outline' {
|
|
36
|
+
switch (status) {
|
|
37
|
+
case 'succeeded':
|
|
38
|
+
return 'default'
|
|
39
|
+
case 'failed':
|
|
40
|
+
return 'destructive'
|
|
41
|
+
case 'refunded':
|
|
42
|
+
return 'secondary'
|
|
43
|
+
default:
|
|
44
|
+
return 'outline'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function RecentPayments({ payments }: RecentPaymentsProps) {
|
|
49
|
+
const columns: DataTableColumn<Payment>[] = [
|
|
50
|
+
{
|
|
51
|
+
type: 'custom',
|
|
52
|
+
header: 'Amount',
|
|
53
|
+
cell: (row) => (
|
|
54
|
+
<span className="font-medium">
|
|
55
|
+
{formatCurrency(row.original.amount, row.original.currency)}
|
|
56
|
+
</span>
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'custom',
|
|
61
|
+
header: 'Status',
|
|
62
|
+
cell: (row) => (
|
|
63
|
+
<Badge variant={getStatusVariant(row.original.status)}>
|
|
64
|
+
{row.original.status}
|
|
65
|
+
</Badge>
|
|
66
|
+
),
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: 'text',
|
|
70
|
+
header: 'Customer',
|
|
71
|
+
accessorKey: 'customerEmail',
|
|
72
|
+
format: (value) => (
|
|
73
|
+
<span className="text-muted-foreground">{value as string}</span>
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'text',
|
|
78
|
+
header: 'Date',
|
|
79
|
+
accessorKey: 'createdAt',
|
|
80
|
+
format: (value) => (
|
|
81
|
+
<span className="text-muted-foreground">
|
|
82
|
+
{new Date(value as string).toLocaleDateString()}
|
|
83
|
+
</span>
|
|
84
|
+
),
|
|
85
|
+
},
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<>
|
|
90
|
+
<style>{contentManagerStyles}</style>
|
|
91
|
+
<DataTable
|
|
92
|
+
data={payments}
|
|
93
|
+
columns={columns}
|
|
94
|
+
getRowId={(row) => row.id}
|
|
95
|
+
enablePagination={false}
|
|
96
|
+
showCount={false}
|
|
97
|
+
variant="content-manager"
|
|
98
|
+
renderEmpty={() => (
|
|
99
|
+
<div className="p-6 text-center">
|
|
100
|
+
<p className="text-sm text-muted-foreground">No payments yet.</p>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
/>
|
|
104
|
+
</>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChartContainer,
|
|
3
|
+
ChartTooltip,
|
|
4
|
+
ChartTooltipContent,
|
|
5
|
+
} from '@magnet-cms/ui/components'
|
|
6
|
+
import type { ChartConfig } from '@magnet-cms/ui/components'
|
|
7
|
+
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts'
|
|
8
|
+
|
|
9
|
+
interface RevenueChartProps {
|
|
10
|
+
data: Array<{ month: string; revenue: number }>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const chartConfig: ChartConfig = {
|
|
14
|
+
revenue: {
|
|
15
|
+
label: 'Revenue',
|
|
16
|
+
color: 'hsl(var(--chart-1))',
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatCurrency(cents: number): string {
|
|
21
|
+
return `$${(cents / 100).toFixed(0)}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function RevenueChart({ data }: RevenueChartProps) {
|
|
25
|
+
return (
|
|
26
|
+
<ChartContainer config={chartConfig} className="h-[300px] w-full">
|
|
27
|
+
<BarChart data={data}>
|
|
28
|
+
<CartesianGrid strokeDasharray="3 3" vertical={false} />
|
|
29
|
+
<XAxis
|
|
30
|
+
dataKey="month"
|
|
31
|
+
tickLine={false}
|
|
32
|
+
axisLine={false}
|
|
33
|
+
fontSize={12}
|
|
34
|
+
/>
|
|
35
|
+
<YAxis
|
|
36
|
+
tickLine={false}
|
|
37
|
+
axisLine={false}
|
|
38
|
+
fontSize={12}
|
|
39
|
+
tickFormatter={formatCurrency}
|
|
40
|
+
/>
|
|
41
|
+
<ChartTooltip
|
|
42
|
+
content={
|
|
43
|
+
<ChartTooltipContent
|
|
44
|
+
formatter={(value) => formatCurrency(Number(value))}
|
|
45
|
+
/>
|
|
46
|
+
}
|
|
47
|
+
/>
|
|
48
|
+
<Bar
|
|
49
|
+
dataKey="revenue"
|
|
50
|
+
fill="var(--color-revenue)"
|
|
51
|
+
radius={[4, 4, 0, 0]}
|
|
52
|
+
/>
|
|
53
|
+
</BarChart>
|
|
54
|
+
</ChartContainer>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Card } from '@magnet-cms/ui/components'
|
|
2
|
+
import { CreditCard, DollarSign, TrendingDown, Users } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
interface MetricCardProps {
|
|
5
|
+
title: string
|
|
6
|
+
value: string
|
|
7
|
+
icon: React.ReactNode
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function MetricCard({ title, value, icon }: MetricCardProps) {
|
|
11
|
+
return (
|
|
12
|
+
<Card className="p-6">
|
|
13
|
+
<div className="flex items-center justify-between">
|
|
14
|
+
<div>
|
|
15
|
+
<p className="text-sm font-medium text-muted-foreground">{title}</p>
|
|
16
|
+
<p className="text-2xl font-bold mt-1">{value}</p>
|
|
17
|
+
</div>
|
|
18
|
+
<div className="text-muted-foreground">{icon}</div>
|
|
19
|
+
</div>
|
|
20
|
+
</Card>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface SubscriptionMetricsProps {
|
|
25
|
+
mrr: number
|
|
26
|
+
revenueThisMonth: number
|
|
27
|
+
activeSubscriptions: number
|
|
28
|
+
churnRate: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatCurrency(cents: number): string {
|
|
32
|
+
return `$${(cents / 100).toLocaleString('en-US', { minimumFractionDigits: 2 })}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function SubscriptionMetrics({
|
|
36
|
+
mrr,
|
|
37
|
+
revenueThisMonth,
|
|
38
|
+
activeSubscriptions,
|
|
39
|
+
churnRate,
|
|
40
|
+
}: SubscriptionMetricsProps) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
43
|
+
<MetricCard
|
|
44
|
+
title="Monthly Recurring Revenue"
|
|
45
|
+
value={formatCurrency(mrr)}
|
|
46
|
+
icon={<DollarSign className="h-5 w-5" />}
|
|
47
|
+
/>
|
|
48
|
+
<MetricCard
|
|
49
|
+
title="Revenue This Month"
|
|
50
|
+
value={formatCurrency(revenueThisMonth)}
|
|
51
|
+
icon={<CreditCard className="h-5 w-5" />}
|
|
52
|
+
/>
|
|
53
|
+
<MetricCard
|
|
54
|
+
title="Active Subscriptions"
|
|
55
|
+
value={String(activeSubscriptions)}
|
|
56
|
+
icon={<Users className="h-5 w-5" />}
|
|
57
|
+
/>
|
|
58
|
+
<MetricCard
|
|
59
|
+
title="Churn Rate"
|
|
60
|
+
value={`${churnRate}%`}
|
|
61
|
+
icon={<TrendingDown className="h-5 w-5" />}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|