@moontra/moonui 2.2.7 → 2.3.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/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +151 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +151 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/locked-component.tsx +219 -23
- package/src/components/ui/pro-component-wrapper.tsx +261 -0
- package/src/__tests__/use-pro-access.test.tsx +0 -183
- package/src/use-subscription.ts +0 -37
package/package.json
CHANGED
|
@@ -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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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 (
|
|
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
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
//
|
|
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
|
+
}
|