@moontra/moonui-pro 2.18.0 → 2.18.2

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-pro",
3
- "version": "2.18.0",
3
+ "version": "2.18.2",
4
4
  "description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -54,17 +54,32 @@ export function useGitHubData({
54
54
 
55
55
  const refreshTimeoutRef = useRef<NodeJS.Timeout>()
56
56
  const previousStarsRef = useRef<Map<string, number>>(new Map())
57
+ const errorCountRef = useRef<number>(0) // Hata sayısını takip et
58
+ const maxErrorCount = 2 // Maksimum hata sayısı
59
+
60
+ // checkMilestones fonksiyonunu ref olarak sakla - bu şekilde her render'da yeniden oluşturulmaz
61
+ const milestonesRef = useRef(milestones)
62
+ const onMilestoneReachedRef = useRef(onMilestoneReached)
63
+
64
+ // Ref'leri güncelle
65
+ useEffect(() => {
66
+ milestonesRef.current = milestones
67
+ }, [milestones])
68
+
69
+ useEffect(() => {
70
+ onMilestoneReachedRef.current = onMilestoneReached
71
+ }, [onMilestoneReached])
57
72
 
58
73
  const checkMilestones = useCallback((repos: GitHubRepository[]) => {
59
- if (!onMilestoneReached) return
74
+ if (!onMilestoneReachedRef.current) return
60
75
 
61
76
  repos.forEach(repo => {
62
77
  const previousStars = previousStarsRef.current.get(repo.full_name) || 0
63
78
  const currentStars = repo.stargazers_count
64
79
 
65
- milestones.forEach(milestone => {
80
+ milestonesRef.current.forEach(milestone => {
66
81
  if (previousStars < milestone && currentStars >= milestone) {
67
- onMilestoneReached({
82
+ onMilestoneReachedRef.current?.({
68
83
  count: milestone,
69
84
  reached: true,
70
85
  date: new Date().toISOString(),
@@ -75,9 +90,33 @@ export function useGitHubData({
75
90
 
76
91
  previousStarsRef.current.set(repo.full_name, currentStars)
77
92
  })
78
- }, [milestones, onMilestoneReached])
93
+ }, []) // Boş dependency array - fonksiyon asla yeniden oluşturulmaz
79
94
 
80
95
  const fetchData = useCallback(async () => {
96
+ // Hata limiti aşıldıysa istek yapma
97
+ if (errorCountRef.current >= maxErrorCount) {
98
+ console.warn("Maximum error count reached. Stopping requests.")
99
+ setLoading(false)
100
+ setError("Maximum retry limit exceeded. Please check your configuration.")
101
+ return
102
+ }
103
+
104
+ // Gerekli bilgiler yoksa istek yapma
105
+ const hasValidInput =
106
+ (username && repository) || // Tek repository modu
107
+ (username && repositories && repositories.length > 0) || // Çoklu repository modu
108
+ (repositories && repositories.length > 0 && repositories.every(r => r.includes('/'))) || // Full path repositories
109
+ username // Kullanıcının tüm repositoryleri
110
+
111
+ if (!hasValidInput) {
112
+ console.warn("No valid input provided. Skipping API request.")
113
+ setLoading(false)
114
+ setError(null)
115
+ setRepos([])
116
+ setStats(null)
117
+ return
118
+ }
119
+
81
120
  try {
82
121
  setLoading(true)
83
122
  setError(null)
@@ -101,7 +140,15 @@ export function useGitHubData({
101
140
  const repo = await fetchRepository(username, repository, token)
102
141
  fetchedRepos = [repo]
103
142
  }
104
- // Multiple specific repositories
143
+ // Multiple specific repositories with full paths
144
+ else if (repositories && repositories.length > 0 && repositories.every(r => r.includes('/'))) {
145
+ const repoPromises = repositories.map(fullPath => {
146
+ const [owner, name] = fullPath.split('/')
147
+ return fetchRepository(owner, name, token)
148
+ })
149
+ fetchedRepos = await Promise.all(repoPromises)
150
+ }
151
+ // Multiple specific repositories with username
105
152
  else if (repositories && repositories.length > 0 && username) {
106
153
  const repoPromises = repositories.map(repoName =>
107
154
  fetchRepository(username, repoName, token)
@@ -173,9 +220,16 @@ export function useGitHubData({
173
220
  }
174
221
 
175
222
  setLastUpdated(new Date())
223
+ // Başarılı olduğunda hata sayacını sıfırla
224
+ errorCountRef.current = 0
176
225
  } catch (err) {
177
226
  const errorMessage = err instanceof Error ? err.message : "Failed to fetch data"
178
227
  setError(errorMessage)
228
+
229
+ // Hata sayacını artır
230
+ errorCountRef.current += 1
231
+ console.error(`GitHub API error (${errorCountRef.current}/${maxErrorCount}):`, errorMessage)
232
+
179
233
  if (onError) {
180
234
  onError(err instanceof Error ? err : new Error(errorMessage))
181
235
  }
@@ -185,18 +239,29 @@ export function useGitHubData({
185
239
  }, [
186
240
  username,
187
241
  repository,
188
- repositories,
242
+ repositories?.join(','), // Array'i string'e çevir ki referans değişmesin
189
243
  token,
190
244
  sortBy,
191
245
  maxItems,
192
- checkMilestones,
246
+ checkMilestones, // Artık stable
193
247
  onDataUpdate,
194
248
  onError,
195
249
  ])
196
250
 
197
251
  // Initial fetch
198
252
  useEffect(() => {
199
- fetchData()
253
+ // Sadece geçerli input varsa fetch yap
254
+ const hasValidInput =
255
+ (username && repository) ||
256
+ (username && repositories && repositories.length > 0) ||
257
+ (repositories && repositories.length > 0 && repositories.every(r => r.includes('/'))) ||
258
+ username
259
+
260
+ if (hasValidInput) {
261
+ fetchData()
262
+ } else {
263
+ setLoading(false)
264
+ }
200
265
  }, [fetchData])
201
266
 
202
267
  // Auto-refresh
@@ -220,6 +285,12 @@ export function useGitHubData({
220
285
  }, [autoRefresh, refreshInterval, fetchData])
221
286
 
222
287
  const refresh = useCallback(() => {
288
+ // Hata limiti aşıldıysa refresh yapma
289
+ if (errorCountRef.current >= maxErrorCount) {
290
+ console.warn("Cannot refresh: maximum error count reached")
291
+ return Promise.resolve()
292
+ }
293
+
223
294
  clearCache()
224
295
  return fetchData()
225
296
  }, [fetchData])
@@ -62,6 +62,27 @@ const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
62
62
  }) => {
63
63
  const { notify } = useGitHubNotifications(enableNotifications)
64
64
 
65
+ // onMilestoneReached callback'ini memoize et
66
+ const handleMilestoneReached = React.useCallback((milestone: any) => {
67
+ // Handle milestone reached
68
+ if (celebrateAt?.includes(milestone.count)) {
69
+ // Trigger celebration animation
70
+ confetti({
71
+ particleCount: 100,
72
+ spread: 70,
73
+ origin: { y: 0.6 },
74
+ })
75
+
76
+ // Show notification
77
+ notify(`🎉 Milestone Reached!`, {
78
+ body: `${milestone.count} stars achieved!`,
79
+ tag: `milestone-${milestone.count}`,
80
+ })
81
+ }
82
+
83
+ onMilestoneReached?.(milestone)
84
+ }, [celebrateAt, notify, onMilestoneReached])
85
+
65
86
  const {
66
87
  repos,
67
88
  stats,
@@ -81,29 +102,11 @@ const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
81
102
  maxItems,
82
103
  onError,
83
104
  onDataUpdate,
84
- onMilestoneReached: (milestone) => {
85
- // Handle milestone reached
86
- if (celebrateAt?.includes(milestone.count)) {
87
- // Trigger celebration animation
88
- confetti({
89
- particleCount: 100,
90
- spread: 70,
91
- origin: { y: 0.6 },
92
- })
93
-
94
- // Show notification
95
- notify(`🎉 Milestone Reached!`, {
96
- body: `${milestone.count} stars achieved!`,
97
- tag: `milestone-${milestone.count}`,
98
- })
99
- }
100
-
101
- onMilestoneReached?.(milestone)
102
- },
105
+ onMilestoneReached: handleMilestoneReached,
103
106
  milestones,
104
107
  })
105
108
 
106
- const handleExport = (format: "json" | "csv") => {
109
+ const handleExport = React.useCallback((format: "json" | "csv") => {
107
110
  if (!enableExport) return
108
111
 
109
112
  const timestamp = new Date().toISOString().split("T")[0]
@@ -116,7 +119,47 @@ const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
116
119
  } else {
117
120
  exportAsCSV(repos, `github-stars-${username}-${timestamp}.csv`)
118
121
  }
119
- }
122
+ }, [enableExport, repos, stats, username])
123
+
124
+ // handleDetailedExport fonksiyonunu memoize et
125
+ const handleDetailedExport = React.useCallback((e: React.MouseEvent) => {
126
+ if (!enableExport) return
127
+
128
+ // Show export dropdown
129
+ const dropdown = document.createElement("div")
130
+ dropdown.className = "absolute z-50 mt-2 w-48 rounded-md shadow-lg bg-popover border"
131
+ dropdown.innerHTML = `
132
+ <div class="py-1">
133
+ <button class="w-full text-left px-4 py-2 text-sm hover:bg-accent" data-format="json">
134
+ Export as JSON
135
+ </button>
136
+ <button class="w-full text-left px-4 py-2 text-sm hover:bg-accent" data-format="csv">
137
+ Export as CSV
138
+ </button>
139
+ </div>
140
+ `
141
+
142
+ dropdown.addEventListener("click", (e) => {
143
+ const target = e.target as HTMLElement
144
+ const format = target.getAttribute("data-format")
145
+ if (format === "json" || format === "csv") {
146
+ handleExport(format)
147
+ dropdown.remove()
148
+ }
149
+ })
150
+
151
+ document.body.appendChild(dropdown)
152
+
153
+ // Position dropdown
154
+ const rect = (e.target as HTMLElement).getBoundingClientRect()
155
+ dropdown.style.top = `${rect.bottom + window.scrollY}px`
156
+ dropdown.style.left = `${rect.left + window.scrollX}px`
157
+
158
+ // Remove on outside click
159
+ setTimeout(() => {
160
+ document.addEventListener("click", () => dropdown.remove(), { once: true })
161
+ }, 0)
162
+ }, [enableExport, handleExport])
120
163
 
121
164
  // Loading state
122
165
  if (loading) {
@@ -201,42 +244,7 @@ const GitHubStarsInternal: React.FC<GitHubStarsProps> = ({
201
244
  return (
202
245
  <DetailedVariant
203
246
  {...baseProps}
204
- onExport={() => {
205
- // Show export dropdown
206
- const dropdown = document.createElement("div")
207
- dropdown.className = "absolute z-50 mt-2 w-48 rounded-md shadow-lg bg-popover border"
208
- dropdown.innerHTML = `
209
- <div class="py-1">
210
- <button class="w-full text-left px-4 py-2 text-sm hover:bg-accent" data-format="json">
211
- Export as JSON
212
- </button>
213
- <button class="w-full text-left px-4 py-2 text-sm hover:bg-accent" data-format="csv">
214
- Export as CSV
215
- </button>
216
- </div>
217
- `
218
-
219
- dropdown.addEventListener("click", (e) => {
220
- const target = e.target as HTMLElement
221
- const format = target.getAttribute("data-format")
222
- if (format === "json" || format === "csv") {
223
- handleExport(format)
224
- dropdown.remove()
225
- }
226
- })
227
-
228
- document.body.appendChild(dropdown)
229
-
230
- // Position dropdown
231
- const rect = (e.target as HTMLElement).getBoundingClientRect()
232
- dropdown.style.top = `${rect.bottom + window.scrollY}px`
233
- dropdown.style.left = `${rect.left + window.scrollX}px`
234
-
235
- // Remove on outside click
236
- setTimeout(() => {
237
- document.addEventListener("click", () => dropdown.remove(), { once: true })
238
- }, 0)
239
- }}
247
+ onExport={handleDetailedExport}
240
248
  />
241
249
  )
242
250
  case "card":