@magnet-cms/plugin-polar 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 +2494 -0
- package/dist/backend/index.d.cts +435 -0
- package/dist/backend/index.d.ts +435 -0
- package/dist/backend/index.js +22 -0
- package/dist/chunk-7KT7JQIH.js +2474 -0
- package/dist/frontend/bundle.iife.js +23323 -0
- package/dist/frontend/bundle.iife.js.map +1 -0
- package/dist/index.cjs +2474 -0
- package/dist/index.d.cts +140 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +1 -0
- package/package.json +83 -0
- package/src/admin/components/recent-orders.tsx +80 -0
- package/src/admin/components/revenue-chart.tsx +56 -0
- package/src/admin/components/subscription-metrics.tsx +65 -0
- package/src/admin/index.ts +128 -0
- package/src/admin/pages/benefits.tsx +175 -0
- package/src/admin/pages/customers.tsx +187 -0
- package/src/admin/pages/orders.tsx +188 -0
- package/src/admin/pages/polar-dashboard.tsx +90 -0
- package/src/admin/pages/products.tsx +203 -0
- package/src/admin/pages/subscriptions.tsx +245 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { EnvVarRequirement, PluginMagnetProvider } from '@magnet-cms/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for the Polar plugin.
|
|
5
|
+
*
|
|
6
|
+
* Passed via `PluginModule.forRoot({ plugins: [{ plugin: PolarPlugin, options: { ... } }] })`
|
|
7
|
+
* and injected in services via `@InjectPluginOptions('polar')`.
|
|
8
|
+
*/
|
|
9
|
+
interface PolarPluginConfig {
|
|
10
|
+
/** Polar access token (polar_at_...). Auto-resolved from POLAR_ACCESS_TOKEN env var. */
|
|
11
|
+
accessToken?: string;
|
|
12
|
+
/** Polar webhook signing secret. Auto-resolved from POLAR_WEBHOOK_SECRET env var. */
|
|
13
|
+
webhookSecret?: string;
|
|
14
|
+
/** Polar organization ID. Auto-resolved from POLAR_ORGANIZATION_ID env var. */
|
|
15
|
+
organizationId?: string;
|
|
16
|
+
/** Whether to sync products from Polar via webhooks (default: true) */
|
|
17
|
+
syncProducts?: boolean;
|
|
18
|
+
/** Default currency for checkout sessions (default: 'usd') */
|
|
19
|
+
currency?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Feature flags per plan name.
|
|
22
|
+
* Map of product/plan name to list of feature identifiers.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* {
|
|
27
|
+
* 'pro': ['unlimited-servers', 'priority-support'],
|
|
28
|
+
* 'basic': ['5-servers'],
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
features?: Record<string, string[]>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Response from the subscription access endpoint.
|
|
36
|
+
*/
|
|
37
|
+
interface SubscriptionAccessResponse {
|
|
38
|
+
/** Whether the user has an active subscription */
|
|
39
|
+
hasActiveSubscription: boolean;
|
|
40
|
+
/** Plan/product name (null if no subscription) */
|
|
41
|
+
plan: string | null;
|
|
42
|
+
/** When the current period ends (null if no subscription) */
|
|
43
|
+
expiresAt: Date | null;
|
|
44
|
+
/** Feature flags for the user's plan */
|
|
45
|
+
features: string[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Request body for creating a checkout session.
|
|
49
|
+
*/
|
|
50
|
+
interface CreateCheckoutDto {
|
|
51
|
+
/** List of Polar Product IDs to offer at checkout */
|
|
52
|
+
products: string[];
|
|
53
|
+
/** URL to redirect to on success */
|
|
54
|
+
successUrl?: string;
|
|
55
|
+
/** URL to redirect to when customer clicks back */
|
|
56
|
+
returnUrl?: string;
|
|
57
|
+
/** Optional customer email to pre-fill */
|
|
58
|
+
customerEmail?: string;
|
|
59
|
+
/** Optional Magnet user ID to associate with the customer via externalCustomerId */
|
|
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
|
+
/** Polar 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 PolarMetricsResponse {
|
|
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 orders */
|
|
102
|
+
recentOrders: Array<{
|
|
103
|
+
id: string;
|
|
104
|
+
totalAmount: number;
|
|
105
|
+
currency: string;
|
|
106
|
+
status: string;
|
|
107
|
+
customerEmail: string;
|
|
108
|
+
createdAt: string;
|
|
109
|
+
}>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Polar Plugin
|
|
114
|
+
*
|
|
115
|
+
* Provides Polar.sh payment integration for Magnet CMS:
|
|
116
|
+
* - Product, 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
|
+
* - Benefit management (license keys, downloads, etc.)
|
|
122
|
+
*/
|
|
123
|
+
declare class PolarPlugin {
|
|
124
|
+
/** Environment variables used by this plugin */
|
|
125
|
+
static readonly envVars: EnvVarRequirement[];
|
|
126
|
+
/**
|
|
127
|
+
* Create a configured plugin provider for MagnetModule.forRoot().
|
|
128
|
+
* Auto-resolves secret values from environment variables if not provided.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* MagnetModule.forRoot([
|
|
133
|
+
* PolarPlugin.forRoot({ currency: 'usd' }),
|
|
134
|
+
* ])
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
static forRoot(config?: Partial<PolarPluginConfig>): PluginMagnetProvider;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { type CreateCheckoutDto, type CreatePortalDto, type PolarMetricsResponse, PolarPlugin, type PolarPluginConfig, type SessionResponse, type SubscriptionAccessResponse };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { EnvVarRequirement, PluginMagnetProvider } from '@magnet-cms/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for the Polar plugin.
|
|
5
|
+
*
|
|
6
|
+
* Passed via `PluginModule.forRoot({ plugins: [{ plugin: PolarPlugin, options: { ... } }] })`
|
|
7
|
+
* and injected in services via `@InjectPluginOptions('polar')`.
|
|
8
|
+
*/
|
|
9
|
+
interface PolarPluginConfig {
|
|
10
|
+
/** Polar access token (polar_at_...). Auto-resolved from POLAR_ACCESS_TOKEN env var. */
|
|
11
|
+
accessToken?: string;
|
|
12
|
+
/** Polar webhook signing secret. Auto-resolved from POLAR_WEBHOOK_SECRET env var. */
|
|
13
|
+
webhookSecret?: string;
|
|
14
|
+
/** Polar organization ID. Auto-resolved from POLAR_ORGANIZATION_ID env var. */
|
|
15
|
+
organizationId?: string;
|
|
16
|
+
/** Whether to sync products from Polar via webhooks (default: true) */
|
|
17
|
+
syncProducts?: boolean;
|
|
18
|
+
/** Default currency for checkout sessions (default: 'usd') */
|
|
19
|
+
currency?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Feature flags per plan name.
|
|
22
|
+
* Map of product/plan name to list of feature identifiers.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* {
|
|
27
|
+
* 'pro': ['unlimited-servers', 'priority-support'],
|
|
28
|
+
* 'basic': ['5-servers'],
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
features?: Record<string, string[]>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Response from the subscription access endpoint.
|
|
36
|
+
*/
|
|
37
|
+
interface SubscriptionAccessResponse {
|
|
38
|
+
/** Whether the user has an active subscription */
|
|
39
|
+
hasActiveSubscription: boolean;
|
|
40
|
+
/** Plan/product name (null if no subscription) */
|
|
41
|
+
plan: string | null;
|
|
42
|
+
/** When the current period ends (null if no subscription) */
|
|
43
|
+
expiresAt: Date | null;
|
|
44
|
+
/** Feature flags for the user's plan */
|
|
45
|
+
features: string[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Request body for creating a checkout session.
|
|
49
|
+
*/
|
|
50
|
+
interface CreateCheckoutDto {
|
|
51
|
+
/** List of Polar Product IDs to offer at checkout */
|
|
52
|
+
products: string[];
|
|
53
|
+
/** URL to redirect to on success */
|
|
54
|
+
successUrl?: string;
|
|
55
|
+
/** URL to redirect to when customer clicks back */
|
|
56
|
+
returnUrl?: string;
|
|
57
|
+
/** Optional customer email to pre-fill */
|
|
58
|
+
customerEmail?: string;
|
|
59
|
+
/** Optional Magnet user ID to associate with the customer via externalCustomerId */
|
|
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
|
+
/** Polar 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 PolarMetricsResponse {
|
|
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 orders */
|
|
102
|
+
recentOrders: Array<{
|
|
103
|
+
id: string;
|
|
104
|
+
totalAmount: number;
|
|
105
|
+
currency: string;
|
|
106
|
+
status: string;
|
|
107
|
+
customerEmail: string;
|
|
108
|
+
createdAt: string;
|
|
109
|
+
}>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Polar Plugin
|
|
114
|
+
*
|
|
115
|
+
* Provides Polar.sh payment integration for Magnet CMS:
|
|
116
|
+
* - Product, 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
|
+
* - Benefit management (license keys, downloads, etc.)
|
|
122
|
+
*/
|
|
123
|
+
declare class PolarPlugin {
|
|
124
|
+
/** Environment variables used by this plugin */
|
|
125
|
+
static readonly envVars: EnvVarRequirement[];
|
|
126
|
+
/**
|
|
127
|
+
* Create a configured plugin provider for MagnetModule.forRoot().
|
|
128
|
+
* Auto-resolves secret values from environment variables if not provided.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* MagnetModule.forRoot([
|
|
133
|
+
* PolarPlugin.forRoot({ currency: 'usd' }),
|
|
134
|
+
* ])
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
static forRoot(config?: Partial<PolarPluginConfig>): PluginMagnetProvider;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { type CreateCheckoutDto, type CreatePortalDto, type PolarMetricsResponse, PolarPlugin, type PolarPluginConfig, type SessionResponse, type SubscriptionAccessResponse };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PolarPlugin } from './chunk-7KT7JQIH.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@magnet-cms/plugin-polar",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Polar.sh 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
|
+
"@polar-sh/sdk": "0.46.4",
|
|
53
|
+
"@repo/biome": "workspace:*",
|
|
54
|
+
"@repo/tsup": "workspace:*",
|
|
55
|
+
"@repo/typescript-config": "workspace:*",
|
|
56
|
+
"@types/react": "^19.0.1",
|
|
57
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
58
|
+
"lucide-react": "^0.468.0",
|
|
59
|
+
"react": "^19.0.0",
|
|
60
|
+
"react-router-dom": "^7.1.1",
|
|
61
|
+
"recharts": "^2.15.0",
|
|
62
|
+
"reflect-metadata": "0.2.2",
|
|
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
|
+
"@polar-sh/sdk": "^0.46.0",
|
|
73
|
+
"lucide-react": ">=0.400.0",
|
|
74
|
+
"react": ">=18.0.0",
|
|
75
|
+
"react-router-dom": ">=6.0.0",
|
|
76
|
+
"reflect-metadata": "0.2.2"
|
|
77
|
+
},
|
|
78
|
+
"magnet": {
|
|
79
|
+
"type": "plugin",
|
|
80
|
+
"backend": true,
|
|
81
|
+
"frontend": true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Badge,
|
|
3
|
+
DataTable,
|
|
4
|
+
type DataTableColumn,
|
|
5
|
+
} from '@magnet-cms/ui/components'
|
|
6
|
+
|
|
7
|
+
interface RecentOrder {
|
|
8
|
+
id: string
|
|
9
|
+
totalAmount: number
|
|
10
|
+
currency: string
|
|
11
|
+
status: string
|
|
12
|
+
customerEmail: string
|
|
13
|
+
createdAt: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RecentOrdersProps {
|
|
17
|
+
orders: RecentOrder[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatCurrency(cents: number, currency: string): string {
|
|
21
|
+
return `${currency.toUpperCase()} $${(cents / 100).toFixed(2)}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getStatusVariant(
|
|
25
|
+
status: string,
|
|
26
|
+
): 'default' | 'secondary' | 'destructive' {
|
|
27
|
+
switch (status) {
|
|
28
|
+
case 'paid':
|
|
29
|
+
return 'default'
|
|
30
|
+
case 'refunded':
|
|
31
|
+
return 'destructive'
|
|
32
|
+
default:
|
|
33
|
+
return 'secondary'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const columns: DataTableColumn<RecentOrder>[] = [
|
|
38
|
+
{
|
|
39
|
+
type: 'text',
|
|
40
|
+
header: 'Amount',
|
|
41
|
+
accessorKey: 'totalAmount',
|
|
42
|
+
format: (_value, row) => formatCurrency(row.totalAmount, row.currency),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: 'custom',
|
|
46
|
+
header: 'Status',
|
|
47
|
+
cell: (row) => (
|
|
48
|
+
<Badge variant={getStatusVariant(row.original.status)}>
|
|
49
|
+
{row.original.status}
|
|
50
|
+
</Badge>
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: 'text',
|
|
55
|
+
header: 'Customer',
|
|
56
|
+
accessorKey: 'customerEmail',
|
|
57
|
+
format: (value) => (
|
|
58
|
+
<span className="font-mono text-sm">{value as string}</span>
|
|
59
|
+
),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'text',
|
|
63
|
+
header: 'Date',
|
|
64
|
+
accessorKey: 'createdAt',
|
|
65
|
+
format: (value) => new Date(value as string).toLocaleDateString(),
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
export function RecentOrders({ orders }: RecentOrdersProps) {
|
|
70
|
+
return (
|
|
71
|
+
<DataTable
|
|
72
|
+
data={orders}
|
|
73
|
+
columns={columns}
|
|
74
|
+
getRowId={(row) => row.id}
|
|
75
|
+
showCount={false}
|
|
76
|
+
className="border rounded-lg"
|
|
77
|
+
variant="content-manager"
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polar Plugin - Frontend Entry
|
|
3
|
+
*
|
|
4
|
+
* Self-registers the plugin when loaded via script injection.
|
|
5
|
+
* The admin app loads plugin bundles at runtime and plugins self-register
|
|
6
|
+
* on window.__MAGNET_PLUGINS__.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ComponentType } from 'react'
|
|
10
|
+
|
|
11
|
+
interface FrontendPluginManifest {
|
|
12
|
+
pluginName: string
|
|
13
|
+
routes?: {
|
|
14
|
+
path: string
|
|
15
|
+
componentId: string
|
|
16
|
+
children?: { path: string; componentId: string }[]
|
|
17
|
+
}[]
|
|
18
|
+
sidebar?: {
|
|
19
|
+
id: string
|
|
20
|
+
title: string
|
|
21
|
+
url: string
|
|
22
|
+
icon: string
|
|
23
|
+
order?: number
|
|
24
|
+
items?: { id: string; title: string; url: string; icon: string }[]
|
|
25
|
+
}[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface PluginRegistration {
|
|
29
|
+
manifest: FrontendPluginManifest
|
|
30
|
+
components: Record<string, () => Promise<{ default: ComponentType<unknown> }>>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const manifest: FrontendPluginManifest = {
|
|
34
|
+
pluginName: 'polar',
|
|
35
|
+
routes: [
|
|
36
|
+
{
|
|
37
|
+
path: 'polar',
|
|
38
|
+
componentId: 'PolarDashboard',
|
|
39
|
+
children: [
|
|
40
|
+
{ path: '', componentId: 'PolarDashboard' },
|
|
41
|
+
{ path: 'customers', componentId: 'PolarCustomers' },
|
|
42
|
+
{ path: 'products', componentId: 'PolarProducts' },
|
|
43
|
+
{ path: 'subscriptions', componentId: 'PolarSubscriptions' },
|
|
44
|
+
{ path: 'orders', componentId: 'PolarOrders' },
|
|
45
|
+
{ path: 'benefits', componentId: 'PolarBenefits' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
sidebar: [
|
|
50
|
+
{
|
|
51
|
+
id: 'polar',
|
|
52
|
+
title: 'Polar Payments',
|
|
53
|
+
url: '/polar',
|
|
54
|
+
icon: 'Zap',
|
|
55
|
+
order: 31,
|
|
56
|
+
items: [
|
|
57
|
+
{
|
|
58
|
+
id: 'polar-dashboard',
|
|
59
|
+
title: 'Dashboard',
|
|
60
|
+
url: '/polar',
|
|
61
|
+
icon: 'BarChart3',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'polar-customers',
|
|
65
|
+
title: 'Customers',
|
|
66
|
+
url: '/polar/customers',
|
|
67
|
+
icon: 'Users',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'polar-products',
|
|
71
|
+
title: 'Products',
|
|
72
|
+
url: '/polar/products',
|
|
73
|
+
icon: 'Package',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'polar-subscriptions',
|
|
77
|
+
title: 'Subscriptions',
|
|
78
|
+
url: '/polar/subscriptions',
|
|
79
|
+
icon: 'RefreshCw',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'polar-orders',
|
|
83
|
+
title: 'Orders',
|
|
84
|
+
url: '/polar/orders',
|
|
85
|
+
icon: 'Receipt',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'polar-benefits',
|
|
89
|
+
title: 'Benefits',
|
|
90
|
+
url: '/polar/benefits',
|
|
91
|
+
icon: 'Gift',
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const components: Record<
|
|
99
|
+
string,
|
|
100
|
+
() => Promise<{ default: ComponentType<unknown> }>
|
|
101
|
+
> = {
|
|
102
|
+
PolarDashboard: () => import('./pages/polar-dashboard'),
|
|
103
|
+
PolarCustomers: () => import('./pages/customers'),
|
|
104
|
+
PolarProducts: () => import('./pages/products'),
|
|
105
|
+
PolarSubscriptions: () => import('./pages/subscriptions'),
|
|
106
|
+
PolarOrders: () => import('./pages/orders'),
|
|
107
|
+
PolarBenefits: () => import('./pages/benefits'),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function registerPlugin() {
|
|
111
|
+
if (!window.__MAGNET_PLUGINS__) {
|
|
112
|
+
window.__MAGNET_PLUGINS__ = []
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const alreadyRegistered = window.__MAGNET_PLUGINS__.some(
|
|
116
|
+
(p) => p.manifest.pluginName === manifest.pluginName,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if (!alreadyRegistered) {
|
|
120
|
+
window.__MAGNET_PLUGINS__.push({ manifest, components })
|
|
121
|
+
console.log(`[Magnet] Plugin registered: ${manifest.pluginName}`)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
registerPlugin()
|
|
126
|
+
|
|
127
|
+
export const polarPlugin = () => ({ manifest, components })
|
|
128
|
+
export default polarPlugin
|