@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 CHANGED
@@ -1,4 +1,10 @@
1
1
  # CHANGELOG
2
+ 0.9.4
3
+
4
+ Add PaymentView component
5
+ Update usage tracking
6
+ Refactor endpoint paths
7
+
2
8
  0.9.3
3
9
 
4
10
  Add input autofocus
@@ -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()}/create-portal-session`;
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("/create-portal-session response: ", data);
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 /create-portal-session. Status:", response.status);
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()}/create-checkout-session`;
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 Stripe Checkout
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 /create-checkout-session. Status:", response.status);
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, unlimited: true };
189
+ return { remaining: -1, total: -1, isSubscriber: true };
190
190
  }
191
191
 
192
192
  try {
193
- const subscriber = await isSubscriber();
194
-
195
- if (subscriber) {
196
- return { remaining: -1, total: -1, unlimited: true };
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
- // For free users, get current usage from localStorage
200
- const appName = constants.appName || 'skateboard';
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, unlimited: false };
212
+ return { remaining: 0, total: 0, isSubscriber: false };
217
213
  }
218
214
  }
219
215
 
220
- export function trackUsage(action) {
221
- if (constants.noLogin === true) return;
216
+ export async function trackUsage(action) {
217
+ if (constants.noLogin === true) {
218
+ return { remaining: -1, total: -1, isSubscriber: true };
219
+ }
222
220
 
223
- const appName = constants.appName || 'skateboard';
224
- const usageKey = `${appName.toLowerCase().replace(/\s+/g, '-')}_usage`;
225
- const usage = JSON.parse(localStorage.getItem(usageKey) || '{}');
226
-
227
- usage[action] = (usage[action] || 0) + 1;
228
- localStorage.setItem(usageKey, JSON.stringify(usage));
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 if user is already a subscriber
233
- const subscriber = await isSubscriber();
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();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stevederico/skateboard-ui",
3
3
  "private": false,
4
- "version": "0.9.3",
4
+ "version": "0.9.4",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  "./AppSidebar": {