@moontra/moonui 2.1.5 → 2.1.7
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.js +8251 -2872
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8100 -2653
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/accordion.tsx +8 -11
- package/src/components/ui/alert.tsx +10 -13
- package/src/components/ui/analyze-components.txt +4 -0
- package/src/components/ui/aspect-ratio.tsx +6 -9
- package/src/components/ui/avatar.tsx +18 -21
- package/src/components/ui/badge.tsx +5 -8
- package/src/components/ui/breadcrumb.tsx +29 -31
- package/src/components/ui/button.tsx +11 -15
- package/src/components/ui/calendar.tsx +6 -9
- package/src/components/ui/card.tsx +19 -22
- package/src/components/ui/checkbox.tsx +12 -15
- package/src/components/ui/collapsible.tsx +12 -14
- package/src/components/ui/color-picker.tsx +2 -2
- package/src/components/ui/command.tsx +33 -35
- package/src/components/ui/component-analysis-report.md +118 -0
- package/src/components/ui/data-table.tsx +2 -2
- package/src/components/ui/date-picker.tsx +9 -9
- package/src/components/ui/dialog.tsx +29 -31
- package/src/components/ui/draggable-list.tsx +81 -0
- package/src/components/ui/dropdown-menu.tsx +42 -44
- package/src/components/ui/file-upload.tsx +3 -3
- package/src/components/ui/gesture-drawer.tsx +146 -0
- package/src/components/ui/index.ts +43 -9
- package/src/components/ui/input.tsx +6 -9
- package/src/components/ui/label.tsx +5 -8
- package/src/components/ui/moon-logo.tsx +1 -1
- package/src/components/ui/pagination.tsx +17 -19
- package/src/components/ui/popover.tsx +16 -18
- package/src/components/ui/progress.tsx +6 -9
- package/src/components/ui/radio-group.tsx +11 -14
- package/src/components/ui/rich-text-editor/index.tsx +71 -0
- package/src/components/ui/select.tsx +30 -32
- package/src/components/ui/separator.tsx +6 -9
- package/src/components/ui/simple-editor.tsx +4 -4
- package/src/components/ui/skeleton.tsx +13 -16
- package/src/components/ui/slider.tsx +5 -8
- package/src/components/ui/swipeable-card.tsx +69 -0
- package/src/components/ui/switch.tsx +4 -7
- package/src/components/ui/table.tsx +29 -31
- package/src/components/ui/tabs.tsx +13 -16
- package/src/components/ui/textarea.tsx +4 -7
- package/src/components/ui/toast.tsx +18 -20
- package/src/components/ui/toggle.tsx +5 -8
- package/src/components/ui/tooltip.tsx +16 -18
- package/src/index.tsx +13 -7
- package/src/lib/utils.ts +2 -65
- package/src/components/ui/locked-component.tsx +0 -215
- package/src/use-pro-access.ts +0 -141
package/src/lib/utils.ts
CHANGED
|
@@ -1,69 +1,6 @@
|
|
|
1
|
-
import { type ClassValue, clsx } from
|
|
2
|
-
import { twMerge } from
|
|
1
|
+
import { type ClassValue, clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
3
|
|
|
4
|
-
// Utility function using clsx and tailwind-merge
|
|
5
4
|
export function cn(...inputs: ClassValue[]) {
|
|
6
5
|
return twMerge(clsx(inputs));
|
|
7
6
|
}
|
|
8
|
-
|
|
9
|
-
// Date formatting utility
|
|
10
|
-
export function formatDate(date: Date): string {
|
|
11
|
-
if (isNaN(date.getTime())) {
|
|
12
|
-
return 'Invalid Date'
|
|
13
|
-
}
|
|
14
|
-
return date.toLocaleDateString('en-US', {
|
|
15
|
-
year: 'numeric',
|
|
16
|
-
month: 'short',
|
|
17
|
-
day: 'numeric'
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Currency formatting utility
|
|
22
|
-
export function formatCurrency(amount: number): string {
|
|
23
|
-
return new Intl.NumberFormat('en-US', {
|
|
24
|
-
style: 'currency',
|
|
25
|
-
currency: 'USD'
|
|
26
|
-
}).format(amount)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Text truncation utility
|
|
30
|
-
export function truncateText(text: string, maxLength: number): string {
|
|
31
|
-
if (text.length <= maxLength) {
|
|
32
|
-
return text
|
|
33
|
-
}
|
|
34
|
-
return text.slice(0, maxLength) + '...'
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ID generation utility
|
|
38
|
-
export function generateId(length: number = 8): string {
|
|
39
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
40
|
-
let result = ''
|
|
41
|
-
for (let i = 0; i < length; i++) {
|
|
42
|
-
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
43
|
-
}
|
|
44
|
-
return result
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Get client IP address from request
|
|
48
|
-
export function getClientIP(request: Request): string {
|
|
49
|
-
// Check various headers for IP address
|
|
50
|
-
const forwarded = request.headers.get('x-forwarded-for')
|
|
51
|
-
const realIP = request.headers.get('x-real-ip')
|
|
52
|
-
const remoteAddr = request.headers.get('x-remote-addr')
|
|
53
|
-
|
|
54
|
-
if (forwarded) {
|
|
55
|
-
// x-forwarded-for can contain multiple IPs, take the first one
|
|
56
|
-
return forwarded.split(',')[0].trim()
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (realIP) {
|
|
60
|
-
return realIP
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (remoteAddr) {
|
|
64
|
-
return remoteAddr
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Fallback to a default IP if none found
|
|
68
|
-
return '127.0.0.1'
|
|
69
|
-
}
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
// Locked Component UI
|
|
2
|
-
// Pro componentlerin locked state'ini gösteren wrapper component
|
|
3
|
-
|
|
4
|
-
import React, { useState } from 'react'
|
|
5
|
-
import { cn } from '../../lib/utils'
|
|
6
|
-
import { Button } from './button'
|
|
7
|
-
import { Badge } from './badge'
|
|
8
|
-
import { Lock, Crown, Zap } from 'lucide-react'
|
|
9
|
-
import Link from 'next/link'
|
|
10
|
-
import { useComponentAccess } from '../../use-pro-access'
|
|
11
|
-
|
|
12
|
-
interface MoonUILockedComponentProps {
|
|
13
|
-
children: React.ReactNode
|
|
14
|
-
componentName: string
|
|
15
|
-
className?: string
|
|
16
|
-
showPreview?: boolean
|
|
17
|
-
previewDuration?: number // ms
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function LockedComponent({
|
|
21
|
-
children,
|
|
22
|
-
componentName,
|
|
23
|
-
className,
|
|
24
|
-
showPreview = true,
|
|
25
|
-
previewDuration = 2000
|
|
26
|
-
}: LockedComponentProps) {
|
|
27
|
-
const [isPreviewActive, setIsPreviewActive] = useState(false)
|
|
28
|
-
const [previewTimeout, setPreviewTimeout] = useState<NodeJS.Timeout | null>(null)
|
|
29
|
-
|
|
30
|
-
const handleMouseEnter = () => {
|
|
31
|
-
if (!showPreview) return
|
|
32
|
-
|
|
33
|
-
setIsPreviewActive(true)
|
|
34
|
-
|
|
35
|
-
// Clear existing timeout
|
|
36
|
-
if (previewTimeout) {
|
|
37
|
-
clearTimeout(previewTimeout)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Set new timeout
|
|
41
|
-
const timeout = setTimeout(() => {
|
|
42
|
-
setIsPreviewActive(false)
|
|
43
|
-
}, previewDuration)
|
|
44
|
-
|
|
45
|
-
setPreviewTimeout(timeout)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const handleMouseLeave = () => {
|
|
49
|
-
if (previewTimeout) {
|
|
50
|
-
clearTimeout(previewTimeout)
|
|
51
|
-
setPreviewTimeout(null)
|
|
52
|
-
}
|
|
53
|
-
setIsPreviewActive(false)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<div
|
|
58
|
-
className={cn(
|
|
59
|
-
"relative group cursor-pointer transition-all duration-300",
|
|
60
|
-
className
|
|
61
|
-
)}
|
|
62
|
-
onMouseEnter={handleMouseEnter}
|
|
63
|
-
onMouseLeave={handleMouseLeave}
|
|
64
|
-
>
|
|
65
|
-
{/* Component Content */}
|
|
66
|
-
<div
|
|
67
|
-
className={cn(
|
|
68
|
-
"transition-all duration-300",
|
|
69
|
-
!isPreviewActive && "blur-sm grayscale-[0.3] opacity-60"
|
|
70
|
-
)}
|
|
71
|
-
>
|
|
72
|
-
{children}
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
{/* Overlay */}
|
|
76
|
-
<div
|
|
77
|
-
className={cn(
|
|
78
|
-
"absolute inset-0 bg-gradient-to-br from-amber-500/10 via-transparent to-purple-500/10",
|
|
79
|
-
"border-2 border-dashed border-amber-400/30 rounded-lg",
|
|
80
|
-
"flex items-center justify-center",
|
|
81
|
-
"transition-all duration-300",
|
|
82
|
-
isPreviewActive ? "opacity-0" : "opacity-100"
|
|
83
|
-
)}
|
|
84
|
-
>
|
|
85
|
-
<div className="text-center space-y-3 p-6">
|
|
86
|
-
{/* Pro Badge */}
|
|
87
|
-
<div className="flex justify-center">
|
|
88
|
-
<Badge
|
|
89
|
-
variant="secondary"
|
|
90
|
-
className="bg-gradient-to-r from-amber-500 to-orange-500 text-white border-0 px-3 py-1"
|
|
91
|
-
>
|
|
92
|
-
<Crown className="w-3 h-3 mr-1" />
|
|
93
|
-
PRO ONLY
|
|
94
|
-
</Badge>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
{/* Lock Icon */}
|
|
98
|
-
<div className="flex justify-center">
|
|
99
|
-
<div className="p-3 rounded-full bg-amber-100 dark:bg-amber-900/30">
|
|
100
|
-
<Lock className="w-6 h-6 text-amber-600 dark:text-amber-400" />
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
|
|
104
|
-
{/* Component Name */}
|
|
105
|
-
<h3 className="font-semibold text-gray-900 dark:text-gray-100">
|
|
106
|
-
{componentName}
|
|
107
|
-
</h3>
|
|
108
|
-
|
|
109
|
-
{/* Description */}
|
|
110
|
-
<p className="text-sm text-gray-600 dark:text-gray-400 max-w-xs">
|
|
111
|
-
This premium component is available with Pro subscription
|
|
112
|
-
</p>
|
|
113
|
-
|
|
114
|
-
{/* Upgrade Button */}
|
|
115
|
-
<Link href="/pricing">
|
|
116
|
-
<Button
|
|
117
|
-
size="sm"
|
|
118
|
-
className="bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-600 hover:to-orange-600 border-0"
|
|
119
|
-
>
|
|
120
|
-
<Zap className="w-4 h-4 mr-2" />
|
|
121
|
-
Upgrade to Pro
|
|
122
|
-
</Button>
|
|
123
|
-
</Link>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
|
|
127
|
-
{/* Preview Hint */}
|
|
128
|
-
{showPreview && !isPreviewActive && (
|
|
129
|
-
<div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
|
130
|
-
<div className="bg-black/80 text-white text-xs px-2 py-1 rounded">
|
|
131
|
-
Hover to preview
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
)}
|
|
135
|
-
</div>
|
|
136
|
-
)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Pro Component Wrapper - Otomatik erişim kontrolü ile
|
|
140
|
-
interface ProComponentWrapperProps {
|
|
141
|
-
children: React.ReactNode
|
|
142
|
-
componentId: string
|
|
143
|
-
componentName?: string
|
|
144
|
-
className?: string
|
|
145
|
-
fallback?: React.ReactNode
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function ProComponentWrapper({
|
|
149
|
-
children,
|
|
150
|
-
componentId,
|
|
151
|
-
componentName,
|
|
152
|
-
className,
|
|
153
|
-
fallback
|
|
154
|
-
}: ProComponentWrapperProps) {
|
|
155
|
-
const { canUse, isLocked, isLoading } = useComponentAccess(componentId)
|
|
156
|
-
|
|
157
|
-
// Loading state
|
|
158
|
-
if (isLoading) {
|
|
159
|
-
return (
|
|
160
|
-
<div className={cn("animate-pulse bg-gray-200 dark:bg-gray-800 rounded-lg h-32", className)}>
|
|
161
|
-
<div className="flex items-center justify-center h-full">
|
|
162
|
-
<div className="text-sm text-gray-500">Loading...</div>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Unlocked - show actual component
|
|
169
|
-
if (canUse) {
|
|
170
|
-
return <div className={className}>{children}</div>
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Locked - show locked state
|
|
174
|
-
if (isLocked) {
|
|
175
|
-
return (
|
|
176
|
-
<LockedComponent
|
|
177
|
-
componentName={componentName || componentId}
|
|
178
|
-
className={className}
|
|
179
|
-
>
|
|
180
|
-
{fallback || children}
|
|
181
|
-
</LockedComponent>
|
|
182
|
-
)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Default fallback
|
|
186
|
-
return <div className={className}>{children}</div>
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Pro Badge Component
|
|
190
|
-
export function ProBadge({ className }: { className?: string }) {
|
|
191
|
-
return (
|
|
192
|
-
<Badge
|
|
193
|
-
variant="secondary"
|
|
194
|
-
className={cn(
|
|
195
|
-
"bg-gradient-to-r from-amber-500 to-orange-500 text-white border-0 text-xs",
|
|
196
|
-
className
|
|
197
|
-
)}
|
|
198
|
-
>
|
|
199
|
-
<Crown className="w-3 h-3 mr-1" />
|
|
200
|
-
PRO
|
|
201
|
-
</Badge>
|
|
202
|
-
)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Free Badge Component
|
|
206
|
-
export function FreeBadge({ className }: { className?: string }) {
|
|
207
|
-
return (
|
|
208
|
-
<Badge
|
|
209
|
-
variant="outline"
|
|
210
|
-
className={cn("border-green-500 text-green-600 dark:text-green-400 text-xs", className)}
|
|
211
|
-
>
|
|
212
|
-
FREE
|
|
213
|
-
</Badge>
|
|
214
|
-
)
|
|
215
|
-
}
|
package/src/use-pro-access.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
// Pro Access Hook
|
|
2
|
-
// Kullanıcının pro componentlere erişim durumunu kontrol eder
|
|
3
|
-
|
|
4
|
-
import React from 'react'
|
|
5
|
-
import { useSession } from 'next-auth/react'
|
|
6
|
-
import { useQuery } from '@tanstack/react-query'
|
|
7
|
-
import { getComponentAccess } from '@/lib/component-metadata'
|
|
8
|
-
|
|
9
|
-
export interface UserSubscription {
|
|
10
|
-
userId: string
|
|
11
|
-
plan: 'free' | 'pro_monthly' | 'pro_annual' | 'pro_lifetime'
|
|
12
|
-
status: 'active' | 'expired' | 'cancelled' | 'trial'
|
|
13
|
-
expiresAt: Date | null // lifetime için null
|
|
14
|
-
createdAt: Date
|
|
15
|
-
updatedAt: Date
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface ProAccessStatus {
|
|
19
|
-
hasProAccess: boolean
|
|
20
|
-
subscription: UserSubscription | null | undefined
|
|
21
|
-
isLoading: boolean
|
|
22
|
-
error: Error | null
|
|
23
|
-
daysUntilExpiry: number | null
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Subscription durumunu API'den çek
|
|
27
|
-
async function fetchSubscriptionStatus(): Promise<UserSubscription | null> {
|
|
28
|
-
const response = await fetch('/api/user/subscription-status')
|
|
29
|
-
|
|
30
|
-
if (!response.ok) {
|
|
31
|
-
throw new Error('Subscription status fetch failed')
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const data = await response.json()
|
|
35
|
-
return data.subscription
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Pro erişim durumunu hesapla
|
|
39
|
-
function calculateProAccess(subscription: UserSubscription | null | undefined): {
|
|
40
|
-
hasProAccess: boolean
|
|
41
|
-
daysUntilExpiry: number | null
|
|
42
|
-
} {
|
|
43
|
-
if (!subscription) {
|
|
44
|
-
return { hasProAccess: false, daysUntilExpiry: null }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Lifetime üyelik
|
|
48
|
-
if (subscription.plan === 'pro_lifetime') {
|
|
49
|
-
return { hasProAccess: true, daysUntilExpiry: null }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Aktif olmayan subscription
|
|
53
|
-
if (subscription.status !== 'active') {
|
|
54
|
-
return { hasProAccess: false, daysUntilExpiry: null }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Süre kontrolü
|
|
58
|
-
if (subscription.expiresAt) {
|
|
59
|
-
const now = new Date()
|
|
60
|
-
const expiryDate = new Date(subscription.expiresAt)
|
|
61
|
-
const daysUntilExpiry = Math.ceil((expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
hasProAccess: daysUntilExpiry > 0,
|
|
65
|
-
daysUntilExpiry: daysUntilExpiry > 0 ? daysUntilExpiry : 0
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return { hasProAccess: false, daysUntilExpiry: null }
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function useProAccess(): ProAccessStatus {
|
|
73
|
-
const { data: session, status } = useSession()
|
|
74
|
-
|
|
75
|
-
const {
|
|
76
|
-
data: subscription,
|
|
77
|
-
isLoading,
|
|
78
|
-
error
|
|
79
|
-
} = useQuery({
|
|
80
|
-
queryKey: ['subscription-status', session?.user?.id],
|
|
81
|
-
queryFn: fetchSubscriptionStatus,
|
|
82
|
-
enabled: !!session?.user?.id && status === 'authenticated',
|
|
83
|
-
staleTime: 5 * 60 * 1000, // 5 dakika cache
|
|
84
|
-
refetchInterval: 10 * 60 * 1000, // 10 dakikada bir otomatik refresh
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const { hasProAccess, daysUntilExpiry } = calculateProAccess(subscription)
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
hasProAccess,
|
|
91
|
-
subscription,
|
|
92
|
-
isLoading: status === 'loading' || isLoading,
|
|
93
|
-
error: error as Error | null,
|
|
94
|
-
daysUntilExpiry
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Component erişim kontrolü için helper hook
|
|
99
|
-
export function useComponentAccess(componentId: string) {
|
|
100
|
-
const { hasProAccess, isLoading } = useProAccess()
|
|
101
|
-
|
|
102
|
-
const access = React.useMemo(() => {
|
|
103
|
-
return getComponentAccess(componentId, hasProAccess ? 'pro' : 'free')
|
|
104
|
-
}, [componentId, hasProAccess])
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
...access,
|
|
108
|
-
isLoading,
|
|
109
|
-
canUse: access.access === 'unlocked',
|
|
110
|
-
isLocked: access.access === 'locked'
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Subscription durumu için utility functions
|
|
115
|
-
export function getSubscriptionDisplayName(plan: string): string {
|
|
116
|
-
switch (plan) {
|
|
117
|
-
case 'pro_monthly':
|
|
118
|
-
return 'Pro Monthly'
|
|
119
|
-
case 'pro_annual':
|
|
120
|
-
return 'Pro Annual'
|
|
121
|
-
case 'pro_lifetime':
|
|
122
|
-
return 'Pro Lifetime'
|
|
123
|
-
default:
|
|
124
|
-
return 'Free'
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function getSubscriptionColor(status: string): string {
|
|
129
|
-
switch (status) {
|
|
130
|
-
case 'active':
|
|
131
|
-
return 'green'
|
|
132
|
-
case 'trial':
|
|
133
|
-
return 'blue'
|
|
134
|
-
case 'expired':
|
|
135
|
-
return 'red'
|
|
136
|
-
case 'cancelled':
|
|
137
|
-
return 'gray'
|
|
138
|
-
default:
|
|
139
|
-
return 'gray'
|
|
140
|
-
}
|
|
141
|
-
}
|