@stevederico/skateboard-ui 0.9.3 → 0.9.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.
- package/CHANGELOG.md +6 -0
- package/PaymentView.jsx +92 -0
- package/Utilities.js +68 -38
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/PaymentView.jsx
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { useNavigate, useSearchParams } from 'react-router-dom';
|
|
3
|
+
import { getState } from '@/context.jsx';
|
|
4
|
+
import { getCurrentUser } from './Utilities.js'
|
|
5
|
+
import constants from "@/constants.json";
|
|
6
|
+
|
|
7
|
+
export default function PaymentView() {
|
|
8
|
+
const navigate = useNavigate();
|
|
9
|
+
const { state, dispatch } = getState();
|
|
10
|
+
const [searchParams] = useSearchParams(); // Hook to access query params
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
// Get app-specific localStorage keys
|
|
14
|
+
const appName = constants.appName || 'skateboard';
|
|
15
|
+
const getAppKey = (suffix) => `${appName.toLowerCase().replace(/\s+/g, '-')}_${suffix}`;
|
|
16
|
+
|
|
17
|
+
// Get query parameters
|
|
18
|
+
const success = searchParams.get('success') === 'true';
|
|
19
|
+
const canceled = searchParams.get('canceled') === 'true';
|
|
20
|
+
const portal = searchParams.get('portal') === 'return';
|
|
21
|
+
|
|
22
|
+
// Default redirect path
|
|
23
|
+
let redirectPath = '/app/home'; // Fallback if no URL is stored
|
|
24
|
+
|
|
25
|
+
async function getUser() {
|
|
26
|
+
let data = await getCurrentUser();
|
|
27
|
+
dispatch({ type: 'SET_USER', payload: data });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Handle different cases
|
|
31
|
+
switch (true) {
|
|
32
|
+
case success:
|
|
33
|
+
console.log("Checkout was successful!");
|
|
34
|
+
redirectPath = localStorage.getItem(getAppKey('beforeCheckoutURL')) || redirectPath;
|
|
35
|
+
getUser();
|
|
36
|
+
break;
|
|
37
|
+
case canceled:
|
|
38
|
+
console.log("Checkout was canceled!");
|
|
39
|
+
redirectPath = localStorage.getItem(getAppKey('beforeCheckoutURL')) || redirectPath;
|
|
40
|
+
break;
|
|
41
|
+
case portal:
|
|
42
|
+
console.log("Returned from billing portal!");
|
|
43
|
+
redirectPath = localStorage.getItem(getAppKey('beforeManageURL')) || redirectPath;
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
console.log("No specific query param detected, using default redirect.");
|
|
47
|
+
redirectPath = localStorage.getItem(getAppKey('beforeCheckoutURL')) || redirectPath;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Normalize redirectPath: Strip any full URL to just the pathname
|
|
52
|
+
if (redirectPath.startsWith('http://') || redirectPath.startsWith('https://')) {
|
|
53
|
+
try {
|
|
54
|
+
const url = new URL(redirectPath);
|
|
55
|
+
redirectPath = url.pathname; // Extract just the path (e.g., '/app/settings')
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error('Invalid URL in redirectPath:', redirectPath);
|
|
58
|
+
redirectPath = '/app/home'; // Fallback to default if parsing fails
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Ensure it starts with a '/'
|
|
63
|
+
if (!redirectPath.startsWith('/')) {
|
|
64
|
+
redirectPath = `/${redirectPath}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Debug the redirectPath
|
|
68
|
+
console.log('Redirecting to path:', redirectPath);
|
|
69
|
+
|
|
70
|
+
// Clear the stored URLs
|
|
71
|
+
localStorage.removeItem(getAppKey('beforeCheckoutURL'));
|
|
72
|
+
localStorage.removeItem(getAppKey('beforeManageURL'));
|
|
73
|
+
|
|
74
|
+
// Redirect after a delay
|
|
75
|
+
const timeoutId = setTimeout(() => {
|
|
76
|
+
navigate(redirectPath, { replace: true });
|
|
77
|
+
}, 1500);
|
|
78
|
+
|
|
79
|
+
// Cleanup timeout
|
|
80
|
+
return () => clearTimeout(timeoutId);
|
|
81
|
+
}, [navigate, searchParams]);
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="flex items-center justify-center h-screen">
|
|
89
|
+
<p className="text-lg font-medium"> Redirecting...</p>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
package/Utilities.js
CHANGED
|
@@ -106,7 +106,7 @@ export async function logEvent(event) {
|
|
|
106
106
|
export async function showManage(stripeID) {
|
|
107
107
|
try {
|
|
108
108
|
const csrfToken = getCSRFToken();
|
|
109
|
-
const uri = `${getBackendURL()}/
|
|
109
|
+
const uri = `${getBackendURL()}/portal`;
|
|
110
110
|
const response = await fetch(uri, {
|
|
111
111
|
method: "POST",
|
|
112
112
|
credentials: 'include',
|
|
@@ -119,7 +119,7 @@ export async function showManage(stripeID) {
|
|
|
119
119
|
|
|
120
120
|
if (response.ok) {
|
|
121
121
|
const data = await response.json();
|
|
122
|
-
console.log("/
|
|
122
|
+
console.log("/portal response: ", data);
|
|
123
123
|
if (data.url) {
|
|
124
124
|
|
|
125
125
|
localStorage.setItem(getAppKey("beforeManageURL"), window.location.href);
|
|
@@ -129,7 +129,7 @@ export async function showManage(stripeID) {
|
|
|
129
129
|
console.error("No URL returned from server");
|
|
130
130
|
}
|
|
131
131
|
} else {
|
|
132
|
-
console.error("Error with /
|
|
132
|
+
console.error("Error with /portal. Status:", response.status);
|
|
133
133
|
}
|
|
134
134
|
} catch (error) {
|
|
135
135
|
console.error("Request failed:", error);
|
|
@@ -145,7 +145,7 @@ export async function showCheckout(email, productIndex = 0) {
|
|
|
145
145
|
email: email
|
|
146
146
|
};
|
|
147
147
|
|
|
148
|
-
const uri = `${getBackendURL()}/
|
|
148
|
+
const uri = `${getBackendURL()}/checkout`;
|
|
149
149
|
const response = await fetch(uri, {
|
|
150
150
|
method: "POST",
|
|
151
151
|
credentials: 'include',
|
|
@@ -161,7 +161,7 @@ export async function showCheckout(email, productIndex = 0) {
|
|
|
161
161
|
if (data.url) {
|
|
162
162
|
// Save the current URL in localStorage before redirecting
|
|
163
163
|
localStorage.setItem(getAppKey("beforeCheckoutURL"), window.location.href);
|
|
164
|
-
// Redirect to
|
|
164
|
+
// Redirect to payment checkout
|
|
165
165
|
window.location.href = data.url;
|
|
166
166
|
return true;
|
|
167
167
|
} else {
|
|
@@ -169,7 +169,7 @@ export async function showCheckout(email, productIndex = 0) {
|
|
|
169
169
|
return false;
|
|
170
170
|
}
|
|
171
171
|
} else {
|
|
172
|
-
console.error("Error with /
|
|
172
|
+
console.error("Error with /checkout. Status:", response.status);
|
|
173
173
|
return false;
|
|
174
174
|
}
|
|
175
175
|
} catch (error) {
|
|
@@ -186,55 +186,85 @@ const FREE_LIMITS = {
|
|
|
186
186
|
|
|
187
187
|
export async function getRemainingUsage(action) {
|
|
188
188
|
if (constants.noLogin === true) {
|
|
189
|
-
return { remaining: -1, total: -1,
|
|
189
|
+
return { remaining: -1, total: -1, isSubscriber: true };
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
try {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
const csrfToken = getCSRFToken();
|
|
194
|
+
const response = await fetch(`${getBackendURL()}/usage`, {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
credentials: 'include',
|
|
197
|
+
headers: {
|
|
198
|
+
'Content-Type': 'application/json',
|
|
199
|
+
...(csrfToken && { 'X-CSRF-Token': csrfToken })
|
|
200
|
+
},
|
|
201
|
+
body: JSON.stringify({ operation: 'check' })
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
197
206
|
}
|
|
198
207
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const usageKey = `${appName.toLowerCase().replace(/\s+/g, '-')}_usage`;
|
|
202
|
-
const usage = JSON.parse(localStorage.getItem(usageKey) || '{}');
|
|
203
|
-
|
|
204
|
-
const limit = FREE_LIMITS[action] || 0;
|
|
205
|
-
const used = usage[action] || 0;
|
|
206
|
-
const remaining = Math.max(0, limit - used);
|
|
207
|
-
|
|
208
|
-
return {
|
|
209
|
-
remaining,
|
|
210
|
-
total: limit,
|
|
211
|
-
unlimited: false,
|
|
212
|
-
used
|
|
213
|
-
};
|
|
208
|
+
const data = await response.json();
|
|
209
|
+
return data;
|
|
214
210
|
} catch (error) {
|
|
215
211
|
console.error('Error checking usage:', error);
|
|
216
|
-
return { remaining: 0, total: 0,
|
|
212
|
+
return { remaining: 0, total: 0, isSubscriber: false };
|
|
217
213
|
}
|
|
218
214
|
}
|
|
219
215
|
|
|
220
|
-
export function trackUsage(action) {
|
|
221
|
-
if (constants.noLogin === true)
|
|
216
|
+
export async function trackUsage(action) {
|
|
217
|
+
if (constants.noLogin === true) {
|
|
218
|
+
return { remaining: -1, total: -1, isSubscriber: true };
|
|
219
|
+
}
|
|
222
220
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
try {
|
|
222
|
+
const csrfToken = getCSRFToken();
|
|
223
|
+
const response = await fetch(`${getBackendURL()}/usage`, {
|
|
224
|
+
method: 'POST',
|
|
225
|
+
credentials: 'include',
|
|
226
|
+
headers: {
|
|
227
|
+
'Content-Type': 'application/json',
|
|
228
|
+
...(csrfToken && { 'X-CSRF-Token': csrfToken })
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify({ operation: 'track' })
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (!response.ok) {
|
|
234
|
+
const data = await response.json();
|
|
235
|
+
if (response.status === 429) {
|
|
236
|
+
// Usage limit reached
|
|
237
|
+
console.warn('Usage limit reached:', data.error);
|
|
238
|
+
return data; // Return the usage data even when limit reached
|
|
239
|
+
}
|
|
240
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const data = await response.json();
|
|
244
|
+
return data; // Return full usage data
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('Error tracking usage:', error);
|
|
247
|
+
return { remaining: 0, total: 0, isSubscriber: false };
|
|
248
|
+
}
|
|
229
249
|
}
|
|
230
250
|
|
|
231
251
|
export async function showUpgradeSheet(upgradeSheetRef) {
|
|
232
|
-
// Check
|
|
233
|
-
const
|
|
252
|
+
// Check subscription from user data in localStorage instead of API call
|
|
253
|
+
const appName = constants.appName || 'skateboard';
|
|
254
|
+
const storageKey = `${appName.toLowerCase().replace(/\s+/g, '-')}_user`;
|
|
255
|
+
const storedUser = localStorage.getItem(storageKey);
|
|
256
|
+
|
|
257
|
+
let subscriber = false;
|
|
258
|
+
if (storedUser && storedUser !== "undefined") {
|
|
259
|
+
const user = JSON.parse(storedUser);
|
|
260
|
+
subscriber = user?.subscription?.status === 'active' &&
|
|
261
|
+
(!user?.subscription?.expires || user?.subscription?.expires > Math.floor(Date.now() / 1000));
|
|
262
|
+
}
|
|
263
|
+
|
|
234
264
|
if (subscriber) {
|
|
235
265
|
return; // Don't show upgrade sheet for subscribers
|
|
236
266
|
}
|
|
237
|
-
|
|
267
|
+
|
|
238
268
|
// Show the upgrade sheet using the ref
|
|
239
269
|
if (upgradeSheetRef?.current) {
|
|
240
270
|
upgradeSheetRef.current.show();
|