-
Notifications
You must be signed in to change notification settings - Fork 10
fix: resolve all 6 open issues (#41-#46) #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9e34a73
8966f9e
04f32f2
9fb2f78
0098984
d0aaf61
7fb0352
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| User-agent: * | ||
| Allow: /* | ||
| Disallow: /api/ | ||
| Disallow: /login | ||
| Disallow: /settings | ||
| Sitemap: https://bittorrented.com/sitemap.xml |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -224,15 +224,15 @@ function NavSection({ items, pathname, onItemClick, isLoggedIn, isPremium }: Nav | |
| {items.map((item) => { | ||
| const isActive = pathname === item.href; | ||
| const Icon = item.icon; | ||
| // Redirect to login if auth-required and not logged in, or paid feature and not premium | ||
| // Show lock icon for auth-required items when not logged in | ||
| const needsLogin = (item.requiresAuth && !isLoggedIn) || (item.requiresPaid && !isPremium); | ||
| const href = needsLogin ? '/login' : item.href; | ||
|
|
||
| return ( | ||
| <li key={item.href}> | ||
| <Link | ||
| href={href} | ||
| href={needsLogin ? '/login' : item.href} | ||
| onClick={onItemClick} | ||
| title={needsLogin ? 'Login required' : undefined} | ||
| className={cn( | ||
|
Comment on lines
+227
to
236
|
||
| 'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors', | ||
| isActive | ||
|
|
@@ -242,6 +242,7 @@ function NavSection({ items, pathname, onItemClick, isLoggedIn, isPremium }: Nav | |
| > | ||
| <Icon size={20} className={isActive ? 'text-accent-primary' : ''} /> | ||
| <span>{item.label}</span> | ||
| {needsLogin ? <span className="ml-auto text-xs opacity-50" title="Login required">🔒</span> : null} | ||
| {item.badge ? <span className="ml-auto rounded-full bg-accent-primary px-2 py-0.5 text-xs font-medium text-white"> | ||
| {item.badge} | ||
| </span> : null} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,7 +55,7 @@ describe('useSupportedCoins', () => { | |
|
|
||
| expect(result.current.coins).toEqual(mockCoins); | ||
| expect(result.current.error).toBeNull(); | ||
| expect(mockFetch).toHaveBeenCalledWith('/api/supported-coins?active_only=true'); | ||
| expect(mockFetch).toHaveBeenCalledWith('/api/supported-coins?active_only=true', expect.objectContaining({ signal: expect.any(AbortSignal) })); | ||
|
||
| }); | ||
|
|
||
| it('should filter active coins by default', async () => { | ||
|
|
@@ -72,7 +72,7 @@ describe('useSupportedCoins', () => { | |
| renderHook(() => useSupportedCoins()); | ||
|
|
||
| await waitFor(() => { | ||
| expect(mockFetch).toHaveBeenCalledWith('/api/supported-coins?active_only=true'); | ||
| expect(mockFetch).toHaveBeenCalledWith('/api/supported-coins?active_only=true', expect.objectContaining({ signal: expect.any(AbortSignal) })); | ||
| }); | ||
| }); | ||
|
|
||
|
|
@@ -90,7 +90,7 @@ describe('useSupportedCoins', () => { | |
| renderHook(() => useSupportedCoins({ activeOnly: false })); | ||
|
|
||
| await waitFor(() => { | ||
| expect(mockFetch).toHaveBeenCalledWith('/api/supported-coins?active_only=false'); | ||
| expect(mockFetch).toHaveBeenCalledWith('/api/supported-coins?active_only=false', expect.objectContaining({ signal: expect.any(AbortSignal) })); | ||
| }); | ||
| }); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -63,9 +63,14 @@ export function useSupportedCoins( | |
| setError(null); | ||
|
|
||
| try { | ||
| const controller = new AbortController(); | ||
| const timeoutId = setTimeout(() => controller.abort(), 10000); | ||
|
|
||
| const response = await fetch( | ||
| `/api/supported-coins?active_only=${activeOnly}` | ||
| `/api/supported-coins?active_only=${activeOnly}`, | ||
| { signal: controller.signal } | ||
| ); | ||
| clearTimeout(timeoutId); | ||
|
Comment on lines
65
to
+73
|
||
|
|
||
| const data = (await response.json()) as | ||
| | SupportedCoinsResponse | ||
|
|
@@ -79,7 +84,10 @@ export function useSupportedCoins( | |
| const successData = data as SupportedCoinsResponse; | ||
| setCoins(successData.coins); | ||
| } catch (err) { | ||
| setError(err instanceof Error ? err.message : 'Failed to fetch supported coins'); | ||
| const message = err instanceof Error && err.name === 'AbortError' | ||
| ? 'Payment methods unavailable. Please try again later.' | ||
| : err instanceof Error ? err.message : 'Failed to fetch supported coins'; | ||
| setError(message); | ||
| setCoins([]); | ||
| } finally { | ||
| setIsLoading(false); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because
MainLayoutis a client component, renderingnew Date().getFullYear()can cause a rare hydration mismatch around New Year (server-rendered year vs client-rendered year). To avoid this, consider hardcoding the year, computing it on the server, or usingsuppressHydrationWarningon the year span.