mecha-pay 1.0.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/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # Mecha-Pay Pricing Table
2
+
3
+ A beautiful, responsive React component for displaying Mecha-Pay pricing plans. **TypeScript supported!**
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install mecha-pay
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Simply provide three required props: `apiKey`, `planId`, and `userId`. That's it!
14
+
15
+ ```jsx
16
+ import React from 'react';
17
+ import { PricingTable } from 'mecha-pay';
18
+
19
+ const App = () => {
20
+ const userId = "user_12345"; // Get from your auth system
21
+
22
+ return (
23
+ <PricingTable
24
+ apiKey="mp_live_your_api_key_here"
25
+ planId="0xefdc..."
26
+ userId={userId}
27
+ />
28
+ );
29
+ };
30
+
31
+ export default App;
32
+ ```
33
+
34
+ ### TypeScript
35
+
36
+ ```tsx
37
+ import React from 'react';
38
+ import { PricingTable } from 'mecha-pay';
39
+
40
+ const App: React.FC = () => {
41
+ const userId = "user_12345";
42
+
43
+ return (
44
+ <PricingTable
45
+ apiKey="mp_live_your_api_key_here"
46
+ planId="0xefdc..."
47
+ userId={userId}
48
+ />
49
+ );
50
+ };
51
+
52
+ export default App;
53
+ ```
54
+
55
+ ### With Error Handling
56
+
57
+ ```jsx
58
+ <PricingTable
59
+ apiKey="mp_live_your_api_key_here"
60
+ planId="0xefdc..."
61
+ userId={userId}
62
+ onError={(error) => {
63
+ console.error('Failed to load plan:', error);
64
+ // Handle error (e.g., show notification)
65
+ }}
66
+ />
67
+ ```
68
+
69
+ ### Next.js Example
70
+
71
+ ```jsx
72
+ import { PricingTable } from 'mecha-pay';
73
+ import { useUser } from '@/hooks/useAuth';
74
+
75
+ export default function PricingPage() {
76
+ const { userId } = useUser();
77
+
78
+ return (
79
+ <PricingTable
80
+ apiKey={process.env.NEXT_PUBLIC_MECHAPAY_API_KEY}
81
+ planId="0xefdc..."
82
+ userId={userId}
83
+ />
84
+ );
85
+ }
86
+ ```
87
+
88
+ ### TypeScript
89
+
90
+ ```tsx
91
+ import React from 'react';
92
+ import { PricingTable } from 'mecha-pay';
93
+
94
+ const App: React.FC = () => {
95
+ return (
96
+ <PricingTable
97
+ apiKey="mp_live_your_api_key_here"
98
+ onError={(error) => console.error(error)}
99
+ />
100
+ );
101
+ };
102
+ ```
103
+
104
+ ## How It Works
105
+
106
+ 1. **Provide 3 Required Props:**
107
+ - `apiKey` - Your Mecha-Pay API key
108
+ - `planId` - The plan ID to display
109
+ - `userId` - Current user's ID
110
+
111
+ 2. **Component Automatically:**
112
+ - Fetches plan details from `http://localhost:3000/api/v1/plans/{planId}`
113
+ - Displays pricing card with features
114
+ - Generates payment link: `http://localhost:3000/pay/{planId}?userId={userId}&successUrl={currentURL}`
115
+
116
+ 3. **User Clicks "Subscribe Now":**
117
+ - Redirected to: `http://localhost:3000/pay/0xefdc...?userId=user_12345&successUrl=https://yoursite.com`
118
+
119
+ ## API
120
+
121
+ ### Required Props
122
+
123
+ | Prop | Type | Description |
124
+ |------|------|-------------|
125
+ | apiKey | `string` | Your Mecha-Pay API key (e.g., `"mp_live_..."`) |
126
+ | planId | `string` | The plan ID to fetch (e.g., `"0xefdc..."`) |
127
+ | userId | `string` | User ID for payment link generation |
128
+
129
+ ### Optional Props
130
+
131
+ | Prop | Type | Description |
132
+ |------|------|-------------|
133
+ | onError | `(error: Error) => void` | Error callback function |
134
+
135
+ ## TypeScript Types
136
+
137
+ ```typescript
138
+ interface Feature {
139
+ title: string;
140
+ description?: string;
141
+ }
142
+
143
+ interface PlanMetadata {
144
+ name: string;
145
+ description: string;
146
+ features: Feature[];
147
+ }
148
+
149
+ interface Plan {
150
+ planId: string;
151
+ price: string;
152
+ duration: string;
153
+ metadata: PlanMetadata;
154
+ // activeSubscribers is returned by API but not used by component
155
+ }
156
+ ```
157
+
158
+ ## API Response Structure
159
+
160
+ The component fetches from `http://localhost:3000/api/v1/plans/{planId}` with response:
161
+
162
+ ```json
163
+ {
164
+ "planId": "0xefdc...",
165
+ "price": "5000000",
166
+ "duration": "2592000",
167
+ "metadata": {
168
+ "name": "Pro Merchant",
169
+ "description": "Premium subscription plan",
170
+ "features": [
171
+ { "title": "Analytics", "description": "Full dashboard access" }
172
+ ]
173
+ },
174
+ "activeSubscribers": [...] // Ignored by component
175
+ }
176
+ ```
177
+
178
+ ## Features
179
+
180
+ - 🎨 Beautiful gradient design
181
+ - 📱 Fully responsive
182
+ - ⚡ **Zero configuration** - just 3 props!
183
+ - 🔄 Automatic loading and error states
184
+ - 🔗 Auto-generated payment links to `localhost:3000`
185
+ - 💎 **TypeScript support** with full type definitions
186
+ - ⚙️ **Next.js compatible**
187
+ - ♿ Accessible
188
+ - 🎯 Easy to customize
189
+
190
+ ## Customization
191
+
192
+ The component uses CSS modules that can be overridden. Import your custom styles after the component:
193
+
194
+ ```jsx
195
+ import { PricingTable } from 'mecha-pay';
196
+ import './my-custom-styles.css';
197
+ ```
198
+
199
+ ## License
200
+
201
+ ISC
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "mecha-pay",
3
+ "version": "1.0.0",
4
+ "description": "A beautiful, responsive React pricing table component for Web3 applications",
5
+ "homepage": "https://github.com/Frank2006x/Mecha-pay#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/Frank2006x/Mecha-pay/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Frank2006x/Mecha-pay.git"
12
+ },
13
+ "license": "ISC",
14
+ "author": "",
15
+ "main": "src/index.js",
16
+ "module": "src/index.js",
17
+ "types": "src/index.d.ts",
18
+ "files": [
19
+ "src"
20
+ ],
21
+ "keywords": [
22
+ "react",
23
+ "pricing",
24
+ "pricing-table",
25
+ "web3",
26
+ "component",
27
+ "subscription",
28
+ "plans",
29
+ "typescript"
30
+ ],
31
+ "peerDependencies": {
32
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
33
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
34
+ },
35
+ "dependencies": {
36
+ "axios": "^1.6.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/react": "^18.0.0"
40
+ },
41
+ "scripts": {
42
+ "test": "echo \"Error: no test specified\" && exit 1"
43
+ }
44
+ }
@@ -0,0 +1,229 @@
1
+ .pricing-table-container {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ gap: 2rem;
5
+ padding: 2rem;
6
+ justify-content: center;
7
+ max-width: 1200px;
8
+ margin: 0 auto;
9
+ }
10
+
11
+ .pricing-card {
12
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
13
+ border-radius: 1rem;
14
+ padding: 2rem;
15
+ min-width: 280px;
16
+ max-width: 350px;
17
+ flex: 1;
18
+ display: flex;
19
+ flex-direction: column;
20
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
21
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
22
+ color: white;
23
+ }
24
+
25
+ .pricing-card:hover {
26
+ transform: translateY(-10px);
27
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
28
+ }
29
+
30
+ .pricing-header {
31
+ text-align: center;
32
+ margin-bottom: 2rem;
33
+ }
34
+
35
+ .pricing-title {
36
+ font-size: 1.75rem;
37
+ font-weight: 700;
38
+ margin: 0 0 0.5rem 0;
39
+ color: white;
40
+ }
41
+
42
+ .pricing-description {
43
+ font-size: 0.95rem;
44
+ opacity: 0.9;
45
+ margin: 0;
46
+ }
47
+
48
+ .pricing-price {
49
+ text-align: center;
50
+ margin-bottom: 2rem;
51
+ padding: 1.5rem 0;
52
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
53
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
54
+ }
55
+
56
+ .price-amount {
57
+ font-size: 3rem;
58
+ font-weight: 700;
59
+ display: block;
60
+ line-height: 1;
61
+ }
62
+
63
+ .price-duration {
64
+ font-size: 1rem;
65
+ opacity: 0.8;
66
+ margin-top: 0.5rem;
67
+ display: block;
68
+ }
69
+
70
+ .pricing-features {
71
+ flex-grow: 1;
72
+ margin-bottom: 2rem;
73
+ }
74
+
75
+ .features-title {
76
+ font-size: 1.1rem;
77
+ font-weight: 600;
78
+ margin: 0 0 1rem 0;
79
+ color: white;
80
+ }
81
+
82
+ .features-list {
83
+ list-style: none;
84
+ padding: 0;
85
+ margin: 0;
86
+ }
87
+
88
+ .feature-item {
89
+ display: flex;
90
+ align-items: flex-start;
91
+ gap: 0.75rem;
92
+ margin-bottom: 1rem;
93
+ padding: 0.75rem;
94
+ background: rgba(255, 255, 255, 0.1);
95
+ border-radius: 0.5rem;
96
+ transition: background 0.2s ease;
97
+ }
98
+
99
+ .feature-item:hover {
100
+ background: rgba(255, 255, 255, 0.15);
101
+ }
102
+
103
+ .feature-icon {
104
+ width: 1.25rem;
105
+ height: 1.25rem;
106
+ flex-shrink: 0;
107
+ margin-top: 0.1rem;
108
+ }
109
+
110
+ .feature-content {
111
+ flex: 1;
112
+ }
113
+
114
+ .feature-title {
115
+ display: block;
116
+ font-weight: 600;
117
+ font-size: 0.95rem;
118
+ margin-bottom: 0.25rem;
119
+ }
120
+
121
+ .feature-description {
122
+ font-size: 0.85rem;
123
+ opacity: 0.85;
124
+ margin: 0;
125
+ line-height: 1.4;
126
+ }
127
+
128
+ .pricing-button {
129
+ display: block;
130
+ width: 100%;
131
+ padding: 1rem;
132
+ font-size: 1.1rem;
133
+ font-weight: 600;
134
+ background: white;
135
+ color: #667eea;
136
+ border: none;
137
+ border-radius: 0.5rem;
138
+ cursor: pointer;
139
+ transition: all 0.3s ease;
140
+ margin-bottom: 1rem;
141
+ text-align: center;
142
+ text-decoration: none;
143
+ }
144
+
145
+ .pricing-button:hover {
146
+ background: #f7f7f7;
147
+ transform: scale(1.02);
148
+ }
149
+
150
+ .pricing-button:active {
151
+ transform: scale(0.98);
152
+ }
153
+
154
+ .pricing-button-disabled {
155
+ opacity: 0.5;
156
+ cursor: not-allowed;
157
+ }
158
+
159
+ .pricing-button-disabled:hover {
160
+ transform: none;
161
+ background: white;
162
+ }
163
+
164
+ /* Loading, Error, and Empty States */
165
+ .pricing-loading,
166
+ .pricing-error,
167
+ .pricing-empty {
168
+ text-align: center;
169
+ padding: 3rem 2rem;
170
+ font-size: 1.2rem;
171
+ color: #667eea;
172
+ width: 100%;
173
+ }
174
+
175
+ .pricing-error {
176
+ color: #e53e3e;
177
+ background: #fff5f5;
178
+ border-radius: 0.5rem;
179
+ padding: 2rem;
180
+ max-width: 500px;
181
+ margin: 0 auto;
182
+ }
183
+
184
+ .error-icon {
185
+ font-size: 2rem;
186
+ display: block;
187
+ margin-bottom: 1rem;
188
+ }
189
+
190
+ .error-message {
191
+ margin: 0;
192
+ font-size: 1rem;
193
+ color: #c53030;
194
+ }
195
+
196
+ .pricing-empty {
197
+ color: #718096;
198
+ font-style: italic;
199
+ }
200
+
201
+ .pricing-loading {
202
+ font-weight: 500;
203
+ animation: pulse 2s ease-in-out infinite;
204
+ }
205
+
206
+ @keyframes pulse {
207
+ 0%, 100% {
208
+ opacity: 1;
209
+ }
210
+ 50% {
211
+ opacity: 0.5;
212
+ }
213
+ }
214
+
215
+ /* Responsive Design */
216
+ @media (max-width: 768px) {
217
+ .pricing-table-container {
218
+ padding: 1rem;
219
+ gap: 1.5rem;
220
+ }
221
+
222
+ .pricing-card {
223
+ max-width: 100%;
224
+ }
225
+
226
+ .price-amount {
227
+ font-size: 2.5rem;
228
+ }
229
+ }
@@ -0,0 +1,32 @@
1
+ export interface Feature {
2
+ title: string;
3
+ description?: string;
4
+ }
5
+
6
+ export interface PlanMetadata {
7
+ name: string;
8
+ description: string;
9
+ features: Feature[];
10
+ }
11
+
12
+ export interface Plan {
13
+ planId: string;
14
+ price: string;
15
+ duration: string;
16
+ metadata: PlanMetadata;
17
+ }
18
+
19
+ export interface PricingTableProps {
20
+ /** Your Mecha-Pay API key (required) */
21
+ apiKey: string;
22
+ /** The plan ID to fetch and display (required) */
23
+ planId: string;
24
+ /** User ID for generating payment links (required) */
25
+ userId: string;
26
+ /** Optional error callback function */
27
+ onError?: (error: Error) => void;
28
+ }
29
+
30
+ declare const PricingTable: React.FC<PricingTableProps>;
31
+
32
+ export default PricingTable;
@@ -0,0 +1,137 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { getPlan } from './api';
3
+ import './PricingTable.css';
4
+
5
+ const BASE_URL = 'http://localhost:3000';
6
+
7
+ const PricingTable = ({ apiKey, planId, userId, onError }) => {
8
+ const [plan, setPlan] = useState(null);
9
+ const [loading, setLoading] = useState(true);
10
+ const [error, setError] = useState(null);
11
+
12
+ useEffect(() => {
13
+ const fetchPlanData = async () => {
14
+ try {
15
+ setLoading(true);
16
+ setError(null);
17
+
18
+ const data = await getPlan(apiKey, planId, BASE_URL);
19
+ setPlan(data);
20
+ setLoading(false);
21
+ } catch (err) {
22
+ setError(err.message);
23
+ setLoading(false);
24
+ if (onError) {
25
+ onError(err);
26
+ }
27
+ }
28
+ };
29
+
30
+ fetchPlanData();
31
+ }, [apiKey, planId, onError]);
32
+
33
+ const generatePaymentLink = () => {
34
+ const currentURL = typeof window !== 'undefined' ? window.location.origin : '';
35
+ const params = new URLSearchParams({
36
+ userId: userId,
37
+ successUrl: currentURL
38
+ });
39
+ return `${BASE_URL}/pay/${planId}?${params.toString()}`;
40
+ };
41
+
42
+ const formatPrice = (price) => {
43
+ // Convert from wei to a more readable format (assuming 18 decimals)
44
+ const formatted = (parseInt(price) / 1e18).toFixed(2);
45
+ return `$${formatted}`;
46
+ };
47
+
48
+ const formatDuration = (seconds) => {
49
+ const days = Math.floor(seconds / 86400);
50
+ if (days >= 30) {
51
+ const months = Math.floor(days / 30);
52
+ return `${months} ${months === 1 ? 'month' : 'months'}`;
53
+ }
54
+ return `${days} ${days === 1 ? 'day' : 'days'}`;
55
+ };
56
+
57
+ if (loading) {
58
+ return (
59
+ <div className="pricing-table-container">
60
+ <div className="pricing-loading">Loading plan...</div>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ if (error) {
66
+ return (
67
+ <div className="pricing-table-container">
68
+ <div className="pricing-error">
69
+ <span className="error-icon">⚠️</span>
70
+ <p className="error-message">{error}</p>
71
+ </div>
72
+ </div>
73
+ );
74
+ }
75
+
76
+ if (!plan) {
77
+ return (
78
+ <div className="pricing-table-container">
79
+ <div className="pricing-empty">No plan found</div>
80
+ </div>
81
+ );
82
+ }
83
+
84
+ return (
85
+ <div className="pricing-table-container">
86
+ <div className="pricing-card">
87
+ <div className="pricing-header">
88
+ <h2 className="pricing-title">{plan.metadata.name}</h2>
89
+ <p className="pricing-description">{plan.metadata.description}</p>
90
+ </div>
91
+
92
+ <div className="pricing-price">
93
+ <span className="price-amount">{formatPrice(plan.price)}</span>
94
+ <span className="price-duration">/{formatDuration(plan.duration)}</span>
95
+ </div>
96
+
97
+ <div className="pricing-features">
98
+ <h3 className="features-title">Features</h3>
99
+ <ul className="features-list">
100
+ {plan.metadata.features.map((feature, index) => (
101
+ <li key={index} className="feature-item">
102
+ <svg
103
+ className="feature-icon"
104
+ fill="none"
105
+ stroke="currentColor"
106
+ viewBox="0 0 24 24"
107
+ >
108
+ <path
109
+ strokeLinecap="round"
110
+ strokeLinejoin="round"
111
+ strokeWidth={2}
112
+ d="M5 13l4 4L19 7"
113
+ />
114
+ </svg>
115
+ <div className="feature-content">
116
+ <strong className="feature-title">{feature.title}</strong>
117
+ {feature.description && (
118
+ <p className="feature-description">{feature.description}</p>
119
+ )}
120
+ </div>
121
+ </li>
122
+ ))}
123
+ </ul>
124
+ </div>
125
+
126
+ <a
127
+ href={generatePaymentLink()}
128
+ className="pricing-button"
129
+ >
130
+ Subscribe Now
131
+ </a>
132
+ </div>
133
+ </div>
134
+ );
135
+ };
136
+
137
+ export default PricingTable;
package/src/api.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { AxiosError } from 'axios';
2
+ import { Plan } from './PricingTable';
3
+
4
+ export interface ApiError {
5
+ message: string;
6
+ status?: number;
7
+ }
8
+
9
+ /**
10
+ * Fetches a plan from the Mecha-Pay API
11
+ * @param apiKey - Your Mecha-Pay API key (e.g., "mp_live_...")
12
+ * @param planId - The plan ID to fetch (e.g., "0xefdc...")
13
+ * @param baseURL - Optional base URL (defaults to http://localhost:3000)
14
+ * @returns Promise resolving to the plan data
15
+ */
16
+ export declare function getPlan(
17
+ apiKey: string,
18
+ planId: string,
19
+ baseURL?: string
20
+ ): Promise<Plan>;
21
+
22
+ /**
23
+ * Fetches all plans from the Mecha-Pay API
24
+ * @param apiKey - Your Mecha-Pay API key (e.g., "mp_live_...")
25
+ * @param baseURL - Optional base URL (defaults to http://localhost:3000)
26
+ * @returns Promise resolving to array of plans
27
+ */
28
+ export declare function getPlans(
29
+ apiKey: string,
30
+ baseURL?: string
31
+ ): Promise<Plan[]>;
32
+
33
+ export default getPlan;
package/src/api.js ADDED
@@ -0,0 +1,61 @@
1
+ import axios from 'axios';
2
+
3
+ /**
4
+ * Fetches a plan from the Mecha-Pay API
5
+ * @param {string} apiKey - Your Mecha-Pay API key (e.g., "mp_live_...")
6
+ * @param {string} planId - The plan ID to fetch (e.g., "0xefdc...")
7
+ * @param {string} baseURL - Optional base URL (defaults to http://localhost:3000)
8
+ * @returns {Promise<Object>} The plan data
9
+ */
10
+ export const getPlan = async (apiKey, planId, baseURL = 'http://localhost:3000') => {
11
+ try {
12
+ const response = await axios.get(`${baseURL}/api/v1/plans/${planId}`, {
13
+ headers: {
14
+ 'x-api-key': apiKey,
15
+ 'Content-Type': 'application/json'
16
+ }
17
+ });
18
+
19
+ return response.data;
20
+ } catch (error) {
21
+ if (error.response) {
22
+ // Server responded with error status
23
+ throw new Error(`API Error: ${error.response.status} - ${error.response.data.message || error.response.statusText}`);
24
+ } else if (error.request) {
25
+ // Request made but no response received
26
+ throw new Error('Network Error: No response from server');
27
+ } else {
28
+ // Something else happened
29
+ throw new Error(`Request Error: ${error.message}`);
30
+ }
31
+ }
32
+ };
33
+
34
+ /**
35
+ * Fetches all plans from the Mecha-Pay API
36
+ * @param {string} apiKey - Your Mecha-Pay API key (e.g., "mp_live_...")
37
+ * @param {string} baseURL - Optional base URL (defaults to http://localhost:3000)
38
+ * @returns {Promise<Array>} Array of plans
39
+ */
40
+ export const getPlans = async (apiKey, baseURL = 'http://localhost:3000') => {
41
+ try {
42
+ const response = await axios.get(`${baseURL}/api/v1/plans`, {
43
+ headers: {
44
+ 'x-api-key': apiKey,
45
+ 'Content-Type': 'application/json'
46
+ }
47
+ });
48
+
49
+ return response.data;
50
+ } catch (error) {
51
+ if (error.response) {
52
+ throw new Error(`API Error: ${error.response.status} - ${error.response.data.message || error.response.statusText}`);
53
+ } else if (error.request) {
54
+ throw new Error('Network Error: No response from server');
55
+ } else {
56
+ throw new Error(`Request Error: ${error.message}`);
57
+ }
58
+ }
59
+ };
60
+
61
+ export default getPlan;
package/src/index.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { FC } from 'react';
2
+
3
+ export interface Feature {
4
+ title: string;
5
+ description?: string;
6
+ }
7
+
8
+ export interface PlanMetadata {
9
+ name: string;
10
+ description: string;
11
+ features: Feature[];
12
+ }
13
+
14
+ export interface Plan {
15
+ planId: string;
16
+ price: string;
17
+ duration: string;
18
+ metadata: PlanMetadata;
19
+ }
20
+
21
+ export interface PricingTableProps {
22
+ /** Your Mecha-Pay API key (required) */
23
+ apiKey: string;
24
+ /** The plan ID to fetch and display (required) */
25
+ planId: string;
26
+ /** User ID for generating payment links (required) */
27
+ userId: string;
28
+ /** Optional error callback function */
29
+ onError?: (error: Error) => void;
30
+ }
31
+
32
+ export const PricingTable: FC<PricingTableProps>;
33
+ export default PricingTable;
34
+
35
+ // API Functions
36
+ export function getPlan(apiKey: string, planId: string, baseURL?: string): Promise<Plan>;
37
+ export function getPlans(apiKey: string, baseURL?: string): Promise<Plan[]>;
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default as PricingTable } from './PricingTable.jsx';
2
+ export { default } from './PricingTable.jsx';
3
+ export { getPlan, getPlans } from './api.js';