Conversation
|
Caution Review failedThe pull request is closed. WalkthroughThe pull request introduces several changes primarily focused on the routing and component structure of the application. The Changes
Possibly related PRs
Suggested reviewers
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Outside diff range and nitpick comments (4)
src/components/LanguageDropdown.tsx (1)
12-32: Consider these improvements for better maintainability and user experience.
- Extract language options to a constant:
const LANGUAGE_OPTIONS = [ { code: 'spanish', label: 'Español' }, { code: 'english', label: 'English' } ] as const;
- Use an icon component for the checkmark for consistency with the design system.
- Consider using theme-aware hover colors instead of hardcoded values.
Here's a suggested implementation:
- <DropdownMenuContent align='end' className='w-48'> + <DropdownMenuContent align='end' className='w-fit min-w-[12rem]'> {LANGUAGE_OPTIONS.map(({ code, label }) => ( <DropdownMenuItem key={code} className='cursor-pointer' onClick={() => setLanguage(code)} > <span>{label}</span> - {language === code && <span className='ml-2'>✓</span>} + {language === code && <Check className='ml-2 h-4 w-4' />} </DropdownMenuItem> ))} </DropdownMenuContent>Also, consider updating the button hover style:
- <Button variant='ghost' size='icon' className='h-8 w-8 p-0 hover:bg-gray-50'> + <Button variant='ghost' size='icon' className='h-8 w-8 p-0 hover:bg-accent'>src/hooks/useLandingPageContent.ts (2)
1-2: Fix the import statement extension.Add the
.tsextension to the supabase import to comply with ESLint rules.-import supabase from '@/lib/supabase' +import supabase from '@/lib/supabase.ts'🧰 Tools
🪛 eslint
[error] 2-2: Missing file extension for "@/lib/supabase"
(import/extensions)
4-8: Strengthen type safety for ContentRow interface.The
keyproperty should be constrained to valid keys ofLandingPageContent.interface ContentRow { - key: string + key: keyof LandingPageContent content_en: string content_es: string }src/components/LandingPageCarousel.tsx (1)
94-121: Enhance accessibility features.The carousel needs additional accessibility improvements:
- Add ARIA live region for dynamic content
- Include controls for manual navigation
- Add keyboard navigation support
return ( - <Card className='h-[400px] overflow-hidden border-white/20 bg-white/10 p-4 backdrop-blur-sm' ref={containerRef}> + <Card + className='h-[400px] overflow-hidden border-white/20 bg-white/10 p-4 backdrop-blur-sm' + ref={containerRef} + onMouseEnter={() => setIsPaused(true)} + onMouseLeave={() => setIsPaused(false)} + role="region" + aria-label="News carousel" + > + <div className="mb-2 flex justify-end"> + <Button + onClick={() => setIsPaused(!isPaused)} + aria-label={isPaused ? "Resume auto-scroll" : "Pause auto-scroll"} + > + {isPaused ? "Play" : "Pause"} + </Button> + </div> <div ref={scrollRef} className='transition-transform duration-1000 ease-linear'>🧰 Tools
🪛 eslint
[error] 98-98: Do not use Array index in keys
(react/no-array-index-key)
[error] 112-112: Do not use Array index in keys
(react/no-array-index-key)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (8)
src/App.tsx(3 hunks)src/components/HeaderBar.tsx(2 hunks)src/components/LandingPageCarousel.tsx(1 hunks)src/components/LanguageDropdown.tsx(1 hunks)src/constants/translations.ts(2 hunks)src/hooks/useLandingPageContent.ts(1 hunks)src/pages/Landing.tsx(1 hunks)src/utils/language.ts(1 hunks)
🧰 Additional context used
🪛 eslint
src/components/HeaderBar.tsx
[error] 6-6: Missing file extension for "@/components/ui/button"
(import/extensions)
[error] 8-8: Missing file extension for "@/providers/auth"
(import/extensions)
[error] 9-9: Missing file extension for "@/components/ui/dropdown-menu"
(import/extensions)
[error] 10-10: Missing file extension for "@/lib/supabase"
(import/extensions)
[error] 12-12: Missing file extension for "@/constants/translations"
(import/extensions)
[error] 16-16: Unsafe array destructuring of a tuple element with an any value.
(@typescript-eslint/no-unsafe-assignment)
src/components/LandingPageCarousel.tsx
[error] 3-3: 'Link' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 5-5: Missing file extension for "@/components/ui/button"
(import/extensions)
[error] 6-6: Missing file extension for "@/components/ui/card"
(import/extensions)
[error] 7-7: Missing file extension for "@/components/ui/badge"
(import/extensions)
[error] 8-8: 'useLanguage' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 8-8: Missing file extension for "@/providers/language"
(import/extensions)
[error] 9-9: 'translations' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 9-9: Missing file extension for "@/constants/translations"
(import/extensions)
[error] 10-10: Expected 1 empty line after import statement not followed by another import.
(import/newline-after-import)
[error] 10-10: 'LanguageDropdown' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 10-10: Missing file extension for "@/components/LanguageDropdown"
(import/extensions)
[error] 98-98: Do not use Array index in keys
(react/no-array-index-key)
[error] 112-112: Do not use Array index in keys
(react/no-array-index-key)
src/components/LanguageDropdown.tsx
[error] 3-3: Missing file extension for "@/components/ui/button"
(import/extensions)
[error] 4-4: Missing file extension for "@/components/ui/dropdown-menu"
(import/extensions)
[error] 5-5: Missing file extension for "@/providers/language"
(import/extensions)
[error] 6-6: Missing file extension for "@/constants/translations"
(import/extensions)
src/hooks/useLandingPageContent.ts
[error] 2-2: Missing file extension for "@/lib/supabase"
(import/extensions)
[error] 17-17: Unsafe assignment of an any value.
(@typescript-eslint/no-unsafe-assignment)
[error] 17-17: Unsafe call of an any typed value.
(@typescript-eslint/no-unsafe-call)
[error] 17-17: Unsafe member access .rpc on an any value.
(@typescript-eslint/no-unsafe-member-access)
[error] 20-20: Unsafe member access .message on an any value.
(@typescript-eslint/no-unsafe-member-access)
[error] 49-52: Unsafe return of an any typed value.
(@typescript-eslint/no-unsafe-return)
[error] 49-49: Unsafe call of an any typed value.
(@typescript-eslint/no-unsafe-call)
[error] 51-51: Functions that return promises must be async.
(@typescript-eslint/promise-function-async)
src/pages/Landing.tsx
[error] 3-3: Missing file extension for "@/components/ui/button"
(import/extensions)
[error] 4-4: Missing file extension for "@/providers/language"
(import/extensions)
[error] 5-5: Missing file extension for "@/constants/translations"
(import/extensions)
[error] 6-6: Missing file extension for "@/components/LandingPageCarousel"
(import/extensions)
[error] 7-7: Missing file extension for "@/hooks/useLandingPageContent"
(import/extensions)
[error] 9-9: Missing file extension for "@/utils/language"
(import/extensions)
[error] 18-18: Expected '===' and instead saw '=='.
(eqeqeq)
[error] 21-21: Unsafe assignment of an any value.
(@typescript-eslint/no-unsafe-assignment)
[error] 22-22: Unsafe member access .isLoading on an any value.
(@typescript-eslint/no-unsafe-member-access)
[error] 45-45: Unsafe member access .data on an any value.
(@typescript-eslint/no-unsafe-member-access)
[error] 47-47: Unsafe member access .data on an any value.
(@typescript-eslint/no-unsafe-member-access)
[error] 67-67: Unsafe member access .data on an any value.
(@typescript-eslint/no-unsafe-member-access)
src/utils/language.ts
[error] 9-9: Prefer default export on a file with single export.
(import/prefer-default-export)
🪛 Biome
src/utils/language.ts
[error] 2-2: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (11)
src/utils/language.ts (1)
9-9: LGTM! Named export is preferred here.
While eslint suggests using default export, named exports are actually better in this case as they:
- Support better tree-shaking
- Make imports more explicit
- Allow easier addition of related exports in the future
🧰 Tools
🪛 eslint
[error] 9-9: Prefer default export on a file with single export.
(import/prefer-default-export)
src/components/LanguageDropdown.tsx (1)
8-10: LGTM! Clean and efficient hook usage.
The component follows React best practices with proper hook usage and translation management.
src/hooks/useLandingPageContent.ts (1)
17-17: Verify Supabase RPC call security.
The RPC call to get_latest_landing_page_content needs to be verified for proper access control and rate limiting.
✅ Verification successful
RPC call uses appropriate public client configuration
The get_latest_landing_page_content RPC is being called using the Supabase client configured with the anonymous (public) key, which is the appropriate security level for retrieving public landing page content. The client is properly initialized using environment variables and doesn't use any elevated service role permissions.
- The RPC call in
src/hooks/useLandingPageContent.tsuses the standard client configuration - The Supabase client in
src/lib/supabase.tsis correctly configured withVITE_SUPABASE_ANON_KEY - Error handling is in place to catch and report any issues with the RPC call
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for Supabase RPC security configurations
rg -A 5 "get_latest_landing_page_content" --type sql
Length of output: 201
Script:
#!/bin/bash
# Search for SQL files in the repository
fd -e sql
# Search for Supabase RPC configurations with broader context
rg -g '*.{sql,ts,js}' -A 10 "get_latest_landing_page_content"
# Look for Supabase configuration files
fd "supabase"
Length of output: 816
Script:
#!/bin/bash
# Check Supabase client configuration
cat src/lib/supabase.ts
# Look for any RPC or security related configurations
rg -g '*.{ts,js}' -B 5 -A 5 "createClient|supabaseKey|anon|service_role"
Length of output: 902
🧰 Tools
🪛 eslint
[error] 17-17: Unsafe assignment of an any value.
(@typescript-eslint/no-unsafe-assignment)
[error] 17-17: Unsafe call of an any typed value.
(@typescript-eslint/no-unsafe-call)
[error] 17-17: Unsafe member access .rpc on an any value.
(@typescript-eslint/no-unsafe-member-access)
src/App.tsx (2)
Line range hint 31-57: LGTM! Well-structured component hierarchy.
The changes maintain a clean and logical organization of providers and routes, with proper nesting of authenticated routes within AuthenticatedLayout.
48-48: Verify authentication flow with new landing page.
The default route now directs to LandingPage instead of LoginPage. While this aligns with the PR objectives, please ensure:
- Protected routes (
SEARCH_PATH,SNIPPET_DETAIL_PATH) remain secure - Users can still access the login page when needed
- Authenticated users are appropriately redirected from the landing page
✅ Verification successful
Protected routes remain secure with the new landing page configuration
The authentication flow is properly maintained:
- Protected routes (
SEARCH_PATH,SNIPPET_DETAIL_PATH) are wrapped inPrivateRoutecomponent which checks authentication status - Landing page provides clear navigation to login/signup through dedicated buttons
useAuthhook andAuthContextare properly implemented to manage authentication state
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for authentication guards in the landing page
rg -A 5 "isAuthenticated|useAuth" src/pages/Landing.tsx
# Check AuthenticatedLayout for protection logic
rg -A 5 "isAuthenticated|useAuth" src/layouts/AuthenticatedLayout.tsx
Length of output: 129
Script:
#!/bin/bash
# Check for route protection mechanisms in App.tsx and related components
rg -A 5 "PrivateRoute|RequireAuth|isAuthenticated|useAuth|authGuard" src/
# Look for router setup and protected routes
ast-grep --pattern 'const router = $$$'
# Find authentication-related hooks or context
ast-grep --pattern 'export const AuthContext = $$$'
ast-grep --pattern 'export function useAuth() { $$$ }'
# Check if LandingPage has any navigation logic
rg -A 5 "navigate|useNavigate|Link" src/pages/Landing.tsx
# Look for route constants and their usage
rg "SEARCH_PATH|SNIPPET_DETAIL_PATH" src/
Length of output: 5589
src/constants/translations.ts (2)
40-42: LGTM! English translations follow consistent patterns.
The new translations maintain proper capitalization and naming conventions. The use of "Log In" as two words is correct for the verb form.
82-84: Consider UI implications of translation length differences.
The Spanish translations are accurate, but note that "Iniciar sesión" (8 chars + space) is significantly longer than "Log In" (4 chars + space). Ensure that the UI components using these translations (likely buttons) can accommodate the longer Spanish text without truncation or layout shifts.
Let's check for potential UI components using these translations:
✅ Verification successful
No UI concerns with the longer Spanish translations
The implementation is safe for the longer Spanish translations:
- The buttons are using the
size='lg'prop which provides ample space - The buttons are wrapped in a flex container with
gap-4and responsive layout (flex-colon mobile,flex-rowon larger screens) - No fixed width constraints are applied to the buttons that could cause text truncation
- The buttons are styled with Tailwind CSS classes that allow for flexible content width
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for components using these translation keys
rg "translations\.(english|spanish)\.(logIn|createAccount)" --type tsx
Length of output: 100
Script:
#!/bin/bash
# Search for components using these translation keys with broader pattern
# and without file type restriction since tsx extension wasn't recognized
rg "(logIn|createAccount)" -A 2 -B 2
# Also search for any translation usage patterns
rg "translation" -A 2 -B 2
Length of output: 7706
Script:
#!/bin/bash
# Let's check the Button component implementation to see if it handles text overflow
rg -A 5 -B 5 "export.*Button"
# Also check if there are any specific size or width constraints in Landing.tsx
cat src/pages/Landing.tsx
Length of output: 5594
src/components/HeaderBar.tsx (2)
5-5: LGTM! Clean import modifications.
The removal of the Globe icon import and addition of LanguageDropdown import aligns well with the component's new responsibilities.
Also applies to: 13-13
41-41: Verify responsive layout with LanguageDropdown.
The LanguageDropdown placement looks good, but please verify that the header maintains its layout integrity on smaller screens with all these controls (InboxPopover, dark mode toggle, language dropdown, and user menu).
✅ Verification successful
Header layout appears responsive and well-structured
The HeaderBar implementation already has proper responsive design patterns in place:
- Uses flexbox layout with
flex items-center justify-betweenfor the main header - Controls are wrapped in a flex container with proper spacing (
flex items-center space-x-4) - Consistent sizing for all control elements (h-8 w-8)
- Mobile-friendly padding (px-8 py-2)
The current implementation should handle the LanguageDropdown addition gracefully since it:
- Is placed within the existing responsive flex container
- Follows the same component patterns as other controls
- Uses consistent spacing through the parent's space-x-4 utility
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for responsive utilities or media queries in HeaderBar and related components
rg -t tsx -t css "sm:|md:|lg:|xl:|2xl:|@media" --glob "src/components/{HeaderBar,LanguageDropdown}.{tsx,css}"
Length of output: 139
Script:
#!/bin/bash
# Search for responsive utilities or media queries with correct file extensions
rg -t ts -t css "sm:|md:|lg:|xl:|2xl:|@media" src/components/HeaderBar.* src/components/LanguageDropdown.*
# Check the structure and styling of HeaderBar
rg -A 5 "className" src/components/HeaderBar.*
# Look for any existing responsive container or wrapper classes
rg -t ts -t css "flex|grid|container|hidden|block|inline" src/components/HeaderBar.*
Length of output: 2328
src/pages/Landing.tsx (1)
62-64: Verify carousel content loading.
The LandingPageCarousel component is rendered without any props or error handling. Ensure it properly handles loading and error states.
src/components/LandingPageCarousel.tsx (1)
11-47: 🛠️ Refactor suggestion
Consider externalizing content data.
The snippets array contains hard-coded content that would be better managed externally:
- Move this data to a separate content file or CMS
- This would improve maintainability and allow for easier content updates
- Consider implementing content validation for sensitive political topics
Consider moving the snippets to a separate file:
// src/content/landingPageSnippets.ts
export const snippets = [
{
titleEn: 'Warning Against Amendment 4 in Florida',
titleEs: 'Advertencia Contra la Enmienda 4 en Florida',
tags: ['Abortion and Reproductive Rights']
},
// ... rest of the snippets
];✅ Verification successful
Let me search for any existing content files or similar patterns to ensure a comprehensive verification.
Content externalization recommended for better maintainability
The verification confirms that the content is currently only present in the LandingPageCarousel component and there's no existing content management structure in place. Moving this politically sensitive content to a dedicated content file would indeed improve maintainability and make content updates more manageable.
- Create a new
src/contentdirectory for structured content - Move the snippets array to
src/content/landingPageSnippets.ts - Import the content in the LandingPageCarousel component
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if similar content is already externalized
rg -g '!*.test.*' -g '!*.spec.*' "titleEn.*Warning Against Amendment"
Length of output: 165
Script:
#!/bin/bash
# Look for potential content files and similar data structures
rg -g '!*.test.*' -g '!*.spec.*' "titleEn.*titleEs" -A 2
# Check for any existing content directory structure
fd "content|data" --type d
# Look for any CMS or content management related imports
rg -g '!*.test.*' -g '!*.spec.*' "import.*content|from.*content"
Length of output: 352
| return ( | ||
| <DropdownMenu> | ||
| <DropdownMenuTrigger asChild> | ||
| <Button variant='ghost' size='icon' className='h-8 w-8 p-0 hover:bg-gray-50'> | ||
| <Globe className='h-6 w-6 text-blue-600' /> | ||
| <span className='sr-only'>{t.changeLanguage}</span> | ||
| </Button> | ||
| </DropdownMenuTrigger> | ||
| <DropdownMenuContent align='end' className='w-48'> | ||
| <DropdownMenuItem className='cursor-pointer' onClick={() => setLanguage('spanish')}> | ||
| <span>Español</span> | ||
| {language === 'spanish' && <span className='ml-2'>✓</span>} | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem className='cursor-pointer' onClick={() => setLanguage('english')}> | ||
| <span>English</span> | ||
| {language === 'english' && <span className='ml-2'>✓</span>} | ||
| </DropdownMenuItem> | ||
| </DropdownMenuContent> | ||
| </DropdownMenu> | ||
| ) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Enhance accessibility for better screen reader support.
Consider these accessibility improvements:
- Add ARIA labels to dropdown items
- Announce language changes to screen readers
- Add keyboard navigation instructions
Here's how to implement these improvements:
+ const announceLanguageChange = (newLanguage: string) => {
+ const message = t[`languageChangedTo${newLanguage.charAt(0).toUpperCase() + newLanguage.slice(1)}`];
+ const announcement = document.createElement('div');
+ announcement.setAttribute('aria-live', 'polite');
+ announcement.textContent = message;
+ document.body.appendChild(announcement);
+ setTimeout(() => document.body.removeChild(announcement), 1000);
+ };
+ const handleLanguageChange = (newLanguage: 'spanish' | 'english') => {
+ setLanguage(newLanguage);
+ announceLanguageChange(newLanguage);
+ };
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant='ghost'
size='icon'
- className='h-8 w-8 p-0 hover:bg-gray-50'
+ className='h-8 w-8 p-0 hover:bg-gray-50'
+ aria-label={t.changeLanguage}
>
<Globe className='h-6 w-6 text-blue-600' />
- <span className='sr-only'>{t.changeLanguage}</span>
</Button>
</DropdownMenuTrigger>
- <DropdownMenuContent align='end' className='w-48'>
+ <DropdownMenuContent
+ align='end'
+ className='w-48'
+ aria-label={t.languageSelection}
+ >
<DropdownMenuItem
className='cursor-pointer'
- onClick={() => setLanguage('spanish')}
+ onClick={() => handleLanguageChange('spanish')}
+ aria-label={`${t.selectSpanish}${language === 'spanish' ? '. ' + t.currentlySelected : ''}`}
>Don't forget to add these new translation keys:
languageChangedToEnglishlanguageChangedToSpanishlanguageSelectionselectSpanishselectEnglishcurrentlySelected
Committable suggestion skipped: line range outside the PR's diff.
src/hooks/useLandingPageContent.ts
Outdated
| async function fetchLandingPageContent(language: 'en' | 'es'): Promise<LandingPageContent> { | ||
| const { data, error } = await supabase.rpc('get_latest_landing_page_content') | ||
|
|
||
| if (error) { | ||
| throw new Error(`Error fetching landing page content: ${error.message}`) | ||
| } | ||
|
|
||
| if (!Array.isArray(data)) { | ||
| throw new Error('Unexpected data format received from Supabase') | ||
| } | ||
|
|
||
| return data.reduce<LandingPageContent>( | ||
| (acc, item: ContentRow) => { | ||
| const key = item.key as keyof LandingPageContent | ||
| acc[key] = language === 'en' ? item.content_en : item.content_es | ||
| return acc | ||
| }, | ||
| { | ||
| hero_title: '', | ||
| hero_description: '', | ||
| footer_text: '' | ||
| } | ||
| ) | ||
| } |
There was a problem hiding this comment.
Improve type safety and error handling in fetchLandingPageContent.
Several improvements are needed:
- Add proper typing for Supabase RPC response
- Validate that all required content fields are present
- Consider throwing an error for missing content instead of silently falling back to empty strings
+type SupabaseResponse = {
+ data: ContentRow[] | null
+ error: Error | null
+}
async function fetchLandingPageContent(language: 'en' | 'es'): Promise<LandingPageContent> {
- const { data, error } = await supabase.rpc('get_latest_landing_page_content')
+ const { data, error }: SupabaseResponse = await supabase.rpc('get_latest_landing_page_content')
if (error) {
throw new Error(`Error fetching landing page content: ${error.message}`)
}
- if (!Array.isArray(data)) {
+ if (!data || !Array.isArray(data)) {
throw new Error('Unexpected data format received from Supabase')
}
+ const requiredKeys: (keyof LandingPageContent)[] = ['hero_title', 'hero_description', 'footer_text']
+ const missingKeys = requiredKeys.filter(key =>
+ !data.some(item => item.key === key)
+ )
+
+ if (missingKeys.length > 0) {
+ throw new Error(`Missing required content: ${missingKeys.join(', ')}`)
+ }
return data.reduce<LandingPageContent>(
(acc, item: ContentRow) => {
const key = item.key as keyof LandingPageContent
acc[key] = language === 'en' ? item.content_en : item.content_es
return acc
},
{
hero_title: '',
hero_description: '',
footer_text: ''
}
)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async function fetchLandingPageContent(language: 'en' | 'es'): Promise<LandingPageContent> { | |
| const { data, error } = await supabase.rpc('get_latest_landing_page_content') | |
| if (error) { | |
| throw new Error(`Error fetching landing page content: ${error.message}`) | |
| } | |
| if (!Array.isArray(data)) { | |
| throw new Error('Unexpected data format received from Supabase') | |
| } | |
| return data.reduce<LandingPageContent>( | |
| (acc, item: ContentRow) => { | |
| const key = item.key as keyof LandingPageContent | |
| acc[key] = language === 'en' ? item.content_en : item.content_es | |
| return acc | |
| }, | |
| { | |
| hero_title: '', | |
| hero_description: '', | |
| footer_text: '' | |
| } | |
| ) | |
| } | |
| type SupabaseResponse = { | |
| data: ContentRow[] | null | |
| error: Error | null | |
| } | |
| async function fetchLandingPageContent(language: 'en' | 'es'): Promise<LandingPageContent> { | |
| const { data, error }: SupabaseResponse = await supabase.rpc('get_latest_landing_page_content') | |
| if (error) { | |
| throw new Error(`Error fetching landing page content: ${error.message}`) | |
| } | |
| if (!data || !Array.isArray(data)) { | |
| throw new Error('Unexpected data format received from Supabase') | |
| } | |
| const requiredKeys: (keyof LandingPageContent)[] = ['hero_title', 'hero_description', 'footer_text'] | |
| const missingKeys = requiredKeys.filter(key => | |
| !data.some(item => item.key === key) | |
| ) | |
| if (missingKeys.length > 0) { | |
| throw new Error(`Missing required content: ${missingKeys.join(', ')}`) | |
| } | |
| return data.reduce<LandingPageContent>( | |
| (acc, item: ContentRow) => { | |
| const key = item.key as keyof LandingPageContent | |
| acc[key] = language === 'en' ? item.content_en : item.content_es | |
| return acc | |
| }, | |
| { | |
| hero_title: '', | |
| hero_description: '', | |
| footer_text: '' | |
| } | |
| ) | |
| } |
🧰 Tools
🪛 eslint
[error] 17-17: Unsafe assignment of an any value.
(@typescript-eslint/no-unsafe-assignment)
[error] 17-17: Unsafe call of an any typed value.
(@typescript-eslint/no-unsafe-call)
[error] 17-17: Unsafe member access .rpc on an any value.
(@typescript-eslint/no-unsafe-member-access)
[error] 20-20: Unsafe member access .message on an any value.
(@typescript-eslint/no-unsafe-member-access)
| {[...snippets, ...snippets].map((snippet, idx) => ( | ||
| <Card key={`${snippet.titleEn}-${idx}`} className='mb-4 border-white/10 bg-white/5 p-4'> |
There was a problem hiding this comment.
Replace array indices with stable unique keys.
Using array indices as React keys can lead to rendering issues when items are reordered or removed. Consider using a stable unique identifier.
-{[...snippets, ...snippets].map((snippet, idx) => (
- <Card key={`${snippet.titleEn}-${idx}`} className='mb-4 border-white/10 bg-white/5 p-4'>
+{[...snippets, ...snippets].map((snippet, idx) => (
+ <Card key={`${snippet.titleEn}-${idx % snippets.length}`} className='mb-4 border-white/10 bg-white/5 p-4'>
-{snippet.tags.map((tag, tagIndex) => (
- <Badge key={tagIndex} variant='secondary' className='bg-white/20 text-xs text-white'>
+{snippet.tags.map((tag) => (
+ <Badge key={tag} variant='secondary' className='bg-white/20 text-xs text-white'>Also applies to: 111-115
🧰 Tools
🪛 eslint
[error] 98-98: Do not use Array index in keys
(react/no-array-index-key)
Description
This Pull Request introduces several enhancements and new features to improve the user experience and functionality of the application. The primary updates include the creation of a Landing Page, the addition of a language dropdown component for better language management, and enhancements to the existing routing and header components.
Summary of Changes
Landing Page Implementation:
LandingPagecomponent (src/pages/Landing.tsx) to serve as the application’s entry point for users.LandingPageCarousel.tsx) on the landing page to dynamically display snippets with titles and tags in both English and Spanish.useLandingPageContent.ts) to fetch and manage landing page content based on the user's language preference.Language Management Enhancements:
LanguageDropdowncomponent (src/components/LanguageDropdown.tsx) to allow users to switch between English and Spanish easily.HeaderBarcomponent to replace the inline language selection with the newLanguageDropdowncomponent, simplifying the code and improving usability.src/constants/translations.tsto support additional UI text, such as "Log In" and "Create Account."Routing and Layout Adjustments:
App.tsxto redirect users to the newly createdLandingPageinstead of theLoginPage.Outletfromreact-router-dominApp.tsxto optimize the code.Miscellaneous Updates:
getUserLanguageinsrc/utils/language.tsto detect and return the user's language preference based on their browser settings.HeaderBar.tsxcomponent to remove redundant language handling logic, streamlining the component's functionality.Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Documentation
Chores