@stevederico/skateboard-ui 1.2.16 → 1.2.18

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,16 @@
1
1
  # CHANGELOG
2
+ 1.2.18
3
+
4
+ Add authentication documentation
5
+ Add JSDoc comments
6
+ Update README configuration
7
+ Create docs folder
8
+
9
+ 1.2.17
10
+
11
+ Fix SignIn layout positioning
12
+ Fix SignUp layout positioning
13
+
2
14
  1.2.14
3
15
 
4
16
  Fix isAuthenticated validation
package/Context.jsx CHANGED
@@ -5,6 +5,22 @@ const context = createContext();
5
5
  // Store dispatch reference for programmatic access outside components
6
6
  let _dispatch = null;
7
7
 
8
+ /**
9
+ * Get dispatch function for programmatic state updates outside components.
10
+ *
11
+ * Useful for updating state from non-React code (event handlers, utilities).
12
+ * Returns null if ContextProvider hasn't mounted yet.
13
+ *
14
+ * @returns {Function|null} Dispatch function or null
15
+ *
16
+ * @example
17
+ * import { getDispatch } from '@stevederico/skateboard-ui/Context';
18
+ *
19
+ * const dispatch = getDispatch();
20
+ * if (dispatch) {
21
+ * dispatch({ type: 'CLEAR_USER' });
22
+ * }
23
+ */
8
24
  export function getDispatch() {
9
25
  return _dispatch;
10
26
  }
@@ -58,6 +74,24 @@ function safeLSRemoveItem(key) {
58
74
  }
59
75
  }
60
76
 
77
+ /**
78
+ * Global state provider for skateboard-ui.
79
+ *
80
+ * Manages user authentication state and UI visibility (sidebar, tabbar).
81
+ * Persists user data to localStorage using app-specific keys.
82
+ *
83
+ * @param {Object} props
84
+ * @param {Object} props.constants - App configuration
85
+ * @param {string} props.constants.appName - Used for localStorage key namespacing
86
+ * @param {React.ReactNode} props.children - Child components
87
+ *
88
+ * @example
89
+ * import { ContextProvider } from '@stevederico/skateboard-ui/Context';
90
+ *
91
+ * <ContextProvider constants={constants}>
92
+ * <App />
93
+ * </ContextProvider>
94
+ */
61
95
  export function ContextProvider({ children, constants }) {
62
96
  const getStorageKey = () => {
63
97
  const appName = constants.appName || 'skateboard';
@@ -133,6 +167,24 @@ export function ContextProvider({ children, constants }) {
133
167
  );
134
168
  }
135
169
 
170
+ /**
171
+ * Hook to access skateboard-ui state.
172
+ *
173
+ * Returns { state, dispatch } where state contains:
174
+ * - user: Current user object or null
175
+ * - ui: { sidebarVisible, tabBarVisible }
176
+ *
177
+ * @returns {{ state: Object, dispatch: Function }}
178
+ *
179
+ * @example
180
+ * import { getState } from '@stevederico/skateboard-ui/Context';
181
+ *
182
+ * function MyComponent() {
183
+ * const { state, dispatch } = getState();
184
+ * console.log(state.user);
185
+ * dispatch({ type: 'SET_USER', payload: userData });
186
+ * }
187
+ */
136
188
  export function getState() {
137
189
  return useContext(context);
138
190
  }
package/README.md CHANGED
@@ -25,6 +25,92 @@ createSkateboardApp({ constants, appRoutes });
25
25
 
26
26
  That's it! You get routing, auth, layout, landing page, settings, and payments.
27
27
 
28
+ ## Configuration
29
+
30
+ skateboard-ui requires a `constants` object that configures your application:
31
+
32
+ ```javascript
33
+ // constants.json or constants.js
34
+ const constants = {
35
+ // Required: Backend URLs (include /api prefix)
36
+ devBackendURL: "http://localhost:8000/api",
37
+ backendURL: "https://api.myapp.com/api",
38
+
39
+ // Required: App identity
40
+ appName: "MyApp",
41
+ appIcon: "🛹",
42
+
43
+ // Required: Landing page content
44
+ tagline: "Build apps faster with skateboard-ui",
45
+ cta: "Get Started",
46
+
47
+ // Required: Features section
48
+ features: {
49
+ title: "Everything you need",
50
+ items: [
51
+ { icon: "Zap", title: "Fast", description: "Built for speed" },
52
+ { icon: "Shield", title: "Secure", description: "Authentication included" }
53
+ ]
54
+ },
55
+
56
+ // Required: Company information
57
+ companyName: "Your Company",
58
+ companyWebsite: "https://yourcompany.com",
59
+ companyEmail: "hello@yourcompany.com",
60
+
61
+ // Optional: Authentication
62
+ noLogin: false, // Set true to disable authentication
63
+
64
+ // Optional: Payments (Stripe)
65
+ stripeProducts: [
66
+ {
67
+ name: "Pro Plan",
68
+ priceId: "price_123",
69
+ price: "$10/month",
70
+ lookup_key: "pro_plan"
71
+ }
72
+ ],
73
+
74
+ // Optional: Legal documents
75
+ termsOfService: "Your terms of service...",
76
+ privacyPolicy: "Your privacy policy...",
77
+
78
+ // Optional: UI visibility
79
+ hideSidebar: false,
80
+ hideTabBar: false
81
+ }
82
+ ```
83
+
84
+ ### Backend URL Pattern
85
+
86
+ The `devBackendURL` and `backendURL` should include your full API base path (including the `/api` prefix):
87
+
88
+ ```javascript
89
+ const constants = {
90
+ devBackendURL: "http://localhost:8000/api", // Include /api prefix
91
+ backendURL: "https://api.myapp.com/api",
92
+ }
93
+ ```
94
+
95
+ Endpoints are relative to this base URL:
96
+ - `${getBackendURL()}/signup` → `http://localhost:8000/api/signup`
97
+ - `${getBackendURL()}/me` → `http://localhost:8000/api/me`
98
+ - `${getBackendURL()}/deals` → `http://localhost:8000/api/deals`
99
+
100
+ **Tip:** Include API versioning in the base URL (e.g., `/api/v2`) rather than in each endpoint path.
101
+
102
+ ### Authentication Setup
103
+
104
+ skateboard-ui uses a hybrid cookie + localStorage authentication system. Your backend must implement specific endpoints and cookie handling.
105
+
106
+ **See [AUTHENTICATION.md](./docs/AUTHENTICATION.md) for complete backend setup requirements.**
107
+
108
+ Quick overview:
109
+ - Session token stored in HttpOnly cookie for security
110
+ - CSRF token in localStorage for request validation
111
+ - Backend validates cookies on protected endpoints
112
+ - Client-side `isAuthenticated()` checks localStorage for fast validation
113
+
28
114
  ## Components
29
115
 
30
116
  ### Core Components
@@ -253,6 +339,10 @@ import ProtectedRoute from '@stevederico/skateboard-ui/ProtectedRoute';
253
339
 
254
340
  Used internally by createSkateboardApp. Redirects to /signin if not authenticated.
255
341
 
342
+ ## Documentation
343
+
344
+ - **[Authentication Guide](./docs/AUTHENTICATION.md)** - Complete guide to the hybrid cookie + localStorage authentication system, including backend requirements, security considerations, and Express.js implementation examples
345
+
256
346
  ## Dependencies
257
347
 
258
348
  - React 19.1+
package/SignInView.jsx CHANGED
@@ -73,7 +73,7 @@ export default function LoginForm({
73
73
  }
74
74
 
75
75
  return (
76
- <div className="min-h-screen bg-white dark:bg-black transition-colors duration-300">
76
+ <div className="fixed inset-0 bg-white dark:bg-black transition-colors duration-300 overflow-auto">
77
77
  <div className={cn("flex flex-col gap-6 p-4 max-w-lg mx-auto mt-20", className)} {...props}>
78
78
  <div className="flex flex-row items-center justify-center mb-4">
79
79
  <div className="bg-app dark:bg-app dark:border dark:border-gray-700 rounded-2xl flex aspect-square size-16 items-center justify-center">
package/SignUpView.jsx CHANGED
@@ -75,7 +75,7 @@ export default function LoginForm({
75
75
  }
76
76
 
77
77
  return (
78
- <div className="min-h-screen bg-white dark:bg-black transition-colors duration-300">
78
+ <div className="fixed inset-0 bg-white dark:bg-black transition-colors duration-300 overflow-auto">
79
79
  <div className={cn("flex flex-col gap-6 p-4 max-w-lg mx-auto mt-20", className)} {...props}>
80
80
  <div className="flex flex-row items-center justify-center mb-4">
81
81
  <div className="bg-app dark:bg-app dark:border dark:border-gray-700 rounded-2xl flex aspect-square size-16 items-center justify-center">
package/Utilities.js CHANGED
@@ -65,6 +65,27 @@ function safeRemoveItem(key) {
65
65
  }
66
66
  }
67
67
 
68
+ /**
69
+ * Initialize skateboard-ui utilities with app constants.
70
+ *
71
+ * MUST be called before any utility functions are used, including
72
+ * before React components render. Stores constants in both module-level
73
+ * variable and window.__SKATEBOARD_CONSTANTS__ to handle Vite module
74
+ * duplication issues.
75
+ *
76
+ * @param {Object} constants - App configuration object
77
+ * @param {string} constants.appName - Application name
78
+ * @param {string} constants.devBackendURL - Development API base URL (include /api prefix)
79
+ * @param {string} constants.backendURL - Production API base URL (include /api prefix)
80
+ * @throws {Error} If constants is null/undefined
81
+ *
82
+ * @example
83
+ * initializeUtilities({
84
+ * appName: "MyApp",
85
+ * devBackendURL: "http://localhost:8000/api",
86
+ * backendURL: "https://api.myapp.com/api"
87
+ * });
88
+ */
68
89
  export function initializeUtilities(constants) {
69
90
  if (!constants) {
70
91
  throw new Error('initializeUtilities called with null/undefined constants');
@@ -102,17 +123,66 @@ export function getCookie(name) {
102
123
  return null;
103
124
  }
104
125
 
126
+ /**
127
+ * Get CSRF token from localStorage.
128
+ *
129
+ * Returns the CSRF token saved during signin/signup. This token
130
+ * should be sent in the X-CSRF-Token header for state-changing requests.
131
+ *
132
+ * @returns {string|null} CSRF token or null if not found
133
+ *
134
+ * @example
135
+ * const csrfToken = getCSRFToken();
136
+ * fetch('/api/endpoint', {
137
+ * headers: { 'X-CSRF-Token': csrfToken }
138
+ * });
139
+ */
105
140
  export function getCSRFToken() {
106
141
  const appName = getConstants().appName || 'skateboard';
107
142
  const csrfKey = `${appName.toLowerCase().replace(/\s+/g, '-')}_csrf`;
108
143
  return safeGetItem(csrfKey);
109
144
  }
110
145
 
146
+ /**
147
+ * Generate app-specific localStorage key.
148
+ *
149
+ * Creates namespaced keys using app name: `{appName}_{suffix}`
150
+ * App name is normalized (lowercase, hyphens replace spaces)
151
+ *
152
+ * @param {string} suffix - Key suffix (e.g., 'csrf', 'user', 'theme')
153
+ * @returns {string} Namespaced key
154
+ *
155
+ * @example
156
+ * getAppKey('csrf') // "myapp_csrf"
157
+ * getAppKey('user') // "myapp_user"
158
+ * getAppKey('theme') // "myapp_theme"
159
+ */
111
160
  export function getAppKey(suffix) {
112
161
  const appName = getConstants().appName || 'skateboard';
113
162
  return `${appName.toLowerCase().replace(/\s+/g, '-')}_${suffix}`;
114
163
  }
115
164
 
165
+ /**
166
+ * Check if user is authenticated.
167
+ *
168
+ * Uses localStorage (NOT cookies) for fast client-side validation.
169
+ * Checks for both CSRF token and user data in localStorage.
170
+ * If constants.noLogin is true, always returns true.
171
+ *
172
+ * Note: This is a client-side check only. ProtectedRoute performs
173
+ * additional server-side validation via /me endpoint.
174
+ *
175
+ * @returns {boolean} True if authenticated or noLogin mode
176
+ *
177
+ * @see {@link ProtectedRoute} for server-side validation
178
+ *
179
+ * @example
180
+ * if (isAuthenticated()) {
181
+ * // Show authenticated UI
182
+ * } else {
183
+ * // Redirect to signin
184
+ * }
185
+ */
116
186
  export function isAuthenticated() {
117
187
  if (getConstants().noLogin === true) {
118
188
  return true;
@@ -122,6 +192,19 @@ export function isAuthenticated() {
122
192
  return Boolean(safeGetItem(csrfKey)) && Boolean(safeGetItem(userKey));
123
193
  }
124
194
 
195
+ /**
196
+ * Get the backend API base URL based on environment.
197
+ *
198
+ * Returns devBackendURL in development mode, backendURL in production.
199
+ * Endpoints should be concatenated: `${getBackendURL()}/endpoint`
200
+ *
201
+ * @returns {string} Base URL including /api prefix
202
+ *
203
+ * @example
204
+ * const url = `${getBackendURL()}/signup`;
205
+ * // Dev: "http://localhost:8000/api/signup"
206
+ * // Prod: "https://api.myapp.com/api/signup"
207
+ */
125
208
  export function getBackendURL() {
126
209
  let result = import.meta.env.DEV ? getConstants().devBackendURL : getConstants().backendURL;
127
210
  return result