@stevederico/skateboard-ui 2.21.0 → 2.23.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/CHANGELOG.md +14 -0
- package/core/Utilities.js +13 -20
- package/index.js +1 -0
- package/layout/Layout.jsx +1 -1
- package/package.json +7 -7
- package/views/PaymentView.jsx +10 -10
- package/views/SignInView.jsx +1 -1
- package/views/SignUpView.jsx +3 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
2.23.0
|
|
4
|
+
|
|
5
|
+
Rename auth view functions
|
|
6
|
+
Harden PaymentView redirect check
|
|
7
|
+
Replace UNSAFE_NavigationContext usage
|
|
8
|
+
Consolidate app-key derivation
|
|
9
|
+
Export createSkateboardApp from root
|
|
10
|
+
Bump lucide-react to 1.x
|
|
11
|
+
Bump dependencies
|
|
12
|
+
|
|
13
|
+
2.22.0
|
|
14
|
+
|
|
15
|
+
Fix Layout sidebar override
|
|
16
|
+
|
|
3
17
|
2.21.0
|
|
4
18
|
|
|
5
19
|
Reduce sidebar width to 12rem
|
package/core/Utilities.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useEffect, useState
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useInRouterContext, useNavigate } from 'react-router-dom';
|
|
3
3
|
import { getDispatch } from './Context.jsx';
|
|
4
4
|
|
|
5
5
|
// Constants will be initialized by the app shell
|
|
@@ -131,8 +131,7 @@ export function getCookie(name) {
|
|
|
131
131
|
// For token cookies, use app-specific name
|
|
132
132
|
let cookieName = name;
|
|
133
133
|
if (name === 'token') {
|
|
134
|
-
|
|
135
|
-
cookieName = `${appName.toLowerCase().replace(/\s+/g, '-')}_token`;
|
|
134
|
+
cookieName = getAppKey('token');
|
|
136
135
|
}
|
|
137
136
|
|
|
138
137
|
const value = `; ${document.cookie}`;
|
|
@@ -162,9 +161,7 @@ export function getCSRFToken() {
|
|
|
162
161
|
if (csrfCookie) return csrfCookie;
|
|
163
162
|
|
|
164
163
|
// Fallback to localStorage (for backwards compatibility)
|
|
165
|
-
|
|
166
|
-
const csrfKey = `${appName.toLowerCase().replace(/\s+/g, '-')}_csrf`;
|
|
167
|
-
return safeGetItem(csrfKey);
|
|
164
|
+
return safeGetItem(getAppKey('csrf'));
|
|
168
165
|
}
|
|
169
166
|
|
|
170
167
|
/**
|
|
@@ -523,9 +520,7 @@ export async function trackUsage(action) {
|
|
|
523
520
|
*/
|
|
524
521
|
export async function showUpgradeSheet(upgradeSheetRef) {
|
|
525
522
|
// Check subscription from user data in localStorage instead of API call
|
|
526
|
-
const
|
|
527
|
-
const storageKey = `${appName.toLowerCase().replace(/\s+/g, '-')}_user`;
|
|
528
|
-
const storedUser = safeGetItem(storageKey);
|
|
523
|
+
const storedUser = safeGetItem(getAppKey('user'));
|
|
529
524
|
|
|
530
525
|
let subscriber = false;
|
|
531
526
|
if (storedUser && storedUser !== "undefined") {
|
|
@@ -1010,9 +1005,8 @@ export function setUIVisibility({ sidebar, tabBar }) {
|
|
|
1010
1005
|
/**
|
|
1011
1006
|
* Safe navigation hook that works with or without Router context.
|
|
1012
1007
|
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1015
|
-
* Falls back to window.location.href if no Router context is available.
|
|
1008
|
+
* Falls back to window.location.href if no Router context is available
|
|
1009
|
+
* (e.g., rendered in Portals or via module duplication).
|
|
1016
1010
|
*
|
|
1017
1011
|
* @returns {function} Navigate function (path, options?) => void
|
|
1018
1012
|
*
|
|
@@ -1022,17 +1016,16 @@ export function setUIVisibility({ sidebar, tabBar }) {
|
|
|
1022
1016
|
* navigate('/app', { replace: true });
|
|
1023
1017
|
*/
|
|
1024
1018
|
export function useSafeNavigate() {
|
|
1025
|
-
const
|
|
1019
|
+
const inRouter = useInRouterContext();
|
|
1020
|
+
// Router presence is stable for a given component instance, so the
|
|
1021
|
+
// conditional useNavigate call respects the rules of hooks at runtime.
|
|
1022
|
+
const navigate = inRouter ? useNavigate() : null;
|
|
1026
1023
|
|
|
1027
|
-
if (!
|
|
1024
|
+
if (!navigate) {
|
|
1028
1025
|
return (path) => { window.location.href = path; };
|
|
1029
1026
|
}
|
|
1030
1027
|
|
|
1031
1028
|
return (path, options = {}) => {
|
|
1032
|
-
|
|
1033
|
-
ctx.navigator.replace(path, options.state);
|
|
1034
|
-
} else {
|
|
1035
|
-
ctx.navigator.push(path, options.state);
|
|
1036
|
-
}
|
|
1029
|
+
navigate(path, { replace: !!options.replace, state: options.state });
|
|
1037
1030
|
};
|
|
1038
1031
|
}
|
package/index.js
CHANGED
package/layout/Layout.jsx
CHANGED
|
@@ -46,7 +46,7 @@ export default function Layout({ children }) {
|
|
|
46
46
|
<SidebarProvider
|
|
47
47
|
defaultOpen={!constants.sidebarCollapsed}
|
|
48
48
|
style={{
|
|
49
|
-
'--sidebar-width': '
|
|
49
|
+
'--sidebar-width': '12rem',
|
|
50
50
|
'--header-height': '3.5rem',
|
|
51
51
|
}}>
|
|
52
52
|
{showSidebar && <Sidebar variant="inset" />}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stevederico/skateboard-ui",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.23.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./Sidebar": {
|
|
@@ -122,19 +122,19 @@
|
|
|
122
122
|
"url": "git+https://github.com/stevederico/skateboard-ui.git"
|
|
123
123
|
},
|
|
124
124
|
"dependencies": {
|
|
125
|
-
"@base-ui/react": "^1.1
|
|
125
|
+
"@base-ui/react": "^1.4.1",
|
|
126
126
|
"use-sync-external-store": "^1.6.0",
|
|
127
127
|
"class-variance-authority": "^0.7.1",
|
|
128
128
|
"clsx": "^2.1.1",
|
|
129
129
|
"cmdk": "^1.1.1",
|
|
130
130
|
"embla-carousel-react": "^8.6.0",
|
|
131
|
-
"lucide-react": "^
|
|
131
|
+
"lucide-react": "^1.11.0",
|
|
132
132
|
"next-themes": "^0.4.6",
|
|
133
|
-
"react-day-picker": "^9.
|
|
134
|
-
"react-resizable-panels": "^4.
|
|
135
|
-
"recharts": "^3.
|
|
133
|
+
"react-day-picker": "^9.14.0",
|
|
134
|
+
"react-resizable-panels": "^4.10.0",
|
|
135
|
+
"recharts": "^3.8.1",
|
|
136
136
|
"sonner": "^2.0.7",
|
|
137
|
-
"tailwind-merge": "^3.
|
|
137
|
+
"tailwind-merge": "^3.5.0",
|
|
138
138
|
"tailwindcss-animate": "^1.0.7",
|
|
139
139
|
"vaul": "^1.1.2"
|
|
140
140
|
},
|
package/views/PaymentView.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useCallback } from 'react';
|
|
2
2
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
|
3
3
|
import { getState } from '../core/Context.jsx';
|
|
4
|
-
import { getCurrentUser } from '../core/Utilities.js'
|
|
4
|
+
import { getCurrentUser, getAppKey } from '../core/Utilities.js'
|
|
5
5
|
import { Spinner } from '../shadcn/ui/spinner.jsx';
|
|
6
6
|
|
|
7
7
|
// Whitelist of allowed redirect paths to prevent open redirect vulnerabilities
|
|
@@ -9,9 +9,14 @@ const ALLOWED_REDIRECT_PREFIXES = ['/app/', '/'];
|
|
|
9
9
|
const DEFAULT_REDIRECT = '/app/home';
|
|
10
10
|
|
|
11
11
|
function isAllowedRedirect(path) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
try {
|
|
13
|
+
// Resolve relative to current origin; reject anything that escapes it.
|
|
14
|
+
const url = new URL(path, window.location.origin);
|
|
15
|
+
if (url.origin !== window.location.origin) return false;
|
|
16
|
+
return ALLOWED_REDIRECT_PREFIXES.some(prefix => url.pathname.startsWith(prefix));
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
/**
|
|
@@ -28,8 +33,7 @@ function isAllowedRedirect(path) {
|
|
|
28
33
|
* <Route path="payment" element={<PaymentView />} />
|
|
29
34
|
*/
|
|
30
35
|
export default function PaymentView() {
|
|
31
|
-
const {
|
|
32
|
-
const constants = state.constants;
|
|
36
|
+
const { dispatch } = getState();
|
|
33
37
|
const navigate = useNavigate();
|
|
34
38
|
const [searchParams] = useSearchParams();
|
|
35
39
|
|
|
@@ -45,10 +49,6 @@ export default function PaymentView() {
|
|
|
45
49
|
}, [dispatch]);
|
|
46
50
|
|
|
47
51
|
useEffect(() => {
|
|
48
|
-
// Get app-specific localStorage keys
|
|
49
|
-
const appName = constants.appName || 'skateboard';
|
|
50
|
-
const getAppKey = (suffix) => `${appName.toLowerCase().replace(/\s+/g, '-')}_${suffix}`;
|
|
51
|
-
|
|
52
52
|
// Get query parameters
|
|
53
53
|
const success = searchParams.get('success') === 'true';
|
|
54
54
|
const canceled = searchParams.get('canceled') === 'true';
|
package/views/SignInView.jsx
CHANGED
|
@@ -30,7 +30,7 @@ import { getBackendURL, useSafeNavigate } from '../core/Utilities'
|
|
|
30
30
|
* // Embedded in dialog
|
|
31
31
|
* <SignInView embedded onSuccess={handleSuccess} onSwitchMode={() => setMode('signup')} />
|
|
32
32
|
*/
|
|
33
|
-
export default function
|
|
33
|
+
export default function SignInView({
|
|
34
34
|
className,
|
|
35
35
|
embedded = false,
|
|
36
36
|
onSuccess,
|
package/views/SignUpView.jsx
CHANGED
|
@@ -7,7 +7,7 @@ import { Card, CardContent, CardHeader } from "../shadcn/ui/card"
|
|
|
7
7
|
import { Alert, AlertDescription } from "../shadcn/ui/alert"
|
|
8
8
|
import DynamicIcon from '../core/DynamicIcon';
|
|
9
9
|
import { getState } from "../core/Context.jsx";
|
|
10
|
-
import { getBackendURL, useSafeNavigate } from '../core/Utilities'
|
|
10
|
+
import { getBackendURL, useSafeNavigate, getAppKey } from '../core/Utilities'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Sign-up form component.
|
|
@@ -31,7 +31,7 @@ import { getBackendURL, useSafeNavigate } from '../core/Utilities'
|
|
|
31
31
|
* // Embedded in dialog
|
|
32
32
|
* <SignUpView embedded onSuccess={handleSuccess} onSwitchMode={() => setMode('signin')} />
|
|
33
33
|
*/
|
|
34
|
-
export default function
|
|
34
|
+
export default function SignUpView({
|
|
35
35
|
className,
|
|
36
36
|
embedded = false,
|
|
37
37
|
onSuccess,
|
|
@@ -79,9 +79,7 @@ export default function LoginForm({
|
|
|
79
79
|
const csrfCookie = document.cookie.split('; ').find(row => row.startsWith('csrf_token='));
|
|
80
80
|
const csrfToken = csrfCookie ? csrfCookie.split('=')[1] : data.csrfToken;
|
|
81
81
|
if (csrfToken) {
|
|
82
|
-
|
|
83
|
-
const csrfKey = `${appName.toLowerCase().replace(/\s+/g, '-')}_csrf`;
|
|
84
|
-
localStorage.setItem(csrfKey, csrfToken);
|
|
82
|
+
localStorage.setItem(getAppKey('csrf'), csrfToken);
|
|
85
83
|
}
|
|
86
84
|
dispatch({ type: 'SET_USER', payload: data });
|
|
87
85
|
if (embedded && onSuccess) {
|