@moontra/moonui-pro 2.0.22 → 2.1.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.mjs +215 -214
- package/package.json +4 -2
- package/src/__tests__/use-intersection-observer.test.tsx +216 -0
- package/src/__tests__/use-local-storage.test.tsx +174 -0
- package/src/__tests__/use-pro-access.test.tsx +183 -0
- package/src/components/advanced-chart/advanced-chart.test.tsx +281 -0
- package/src/components/advanced-chart/index.tsx +412 -0
- package/src/components/advanced-forms/index.tsx +431 -0
- package/src/components/animated-button/index.tsx +202 -0
- package/src/components/calendar/event-dialog.tsx +372 -0
- package/src/components/calendar/index.tsx +557 -0
- package/src/components/color-picker/index.tsx +434 -0
- package/src/components/dashboard/index.tsx +334 -0
- package/src/components/data-table/data-table.test.tsx +187 -0
- package/src/components/data-table/index.tsx +368 -0
- package/src/components/draggable-list/index.tsx +100 -0
- package/src/components/enhanced/button.tsx +360 -0
- package/src/components/enhanced/card.tsx +272 -0
- package/src/components/enhanced/dialog.tsx +248 -0
- package/src/components/enhanced/index.ts +3 -0
- package/src/components/error-boundary/index.tsx +111 -0
- package/src/components/file-upload/file-upload.test.tsx +242 -0
- package/src/components/file-upload/index.tsx +362 -0
- package/src/components/floating-action-button/index.tsx +209 -0
- package/src/components/github-stars/index.tsx +414 -0
- package/src/components/health-check/index.tsx +441 -0
- package/src/components/hover-card-3d/index.tsx +170 -0
- package/src/components/index.ts +76 -0
- package/src/components/kanban/index.tsx +436 -0
- package/src/components/lazy-component/index.tsx +342 -0
- package/src/components/magnetic-button/index.tsx +170 -0
- package/src/components/memory-efficient-data/index.tsx +352 -0
- package/src/components/optimized-image/index.tsx +427 -0
- package/src/components/performance-debugger/index.tsx +591 -0
- package/src/components/performance-monitor/index.tsx +775 -0
- package/src/components/pinch-zoom/index.tsx +172 -0
- package/src/components/rich-text-editor/index-old-backup.tsx +443 -0
- package/src/components/rich-text-editor/index.tsx +1537 -0
- package/src/components/rich-text-editor/slash-commands-extension.ts +220 -0
- package/src/components/rich-text-editor/slash-commands.css +35 -0
- package/src/components/rich-text-editor/table-styles.css +65 -0
- package/src/components/spotlight-card/index.tsx +194 -0
- package/src/components/swipeable-card/index.tsx +100 -0
- package/src/components/timeline/index.tsx +333 -0
- package/src/components/ui/animated-button.tsx +185 -0
- package/src/components/ui/avatar.tsx +135 -0
- package/src/components/ui/badge.tsx +225 -0
- package/src/components/ui/button.tsx +221 -0
- package/src/components/ui/card.tsx +141 -0
- package/src/components/ui/checkbox.tsx +256 -0
- package/src/components/ui/color-picker.tsx +95 -0
- package/src/components/ui/dialog.tsx +332 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/hover-card-3d.tsx +103 -0
- package/src/components/ui/index.ts +33 -0
- package/src/components/ui/input.tsx +219 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/magnetic-button.tsx +129 -0
- package/src/components/ui/popover.tsx +183 -0
- package/src/components/ui/select.tsx +273 -0
- package/src/components/ui/separator.tsx +140 -0
- package/src/components/ui/slider.tsx +351 -0
- package/src/components/ui/spotlight-card.tsx +119 -0
- package/src/components/ui/switch.tsx +83 -0
- package/src/components/ui/tabs.tsx +195 -0
- package/src/components/ui/textarea.tsx +25 -0
- package/src/components/ui/toast.tsx +313 -0
- package/src/components/ui/tooltip.tsx +152 -0
- package/src/components/virtual-list/index.tsx +369 -0
- package/src/hooks/use-chart.ts +205 -0
- package/src/hooks/use-data-table.ts +182 -0
- package/src/hooks/use-docs-pro-access.ts +13 -0
- package/src/hooks/use-license-check.ts +65 -0
- package/src/hooks/use-subscription.ts +19 -0
- package/src/index.ts +14 -0
- package/src/lib/micro-interactions.ts +255 -0
- package/src/lib/utils.ts +6 -0
- package/src/patterns/login-form/index.tsx +276 -0
- package/src/patterns/login-form/types.ts +67 -0
- package/src/setupTests.ts +41 -0
- package/src/styles/design-system.css +365 -0
- package/src/styles/index.css +4 -0
- package/src/styles/tailwind.css +6 -0
- package/src/styles/tokens.css +453 -0
- package/src/types/moonui.d.ts +22 -0
- package/src/use-intersection-observer.tsx +154 -0
- package/src/use-local-storage.tsx +71 -0
- package/src/use-paddle.ts +138 -0
- package/src/use-performance-optimizer.ts +379 -0
- package/src/use-pro-access.ts +141 -0
- package/src/use-scroll-animation.ts +221 -0
- package/src/use-subscription.ts +37 -0
- package/src/use-toast.ts +32 -0
- package/src/utils/chart-helpers.ts +257 -0
- package/src/utils/cn.ts +69 -0
- package/src/utils/data-processing.ts +151 -0
- package/src/utils/license-guard.tsx +177 -0
- package/src/utils/license-validator.tsx +183 -0
- package/src/utils/package-guard.ts +60 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect, useCallback } from "react"
|
|
4
|
+
import { motion, AnimatePresence } from "framer-motion"
|
|
5
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
|
|
6
|
+
import { Button } from "../ui/button"
|
|
7
|
+
import { Badge } from "../ui/badge"
|
|
8
|
+
import { Progress } from "../ui/progress"
|
|
9
|
+
import { cn } from "../../lib/utils"
|
|
10
|
+
import {
|
|
11
|
+
Activity,
|
|
12
|
+
CheckCircle,
|
|
13
|
+
XCircle,
|
|
14
|
+
AlertCircle,
|
|
15
|
+
Clock,
|
|
16
|
+
Zap,
|
|
17
|
+
Globe,
|
|
18
|
+
Database,
|
|
19
|
+
Server,
|
|
20
|
+
Lock,
|
|
21
|
+
Sparkles,
|
|
22
|
+
RefreshCw,
|
|
23
|
+
TrendingUp,
|
|
24
|
+
Timer
|
|
25
|
+
} from "lucide-react"
|
|
26
|
+
import { useSubscription } from "../../hooks/use-subscription"
|
|
27
|
+
|
|
28
|
+
export interface HealthCheckEndpoint {
|
|
29
|
+
id: string
|
|
30
|
+
name: string
|
|
31
|
+
url: string
|
|
32
|
+
method: "GET" | "POST" | "PUT" | "DELETE" | "HEAD"
|
|
33
|
+
expectedStatus?: number[]
|
|
34
|
+
timeout?: number
|
|
35
|
+
headers?: Record<string, string>
|
|
36
|
+
body?: string
|
|
37
|
+
critical?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface HealthCheckResult {
|
|
41
|
+
id: string
|
|
42
|
+
name: string
|
|
43
|
+
status: "healthy" | "unhealthy" | "warning" | "pending"
|
|
44
|
+
responseTime: number
|
|
45
|
+
statusCode?: number
|
|
46
|
+
error?: string
|
|
47
|
+
timestamp: Date
|
|
48
|
+
uptime?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface HealthCheckProps {
|
|
52
|
+
endpoints: HealthCheckEndpoint[]
|
|
53
|
+
interval?: number
|
|
54
|
+
autoRefresh?: boolean
|
|
55
|
+
showResponseTime?: boolean
|
|
56
|
+
showUptime?: boolean
|
|
57
|
+
showHistory?: boolean
|
|
58
|
+
maxHistory?: number
|
|
59
|
+
onStatusChange?: (results: HealthCheckResult[]) => void
|
|
60
|
+
className?: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const HealthCheckInternal: React.FC<HealthCheckProps> = ({
|
|
64
|
+
endpoints,
|
|
65
|
+
interval = 30000, // 30 seconds
|
|
66
|
+
autoRefresh = true,
|
|
67
|
+
showResponseTime = true,
|
|
68
|
+
showUptime = true,
|
|
69
|
+
showHistory = true,
|
|
70
|
+
maxHistory = 20,
|
|
71
|
+
onStatusChange,
|
|
72
|
+
className
|
|
73
|
+
}) => {
|
|
74
|
+
const [results, setResults] = useState<HealthCheckResult[]>([])
|
|
75
|
+
const [history, setHistory] = useState<Record<string, HealthCheckResult[]>>({})
|
|
76
|
+
const [isChecking, setIsChecking] = useState(false)
|
|
77
|
+
const [lastCheck, setLastCheck] = useState<Date | null>(null)
|
|
78
|
+
|
|
79
|
+
const checkEndpoint = async (endpoint: HealthCheckEndpoint): Promise<HealthCheckResult> => {
|
|
80
|
+
const startTime = Date.now()
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const controller = new AbortController()
|
|
84
|
+
const timeoutId = setTimeout(() => controller.abort(), endpoint.timeout || 5000)
|
|
85
|
+
|
|
86
|
+
const response = await fetch(endpoint.url, {
|
|
87
|
+
method: endpoint.method,
|
|
88
|
+
headers: {
|
|
89
|
+
'Content-Type': 'application/json',
|
|
90
|
+
...endpoint.headers
|
|
91
|
+
},
|
|
92
|
+
body: endpoint.body,
|
|
93
|
+
signal: controller.signal
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
clearTimeout(timeoutId)
|
|
97
|
+
const responseTime = Date.now() - startTime
|
|
98
|
+
const expectedStatuses = endpoint.expectedStatus || [200, 201, 204]
|
|
99
|
+
|
|
100
|
+
const isHealthy = expectedStatuses.includes(response.status)
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
id: endpoint.id,
|
|
104
|
+
name: endpoint.name,
|
|
105
|
+
status: isHealthy ? "healthy" : "unhealthy",
|
|
106
|
+
responseTime,
|
|
107
|
+
statusCode: response.status,
|
|
108
|
+
timestamp: new Date()
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
const responseTime = Date.now() - startTime
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
id: endpoint.id,
|
|
115
|
+
name: endpoint.name,
|
|
116
|
+
status: "unhealthy",
|
|
117
|
+
responseTime,
|
|
118
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
119
|
+
timestamp: new Date()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const checkAllEndpoints = useCallback(async () => {
|
|
125
|
+
setIsChecking(true)
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const checks = endpoints.map(endpoint => checkEndpoint(endpoint))
|
|
129
|
+
const newResults = await Promise.all(checks)
|
|
130
|
+
|
|
131
|
+
setResults(newResults)
|
|
132
|
+
setLastCheck(new Date())
|
|
133
|
+
|
|
134
|
+
// Update history
|
|
135
|
+
setHistory(prevHistory => {
|
|
136
|
+
const newHistory = { ...prevHistory }
|
|
137
|
+
|
|
138
|
+
newResults.forEach(result => {
|
|
139
|
+
if (!newHistory[result.id]) {
|
|
140
|
+
newHistory[result.id] = []
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
newHistory[result.id] = [
|
|
144
|
+
result,
|
|
145
|
+
...newHistory[result.id].slice(0, maxHistory - 1)
|
|
146
|
+
]
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
return newHistory
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
onStatusChange?.(newResults)
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error("Health check failed:", error)
|
|
155
|
+
} finally {
|
|
156
|
+
setIsChecking(false)
|
|
157
|
+
}
|
|
158
|
+
}, [endpoints, maxHistory, onStatusChange])
|
|
159
|
+
|
|
160
|
+
// Initial check
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
checkAllEndpoints()
|
|
163
|
+
}, [checkAllEndpoints])
|
|
164
|
+
|
|
165
|
+
// Auto refresh
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (!autoRefresh) return
|
|
168
|
+
|
|
169
|
+
const intervalId = setInterval(checkAllEndpoints, interval)
|
|
170
|
+
return () => clearInterval(intervalId)
|
|
171
|
+
}, [autoRefresh, interval, checkAllEndpoints])
|
|
172
|
+
|
|
173
|
+
const getOverallStatus = (): "healthy" | "unhealthy" | "warning" => {
|
|
174
|
+
if (results.length === 0) return "warning"
|
|
175
|
+
|
|
176
|
+
const criticalEndpoints = endpoints.filter(e => e.critical !== false)
|
|
177
|
+
const criticalResults = results.filter(r =>
|
|
178
|
+
criticalEndpoints.some(e => e.id === r.id)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
const hasUnhealthy = criticalResults.some(r => r.status === "unhealthy")
|
|
182
|
+
const hasWarning = results.some(r => r.status === "warning")
|
|
183
|
+
|
|
184
|
+
if (hasUnhealthy) return "unhealthy"
|
|
185
|
+
if (hasWarning) return "warning"
|
|
186
|
+
return "healthy"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const getStatusIcon = (status: string) => {
|
|
190
|
+
switch (status) {
|
|
191
|
+
case "healthy":
|
|
192
|
+
return <CheckCircle className="h-4 w-4 text-green-500" />
|
|
193
|
+
case "unhealthy":
|
|
194
|
+
return <XCircle className="h-4 w-4 text-red-500" />
|
|
195
|
+
case "warning":
|
|
196
|
+
return <AlertCircle className="h-4 w-4 text-yellow-500" />
|
|
197
|
+
default:
|
|
198
|
+
return <Clock className="h-4 w-4 text-gray-500" />
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const getStatusColor = (status: string) => {
|
|
203
|
+
switch (status) {
|
|
204
|
+
case "healthy":
|
|
205
|
+
return "bg-green-500"
|
|
206
|
+
case "unhealthy":
|
|
207
|
+
return "bg-red-500"
|
|
208
|
+
case "warning":
|
|
209
|
+
return "bg-yellow-500"
|
|
210
|
+
default:
|
|
211
|
+
return "bg-gray-500"
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const formatResponseTime = (time: number) => {
|
|
216
|
+
if (time < 1000) return `${time}ms`
|
|
217
|
+
return `${(time / 1000).toFixed(2)}s`
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const calculateUptime = (endpointId: string): number => {
|
|
221
|
+
const endpointHistory = history[endpointId] || []
|
|
222
|
+
if (endpointHistory.length === 0) return 0
|
|
223
|
+
|
|
224
|
+
const healthyCount = endpointHistory.filter(r => r.status === "healthy").length
|
|
225
|
+
return (healthyCount / endpointHistory.length) * 100
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const overallStatus = getOverallStatus()
|
|
229
|
+
const healthyCount = results.filter(r => r.status === "healthy").length
|
|
230
|
+
const overallUptime = results.length > 0
|
|
231
|
+
? results.reduce((acc, result) => acc + calculateUptime(result.id), 0) / results.length
|
|
232
|
+
: 0
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<Card className={cn("w-full", className)}>
|
|
236
|
+
<CardHeader>
|
|
237
|
+
<div className="flex items-center justify-between">
|
|
238
|
+
<div className="flex items-center gap-3">
|
|
239
|
+
<div className="flex items-center gap-2">
|
|
240
|
+
<Activity className="h-6 w-6" />
|
|
241
|
+
<div>
|
|
242
|
+
<CardTitle className="flex items-center gap-2">
|
|
243
|
+
System Health
|
|
244
|
+
<Badge
|
|
245
|
+
variant={overallStatus === "healthy" ? "default" : "destructive"}
|
|
246
|
+
className={cn(
|
|
247
|
+
"text-white",
|
|
248
|
+
overallStatus === "healthy" && "bg-green-500",
|
|
249
|
+
overallStatus === "warning" && "bg-yellow-500",
|
|
250
|
+
overallStatus === "unhealthy" && "bg-red-500"
|
|
251
|
+
)}
|
|
252
|
+
>
|
|
253
|
+
{overallStatus.toUpperCase()}
|
|
254
|
+
</Badge>
|
|
255
|
+
</CardTitle>
|
|
256
|
+
{lastCheck && (
|
|
257
|
+
<CardDescription>
|
|
258
|
+
Last checked: {lastCheck.toLocaleTimeString()}
|
|
259
|
+
</CardDescription>
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<div className="flex items-center gap-2">
|
|
266
|
+
{showUptime && (
|
|
267
|
+
<div className="text-right">
|
|
268
|
+
<div className="text-sm font-medium">{overallUptime.toFixed(1)}%</div>
|
|
269
|
+
<div className="text-xs text-muted-foreground">Uptime</div>
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
|
|
273
|
+
<Button
|
|
274
|
+
onClick={checkAllEndpoints}
|
|
275
|
+
disabled={isChecking}
|
|
276
|
+
variant="outline"
|
|
277
|
+
size="sm"
|
|
278
|
+
>
|
|
279
|
+
<RefreshCw className={cn("h-4 w-4", isChecking && "animate-spin")} />
|
|
280
|
+
</Button>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
{/* Overall Stats */}
|
|
285
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
|
|
286
|
+
<div className="text-center">
|
|
287
|
+
<div className="text-2xl font-bold text-green-500">{healthyCount}</div>
|
|
288
|
+
<div className="text-xs text-muted-foreground">Healthy</div>
|
|
289
|
+
</div>
|
|
290
|
+
<div className="text-center">
|
|
291
|
+
<div className="text-2xl font-bold text-red-500">
|
|
292
|
+
{results.filter(r => r.status === "unhealthy").length}
|
|
293
|
+
</div>
|
|
294
|
+
<div className="text-xs text-muted-foreground">Unhealthy</div>
|
|
295
|
+
</div>
|
|
296
|
+
{showResponseTime && (
|
|
297
|
+
<div className="text-center">
|
|
298
|
+
<div className="text-lg font-bold">
|
|
299
|
+
{results.length > 0
|
|
300
|
+
? formatResponseTime(
|
|
301
|
+
results.reduce((acc, r) => acc + r.responseTime, 0) / results.length
|
|
302
|
+
)
|
|
303
|
+
: "0ms"
|
|
304
|
+
}
|
|
305
|
+
</div>
|
|
306
|
+
<div className="text-xs text-muted-foreground">Avg Response</div>
|
|
307
|
+
</div>
|
|
308
|
+
)}
|
|
309
|
+
<div className="text-center">
|
|
310
|
+
<div className="text-lg font-bold">{results.length}</div>
|
|
311
|
+
<div className="text-xs text-muted-foreground">Services</div>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
</CardHeader>
|
|
315
|
+
|
|
316
|
+
<CardContent>
|
|
317
|
+
<div className="space-y-4">
|
|
318
|
+
<AnimatePresence>
|
|
319
|
+
{results.map((result, index) => (
|
|
320
|
+
<motion.div
|
|
321
|
+
key={result.id}
|
|
322
|
+
initial={{ opacity: 0, x: -20 }}
|
|
323
|
+
animate={{ opacity: 1, x: 0 }}
|
|
324
|
+
exit={{ opacity: 0, x: 20 }}
|
|
325
|
+
transition={{ delay: index * 0.05 }}
|
|
326
|
+
>
|
|
327
|
+
<Card>
|
|
328
|
+
<CardContent className="p-4">
|
|
329
|
+
<div className="flex items-center justify-between">
|
|
330
|
+
<div className="flex items-center gap-3">
|
|
331
|
+
{getStatusIcon(result.status)}
|
|
332
|
+
<div>
|
|
333
|
+
<div className="font-medium">{result.name}</div>
|
|
334
|
+
{result.error && (
|
|
335
|
+
<div className="text-sm text-red-500">{result.error}</div>
|
|
336
|
+
)}
|
|
337
|
+
{result.statusCode && (
|
|
338
|
+
<div className="text-xs text-muted-foreground">
|
|
339
|
+
HTTP {result.statusCode}
|
|
340
|
+
</div>
|
|
341
|
+
)}
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<div className="flex items-center gap-4 text-sm">
|
|
346
|
+
{showResponseTime && (
|
|
347
|
+
<div className="text-right">
|
|
348
|
+
<div className="font-medium">
|
|
349
|
+
{formatResponseTime(result.responseTime)}
|
|
350
|
+
</div>
|
|
351
|
+
<div className="text-xs text-muted-foreground">Response</div>
|
|
352
|
+
</div>
|
|
353
|
+
)}
|
|
354
|
+
|
|
355
|
+
{showUptime && (
|
|
356
|
+
<div className="text-right">
|
|
357
|
+
<div className="font-medium">
|
|
358
|
+
{calculateUptime(result.id).toFixed(1)}%
|
|
359
|
+
</div>
|
|
360
|
+
<div className="text-xs text-muted-foreground">Uptime</div>
|
|
361
|
+
</div>
|
|
362
|
+
)}
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
{/* Mini history chart */}
|
|
367
|
+
{showHistory && history[result.id] && (
|
|
368
|
+
<div className="mt-3">
|
|
369
|
+
<div className="flex items-center gap-1 h-2">
|
|
370
|
+
{history[result.id].slice(0, 20).reverse().map((historyResult, i) => (
|
|
371
|
+
<div
|
|
372
|
+
key={i}
|
|
373
|
+
className={cn(
|
|
374
|
+
"flex-1 h-full rounded-sm",
|
|
375
|
+
getStatusColor(historyResult.status)
|
|
376
|
+
)}
|
|
377
|
+
/>
|
|
378
|
+
))}
|
|
379
|
+
</div>
|
|
380
|
+
<div className="text-xs text-muted-foreground mt-1">
|
|
381
|
+
Last {Math.min(history[result.id].length, 20)} checks
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
)}
|
|
385
|
+
</CardContent>
|
|
386
|
+
</Card>
|
|
387
|
+
</motion.div>
|
|
388
|
+
))}
|
|
389
|
+
</AnimatePresence>
|
|
390
|
+
|
|
391
|
+
{results.length === 0 && !isChecking && (
|
|
392
|
+
<div className="text-center py-8">
|
|
393
|
+
<Server className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
|
|
394
|
+
<p className="text-muted-foreground">No endpoints configured</p>
|
|
395
|
+
</div>
|
|
396
|
+
)}
|
|
397
|
+
</div>
|
|
398
|
+
</CardContent>
|
|
399
|
+
</Card>
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export const HealthCheck: React.FC<HealthCheckProps> = ({ className, ...props }) => {
|
|
404
|
+
// Check if we're in docs mode or have pro access
|
|
405
|
+
const docsProAccess = { hasAccess: true } // Pro access assumed in package
|
|
406
|
+
const { hasProAccess, isLoading } = useSubscription()
|
|
407
|
+
|
|
408
|
+
// In docs mode, always show the component
|
|
409
|
+
const canShowComponent = docsProAccess.isDocsMode || hasProAccess
|
|
410
|
+
|
|
411
|
+
// If not in docs mode and no pro access, show upgrade prompt
|
|
412
|
+
if (!docsProAccess.isDocsMode && !isLoading && !hasProAccess) {
|
|
413
|
+
return (
|
|
414
|
+
<Card className={cn("w-fit", className)}>
|
|
415
|
+
<CardContent className="py-6 text-center">
|
|
416
|
+
<div className="space-y-4">
|
|
417
|
+
<div className="rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto">
|
|
418
|
+
<Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
|
419
|
+
</div>
|
|
420
|
+
<div>
|
|
421
|
+
<h3 className="font-semibold text-sm mb-2">Pro Feature</h3>
|
|
422
|
+
<p className="text-muted-foreground text-xs mb-4">
|
|
423
|
+
Health Check is available exclusively to MoonUI Pro subscribers.
|
|
424
|
+
</p>
|
|
425
|
+
<a href="/pricing">
|
|
426
|
+
<Button size="sm">
|
|
427
|
+
<Sparkles className="mr-2 h-4 w-4" />
|
|
428
|
+
Upgrade to Pro
|
|
429
|
+
</Button>
|
|
430
|
+
</a>
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
433
|
+
</CardContent>
|
|
434
|
+
</Card>
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return <HealthCheckInternal className={className} {...props} />
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export type { HealthCheckEndpoint, HealthCheckResult, HealthCheckProps }
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useRef, useState } from "react"
|
|
4
|
+
import { motion, useMotionValue, useSpring, useTransform } from "framer-motion"
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
import { Card, CardContent } from "../ui/card"
|
|
7
|
+
import { Button } from "../ui/button"
|
|
8
|
+
import { Lock, Sparkles } from "lucide-react"
|
|
9
|
+
import { useSubscription } from "../../hooks/use-subscription"
|
|
10
|
+
|
|
11
|
+
export interface HoverCard3DProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
12
|
+
perspective?: number
|
|
13
|
+
rotationIntensity?: number
|
|
14
|
+
springConfig?: {
|
|
15
|
+
stiffness?: number
|
|
16
|
+
damping?: number
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const HoverCard3DInternal = React.forwardRef<HTMLDivElement, HoverCard3DProps>(
|
|
21
|
+
({
|
|
22
|
+
children,
|
|
23
|
+
className,
|
|
24
|
+
perspective = 1000,
|
|
25
|
+
rotationIntensity = 15,
|
|
26
|
+
springConfig = { stiffness: 200, damping: 15 },
|
|
27
|
+
...props
|
|
28
|
+
}, ref) => {
|
|
29
|
+
const cardRef = useRef<HTMLDivElement>(null)
|
|
30
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
31
|
+
|
|
32
|
+
const x = useMotionValue(0)
|
|
33
|
+
const y = useMotionValue(0)
|
|
34
|
+
|
|
35
|
+
const springX = useSpring(x, springConfig)
|
|
36
|
+
const springY = useSpring(y, springConfig)
|
|
37
|
+
|
|
38
|
+
const rotateX = useTransform(springY, [-0.5, 0.5], [rotationIntensity, -rotationIntensity])
|
|
39
|
+
const rotateY = useTransform(springX, [-0.5, 0.5], [-rotationIntensity, rotationIntensity])
|
|
40
|
+
|
|
41
|
+
const handleMouseMove = (e: React.MouseEvent) => {
|
|
42
|
+
if (!cardRef.current) return
|
|
43
|
+
|
|
44
|
+
const rect = cardRef.current.getBoundingClientRect()
|
|
45
|
+
const centerX = rect.left + rect.width / 2
|
|
46
|
+
const centerY = rect.top + rect.height / 2
|
|
47
|
+
|
|
48
|
+
const rotateXValue = (e.clientY - centerY) / (rect.height / 2)
|
|
49
|
+
const rotateYValue = (e.clientX - centerX) / (rect.width / 2)
|
|
50
|
+
|
|
51
|
+
x.set(rotateYValue)
|
|
52
|
+
y.set(rotateXValue)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const handleMouseEnter = () => {
|
|
56
|
+
setIsHovered(true)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const handleMouseLeave = () => {
|
|
60
|
+
setIsHovered(false)
|
|
61
|
+
x.set(0)
|
|
62
|
+
y.set(0)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<motion.div
|
|
67
|
+
ref={(node) => {
|
|
68
|
+
cardRef.current = node
|
|
69
|
+
if (typeof ref === "function") {
|
|
70
|
+
ref(node)
|
|
71
|
+
} else if (ref) {
|
|
72
|
+
ref.current = node
|
|
73
|
+
}
|
|
74
|
+
}}
|
|
75
|
+
className={cn(
|
|
76
|
+
"relative rounded-lg border bg-card text-card-foreground shadow-sm transition-colors duration-200 hover:bg-card/90",
|
|
77
|
+
className
|
|
78
|
+
)}
|
|
79
|
+
onMouseMove={handleMouseMove}
|
|
80
|
+
onMouseEnter={handleMouseEnter}
|
|
81
|
+
onMouseLeave={handleMouseLeave}
|
|
82
|
+
style={{
|
|
83
|
+
transformPerspective: perspective,
|
|
84
|
+
transformStyle: "preserve-3d",
|
|
85
|
+
}}
|
|
86
|
+
animate={{
|
|
87
|
+
rotateX: isHovered ? rotateX : 0,
|
|
88
|
+
rotateY: isHovered ? rotateY : 0,
|
|
89
|
+
scale: isHovered ? 1.05 : 1,
|
|
90
|
+
}}
|
|
91
|
+
transition={{ duration: 0.15 }}
|
|
92
|
+
{...props}
|
|
93
|
+
>
|
|
94
|
+
<motion.div
|
|
95
|
+
className="relative z-10"
|
|
96
|
+
animate={{
|
|
97
|
+
z: isHovered ? 20 : 0,
|
|
98
|
+
}}
|
|
99
|
+
transition={{ duration: 0.2 }}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</motion.div>
|
|
103
|
+
|
|
104
|
+
{/* Gradient overlay */}
|
|
105
|
+
<motion.div
|
|
106
|
+
className="absolute inset-0 rounded-lg bg-gradient-to-br from-white/5 via-transparent to-black/5 pointer-events-none"
|
|
107
|
+
animate={{
|
|
108
|
+
opacity: isHovered ? 1 : 0,
|
|
109
|
+
}}
|
|
110
|
+
transition={{ duration: 0.3 }}
|
|
111
|
+
/>
|
|
112
|
+
|
|
113
|
+
{/* Glow effect */}
|
|
114
|
+
<motion.div
|
|
115
|
+
className="absolute inset-0 rounded-lg bg-primary/5 blur-xl"
|
|
116
|
+
animate={{
|
|
117
|
+
opacity: isHovered ? 0.8 : 0,
|
|
118
|
+
scale: isHovered ? 1.2 : 1,
|
|
119
|
+
}}
|
|
120
|
+
transition={{ duration: 0.3 }}
|
|
121
|
+
style={{ zIndex: -1 }}
|
|
122
|
+
/>
|
|
123
|
+
</motion.div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
HoverCard3DInternal.displayName = "HoverCard3DInternal"
|
|
129
|
+
|
|
130
|
+
export const HoverCard3D = React.forwardRef<HTMLDivElement, HoverCard3DProps>(
|
|
131
|
+
({ className, ...props }, ref) => {
|
|
132
|
+
// Check if we're in docs mode or have pro access
|
|
133
|
+
const docsProAccess = { hasAccess: true } // Pro access assumed in package
|
|
134
|
+
const { hasProAccess, isLoading } = useSubscription()
|
|
135
|
+
|
|
136
|
+
// In docs mode, always show the component
|
|
137
|
+
const canShowComponent = docsProAccess.isDocsMode || hasProAccess
|
|
138
|
+
|
|
139
|
+
// If not in docs mode and no pro access, show upgrade prompt
|
|
140
|
+
if (!docsProAccess.isDocsMode && !isLoading && !hasProAccess) {
|
|
141
|
+
return (
|
|
142
|
+
<Card className={cn("w-fit", className)}>
|
|
143
|
+
<CardContent className="py-6 text-center">
|
|
144
|
+
<div className="space-y-4">
|
|
145
|
+
<div className="rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto">
|
|
146
|
+
<Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
|
147
|
+
</div>
|
|
148
|
+
<div>
|
|
149
|
+
<h3 className="font-semibold text-sm mb-2">Pro Feature</h3>
|
|
150
|
+
<p className="text-muted-foreground text-xs mb-4">
|
|
151
|
+
3D Hover Card is available exclusively to MoonUI Pro subscribers.
|
|
152
|
+
</p>
|
|
153
|
+
<a href="/pricing">
|
|
154
|
+
<Button size="sm">
|
|
155
|
+
<Sparkles className="mr-2 h-4 w-4" />
|
|
156
|
+
Upgrade to Pro
|
|
157
|
+
</Button>
|
|
158
|
+
</a>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</CardContent>
|
|
162
|
+
</Card>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return <HoverCard3DInternal className={className} ref={ref} {...props} />
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
HoverCard3D.displayName = "HoverCard3D"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Pro Components Export - Source of Truth: packages/moonui-pro
|
|
2
|
+
|
|
3
|
+
// Animated Button
|
|
4
|
+
export * from "./animated-button"
|
|
5
|
+
|
|
6
|
+
// Error Boundary
|
|
7
|
+
export * from "./error-boundary"
|
|
8
|
+
|
|
9
|
+
// Floating Action Button
|
|
10
|
+
export * from "./floating-action-button"
|
|
11
|
+
|
|
12
|
+
// Hover Card 3D
|
|
13
|
+
export * from "./hover-card-3d"
|
|
14
|
+
|
|
15
|
+
// Magnetic Button
|
|
16
|
+
export * from "./magnetic-button"
|
|
17
|
+
|
|
18
|
+
// Pinch Zoom
|
|
19
|
+
export * from "./pinch-zoom"
|
|
20
|
+
|
|
21
|
+
// Spotlight Card
|
|
22
|
+
export * from "./spotlight-card"
|
|
23
|
+
|
|
24
|
+
// Calendar
|
|
25
|
+
export * from "./calendar"
|
|
26
|
+
|
|
27
|
+
// Kanban
|
|
28
|
+
export * from "./kanban"
|
|
29
|
+
|
|
30
|
+
// Rich Text Editor
|
|
31
|
+
export * from "./rich-text-editor"
|
|
32
|
+
|
|
33
|
+
// Memory Efficient Data
|
|
34
|
+
export * from "./memory-efficient-data"
|
|
35
|
+
|
|
36
|
+
// Virtual List
|
|
37
|
+
export * from "./virtual-list"
|
|
38
|
+
|
|
39
|
+
// Swipeable Card
|
|
40
|
+
export * from "./swipeable-card"
|
|
41
|
+
|
|
42
|
+
// Timeline
|
|
43
|
+
export * from "./timeline"
|
|
44
|
+
|
|
45
|
+
// Advanced Chart
|
|
46
|
+
export * from "./advanced-chart"
|
|
47
|
+
|
|
48
|
+
// Dashboard
|
|
49
|
+
export * from "./dashboard"
|
|
50
|
+
|
|
51
|
+
// Draggable List
|
|
52
|
+
export * from "./draggable-list"
|
|
53
|
+
|
|
54
|
+
// Advanced Forms
|
|
55
|
+
export * from "./advanced-forms"
|
|
56
|
+
|
|
57
|
+
// Color Picker
|
|
58
|
+
export * from "./color-picker"
|
|
59
|
+
|
|
60
|
+
// GitHub Stars
|
|
61
|
+
export * from "./github-stars"
|
|
62
|
+
|
|
63
|
+
// Health Check
|
|
64
|
+
export * from "./health-check"
|
|
65
|
+
|
|
66
|
+
// Lazy Component
|
|
67
|
+
export * from "./lazy-component"
|
|
68
|
+
|
|
69
|
+
// Optimized Image
|
|
70
|
+
export * from "./optimized-image"
|
|
71
|
+
|
|
72
|
+
// Performance Debugger
|
|
73
|
+
export * from "./performance-debugger"
|
|
74
|
+
|
|
75
|
+
// Performance Monitor
|
|
76
|
+
export * from "./performance-monitor"
|