@moontra/moonui 2.2.7 → 2.3.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui",
3
- "version": "2.2.7",
3
+ "version": "2.3.1",
4
4
  "description": "Premium React component library with modern design and customization",
5
5
  "author": "MoonUI",
6
6
  "license": "MIT",
@@ -15,6 +15,8 @@
15
15
  "main": "dist/index.js",
16
16
  "module": "dist/index.mjs",
17
17
  "types": "dist/index.d.ts",
18
+ "unpkg": "dist/index.global.js",
19
+ "jsdelivr": "dist/index.global.js",
18
20
  "sideEffects": false,
19
21
  "files": [
20
22
  "dist/**",
@@ -3,11 +3,11 @@
3
3
  // Locked Component UI
4
4
  // Pro componentlerin locked state'ini gösteren wrapper component
5
5
 
6
- import React, { useState } from 'react'
6
+ import React, { useState, useEffect } from 'react'
7
7
  import { cn } from '../../lib/utils'
8
8
  import { Button } from './button'
9
9
  import { Badge } from './badge'
10
- import { Lock, Crown, Zap } from 'lucide-react'
10
+ import { Lock, Crown, Zap, AlertCircle } from 'lucide-react'
11
11
 
12
12
  interface LockedComponentProps {
13
13
  children: React.ReactNode
@@ -146,6 +146,7 @@ interface ProComponentWrapperProps {
146
146
  componentName?: string
147
147
  className?: string
148
148
  fallback?: React.ReactNode
149
+ requireCLI?: boolean
149
150
  }
150
151
 
151
152
  export function ProComponentWrapper({
@@ -153,16 +154,130 @@ export function ProComponentWrapper({
153
154
  componentId,
154
155
  componentName,
155
156
  className,
156
- fallback
157
+ fallback,
158
+ requireCLI = true
157
159
  }: ProComponentWrapperProps) {
158
- // For now, always show the component (no access control)
159
- // This can be updated later when proper access control is implemented
160
- const canUse = true
161
- const isLocked = false
162
- const isLoading = false
160
+ const [authState, setAuthState] = useState<{
161
+ isAuthenticated: boolean
162
+ deviceValid: boolean
163
+ hasProAccess: boolean
164
+ loading: boolean
165
+ }>({
166
+ isAuthenticated: false,
167
+ deviceValid: false,
168
+ hasProAccess: false,
169
+ loading: true
170
+ })
171
+
172
+ useEffect(() => {
173
+ // Enhanced environment detection - prevent manipulation
174
+ const isDevelopment = () => {
175
+ // Check multiple indicators to prevent spoofing
176
+ const checks = {
177
+ nodeEnv: process.env.NODE_ENV === 'development',
178
+ nextPublicEnv: process.env.NEXT_PUBLIC_VERCEL_ENV === 'development',
179
+ localhost: typeof window !== 'undefined' && (
180
+ window.location.hostname === 'localhost' ||
181
+ window.location.hostname === '127.0.0.1' ||
182
+ window.location.hostname.includes('.local')
183
+ ),
184
+ port: typeof window !== 'undefined' && (
185
+ window.location.port === '3000' ||
186
+ window.location.port === '3001' ||
187
+ window.location.port === '8080'
188
+ ),
189
+ vercelEnv: !process.env.VERCEL,
190
+ ciEnv: !process.env.CI,
191
+ deploymentEnv: !process.env.DEPLOYMENT_ENV
192
+ }
193
+
194
+ // If running on Vercel, Netlify, or any deployment platform - it's production
195
+ if (process.env.VERCEL || process.env.NETLIFY || process.env.RENDER ||
196
+ process.env.RAILWAY_ENVIRONMENT || process.env.FLY_APP_NAME ||
197
+ process.env.DEPLOYMENT_ENV === 'production') {
198
+ return false
199
+ }
200
+
201
+ // Must be localhost AND development env
202
+ return checks.nodeEnv && checks.localhost && !checks.vercelEnv && !checks.ciEnv
203
+ }
204
+
205
+ // Skip checks only in true production or if not requiring CLI
206
+ if (!isDevelopment() || !requireCLI) {
207
+ setAuthState({
208
+ isAuthenticated: true,
209
+ deviceValid: true,
210
+ hasProAccess: true,
211
+ loading: false
212
+ })
213
+ return
214
+ }
215
+
216
+ // Check CLI authentication in development
217
+ async function checkAuth() {
218
+ try {
219
+ const cliToken = localStorage.getItem('moonui_cli_token')
220
+ const deviceId = localStorage.getItem('moonui_device_id')
221
+
222
+ if (!cliToken || !deviceId) {
223
+ setAuthState({
224
+ isAuthenticated: false,
225
+ deviceValid: false,
226
+ hasProAccess: false,
227
+ loading: false
228
+ })
229
+ return
230
+ }
231
+
232
+ // Validate token and device with API
233
+ const response = await fetch('/api/device/validate', {
234
+ method: 'POST',
235
+ headers: {
236
+ 'Authorization': `Bearer ${cliToken}`,
237
+ 'X-Device-ID': deviceId,
238
+ 'Content-Type': 'application/json'
239
+ },
240
+ body: JSON.stringify({
241
+ environment: {
242
+ nodeEnv: process.env.NODE_ENV,
243
+ hostname: window.location.hostname,
244
+ port: window.location.port,
245
+ protocol: window.location.protocol
246
+ }
247
+ })
248
+ })
249
+
250
+ if (response.ok) {
251
+ const data = await response.json()
252
+ setAuthState({
253
+ isAuthenticated: true,
254
+ deviceValid: data.valid,
255
+ hasProAccess: data.hasProAccess,
256
+ loading: false
257
+ })
258
+ } else {
259
+ setAuthState({
260
+ isAuthenticated: false,
261
+ deviceValid: false,
262
+ hasProAccess: false,
263
+ loading: false
264
+ })
265
+ }
266
+ } catch (error) {
267
+ setAuthState({
268
+ isAuthenticated: false,
269
+ deviceValid: false,
270
+ hasProAccess: false,
271
+ loading: false
272
+ })
273
+ }
274
+ }
275
+
276
+ checkAuth()
277
+ }, [requireCLI])
163
278
 
164
279
  // Loading state
165
- if (isLoading) {
280
+ if (authState.loading) {
166
281
  return (
167
282
  <div className={cn("animate-pulse bg-gray-200 dark:bg-gray-800 rounded-lg h-32", className)}>
168
283
  <div className="flex items-center justify-center h-full">
@@ -172,24 +287,105 @@ export function ProComponentWrapper({
172
287
  )
173
288
  }
174
289
 
175
- // Unlocked - show actual component
176
- if (canUse) {
177
- return <div className={className}>{children}</div>
290
+ // Development mode checks - use same enhanced detection
291
+ const isDevelopment = () => {
292
+ const checks = {
293
+ nodeEnv: process.env.NODE_ENV === 'development',
294
+ localhost: typeof window !== 'undefined' && (
295
+ window.location.hostname === 'localhost' ||
296
+ window.location.hostname === '127.0.0.1' ||
297
+ window.location.hostname.includes('.local')
298
+ ),
299
+ port: typeof window !== 'undefined' && (
300
+ window.location.port === '3000' ||
301
+ window.location.port === '3001' ||
302
+ window.location.port === '8080'
303
+ )
304
+ }
305
+
306
+ if (process.env.VERCEL || process.env.NETLIFY || process.env.RENDER ||
307
+ process.env.RAILWAY_ENVIRONMENT || process.env.FLY_APP_NAME) {
308
+ return false
309
+ }
310
+
311
+ return checks.nodeEnv && checks.localhost
178
312
  }
313
+
314
+ if (isDevelopment() && requireCLI) {
315
+ // Not authenticated
316
+ if (!authState.isAuthenticated) {
317
+ return (
318
+ <div className={cn("relative", className)}>
319
+ <div className="blur-sm grayscale-[0.3] opacity-60 pointer-events-none">
320
+ {fallback || children}
321
+ </div>
322
+ <div className="absolute inset-0 bg-gradient-to-br from-red-500/10 via-transparent to-orange-500/10 border-2 border-dashed border-red-400/30 rounded-lg flex items-center justify-center">
323
+ <div className="text-center space-y-3 p-6">
324
+ <Badge variant="destructive" className="mb-2">
325
+ <Lock className="w-3 h-3 mr-1" />
326
+ CLI AUTH REQUIRED
327
+ </Badge>
328
+ <h3 className="font-semibold text-gray-900 dark:text-gray-100">
329
+ CLI Authentication Required
330
+ </h3>
331
+ <p className="text-sm text-gray-600 dark:text-gray-400 max-w-xs">
332
+ Run the following command to authenticate:
333
+ </p>
334
+ <code className="block bg-black/80 text-green-400 p-2 rounded text-xs">
335
+ npx moonui auth login
336
+ </code>
337
+ </div>
338
+ </div>
339
+ </div>
340
+ )
341
+ }
179
342
 
180
- // Locked - show locked state
181
- if (isLocked) {
182
- return (
183
- <LockedComponent
184
- componentName={componentName || componentId}
185
- className={className}
186
- >
187
- {fallback || children}
188
- </LockedComponent>
189
- )
343
+ // Device not valid
344
+ if (!authState.deviceValid) {
345
+ return (
346
+ <div className={cn("relative", className)}>
347
+ <div className="blur-sm grayscale-[0.3] opacity-60 pointer-events-none">
348
+ {fallback || children}
349
+ </div>
350
+ <div className="absolute inset-0 bg-gradient-to-br from-yellow-500/10 via-transparent to-orange-500/10 border-2 border-dashed border-yellow-400/30 rounded-lg flex items-center justify-center">
351
+ <div className="text-center space-y-3 p-6">
352
+ <Badge variant="secondary" className="bg-yellow-500 text-white mb-2">
353
+ <Zap className="w-3 h-3 mr-1" />
354
+ DEVICE LIMIT
355
+ </Badge>
356
+ <h3 className="font-semibold text-gray-900 dark:text-gray-100">
357
+ Device Limit Exceeded
358
+ </h3>
359
+ <p className="text-sm text-gray-600 dark:text-gray-400 max-w-xs">
360
+ This device is not registered or you've reached your device limit
361
+ </p>
362
+ <Button
363
+ size="sm"
364
+ className="bg-gradient-to-r from-yellow-500 to-orange-500"
365
+ onClick={() => window.open('/dashboard/devices', '_blank')}
366
+ >
367
+ Manage Devices
368
+ </Button>
369
+ </div>
370
+ </div>
371
+ </div>
372
+ )
373
+ }
374
+
375
+ // No pro access
376
+ if (!authState.hasProAccess) {
377
+ return (
378
+ <LockedComponent
379
+ componentName={componentName || componentId}
380
+ className={className}
381
+ >
382
+ {fallback || children}
383
+ </LockedComponent>
384
+ )
385
+ }
190
386
  }
191
387
 
192
- // Default fallback
388
+ // All checks passed - show component
193
389
  return <div className={className}>{children}</div>
194
390
  }
195
391
 
@@ -0,0 +1,261 @@
1
+ "use client"
2
+
3
+ // Pro Component Wrapper - Simplified Version
4
+ // Development: CLI auth required
5
+ // Production: License key validation with 24h cache
6
+
7
+ import React, { useState, useEffect } from 'react'
8
+ import { cn } from '../../lib/utils'
9
+ import { Badge } from './badge'
10
+ import { Lock, Terminal, AlertCircle } from 'lucide-react'
11
+
12
+ interface ProComponentWrapperProps {
13
+ children: React.ReactNode
14
+ componentId: string
15
+ componentName?: string
16
+ className?: string
17
+ fallback?: React.ReactNode
18
+ }
19
+
20
+ interface LicenseCache {
21
+ timestamp: number
22
+ hasProAccess: boolean
23
+ }
24
+
25
+ const CACHE_KEY = 'moonui_license_cache'
26
+ const CACHE_DURATION = 24 * 60 * 60 * 1000 // 24 hours
27
+
28
+ export function ProComponentWrapper({
29
+ children,
30
+ componentId,
31
+ componentName,
32
+ className,
33
+ fallback
34
+ }: ProComponentWrapperProps) {
35
+ const [state, setState] = useState<{
36
+ hasAccess: boolean
37
+ loading: boolean
38
+ message?: string
39
+ }>({
40
+ hasAccess: false,
41
+ loading: true
42
+ })
43
+
44
+ useEffect(() => {
45
+ // Check if development environment
46
+ const isDevelopment = () => {
47
+ if (typeof window === 'undefined') return false
48
+
49
+ const isLocalhost =
50
+ window.location.hostname === 'localhost' ||
51
+ window.location.hostname === '127.0.0.1' ||
52
+ window.location.hostname.includes('.local')
53
+
54
+ const isDevPort =
55
+ window.location.port === '3000' ||
56
+ window.location.port === '3001' ||
57
+ window.location.port === '8080' ||
58
+ window.location.port === '5173'
59
+
60
+ // Must be localhost with dev port
61
+ return process.env.NODE_ENV === 'development' && isLocalhost && isDevPort
62
+ }
63
+
64
+ // DEVELOPMENT: Check CLI auth
65
+ if (isDevelopment()) {
66
+ const checkDevAuth = () => {
67
+ const cliToken = localStorage.getItem('moonui_cli_token')
68
+ const deviceId = localStorage.getItem('moonui_device_id')
69
+
70
+ if (!cliToken || !deviceId) {
71
+ setState({
72
+ hasAccess: false,
73
+ loading: false,
74
+ message: 'CLI authentication required'
75
+ })
76
+ return
77
+ }
78
+
79
+ // Token exists - allow access
80
+ setState({
81
+ hasAccess: true,
82
+ loading: false
83
+ })
84
+ }
85
+
86
+ checkDevAuth()
87
+ return
88
+ }
89
+
90
+ // PRODUCTION: Check license cache
91
+ const checkProdLicense = () => {
92
+ try {
93
+ // Check cache first
94
+ const cached = localStorage.getItem(CACHE_KEY)
95
+ if (cached) {
96
+ const data = JSON.parse(cached) as LicenseCache
97
+ const now = Date.now()
98
+
99
+ // Cache still valid (24 hours)
100
+ if (data.timestamp && (now - data.timestamp) < CACHE_DURATION) {
101
+ setState({
102
+ hasAccess: data.hasProAccess,
103
+ loading: false
104
+ })
105
+ return
106
+ }
107
+ }
108
+
109
+ // No valid cache - need license key
110
+ const licenseKey = process.env.NEXT_PUBLIC_MOONUI_LICENSE_KEY
111
+
112
+ if (!licenseKey) {
113
+ setState({
114
+ hasAccess: false,
115
+ loading: false,
116
+ message: 'No license key configured'
117
+ })
118
+ return
119
+ }
120
+
121
+ // Validate license async
122
+ validateLicense(licenseKey).then(result => {
123
+ // Save to cache
124
+ const cacheData: LicenseCache = {
125
+ timestamp: Date.now(),
126
+ hasProAccess: result.hasProAccess
127
+ }
128
+ localStorage.setItem(CACHE_KEY, JSON.stringify(cacheData))
129
+
130
+ setState({
131
+ hasAccess: result.hasProAccess,
132
+ loading: false
133
+ })
134
+ }).catch(error => {
135
+ console.error('License validation failed:', error)
136
+
137
+ // Try expired cache as fallback
138
+ const cached = localStorage.getItem(CACHE_KEY)
139
+ if (cached) {
140
+ const data = JSON.parse(cached) as LicenseCache
141
+ setState({
142
+ hasAccess: data.hasProAccess,
143
+ loading: false,
144
+ message: 'Using cached license'
145
+ })
146
+ } else {
147
+ setState({
148
+ hasAccess: false,
149
+ loading: false,
150
+ message: 'License validation failed'
151
+ })
152
+ }
153
+ })
154
+
155
+ } catch (error) {
156
+ console.error('License check error:', error)
157
+ setState({
158
+ hasAccess: false,
159
+ loading: false,
160
+ message: 'License check failed'
161
+ })
162
+ }
163
+ }
164
+
165
+ checkProdLicense()
166
+ }, [])
167
+
168
+ // Loading state
169
+ if (state.loading) {
170
+ return (
171
+ <div className={cn("animate-pulse bg-gray-200 dark:bg-gray-800 rounded-lg h-32", className)}>
172
+ <div className="flex items-center justify-center h-full">
173
+ <div className="text-sm text-gray-500">Loading...</div>
174
+ </div>
175
+ </div>
176
+ )
177
+ }
178
+
179
+ // Access granted
180
+ if (state.hasAccess) {
181
+ return <>{children}</>
182
+ }
183
+
184
+ // Development: Show CLI auth required
185
+ if (state.message === 'CLI authentication required') {
186
+ return (
187
+ <div className={cn("relative", className)}>
188
+ <div className="blur-sm grayscale opacity-60 pointer-events-none">
189
+ {fallback || children}
190
+ </div>
191
+ <div className="absolute inset-0 bg-gradient-to-br from-blue-500/10 via-transparent to-purple-500/10 border-2 border-dashed border-blue-400/30 rounded-lg flex items-center justify-center">
192
+ <div className="text-center space-y-3 p-6 bg-white/90 dark:bg-gray-900/90 rounded-lg">
193
+ <Badge variant="secondary" className="mb-2">
194
+ <Terminal className="w-3 h-3 mr-1" />
195
+ DEV AUTH REQUIRED
196
+ </Badge>
197
+ <h3 className="font-semibold">CLI Authentication Required</h3>
198
+ <p className="text-sm text-gray-600 dark:text-gray-400 max-w-xs">
199
+ Login with MoonUI CLI to use Pro components in development
200
+ </p>
201
+ <code className="block bg-black text-green-400 p-2 rounded text-xs font-mono">
202
+ npx moonui auth login
203
+ </code>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ )
208
+ }
209
+
210
+ // Production: Show license required
211
+ return (
212
+ <div className={cn("relative", className)}>
213
+ <div className="blur-sm grayscale opacity-60 pointer-events-none">
214
+ {fallback || children}
215
+ </div>
216
+ <div className="absolute inset-0 bg-gradient-to-br from-amber-500/10 via-transparent to-orange-500/10 border-2 border-dashed border-amber-400/30 rounded-lg flex items-center justify-center">
217
+ <div className="text-center space-y-3 p-6 bg-white/90 dark:bg-gray-900/90 rounded-lg">
218
+ <Badge variant="destructive" className="mb-2">
219
+ <AlertCircle className="w-3 h-3 mr-1" />
220
+ LICENSE REQUIRED
221
+ </Badge>
222
+ <h3 className="font-semibold">{componentName || componentId}</h3>
223
+ <p className="text-sm text-gray-600 dark:text-gray-400 max-w-xs">
224
+ {state.message || 'Pro license required to use this component'}
225
+ </p>
226
+ <a
227
+ href="https://moonui.dev/pricing"
228
+ target="_blank"
229
+ rel="noopener noreferrer"
230
+ className="inline-block bg-gradient-to-r from-amber-500 to-orange-500 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-amber-600 hover:to-orange-600 transition-colors"
231
+ >
232
+ Get Pro License
233
+ </a>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ )
238
+ }
239
+
240
+ // Validate license with API
241
+ async function validateLicense(licenseKey: string): Promise<{ hasProAccess: boolean }> {
242
+ const response = await fetch('/api/v1/license/validate', {
243
+ method: 'POST',
244
+ headers: {
245
+ 'Content-Type': 'application/json'
246
+ },
247
+ body: JSON.stringify({
248
+ licenseKey,
249
+ domain: window.location.hostname
250
+ })
251
+ })
252
+
253
+ if (!response.ok) {
254
+ throw new Error('License validation failed')
255
+ }
256
+
257
+ const data = await response.json()
258
+ return {
259
+ hasProAccess: data.hasProAccess || false
260
+ }
261
+ }