Skip to content

[f] Ver 149 - Landing page#109

Merged
nlgthuan merged 10 commits intomainfrom
ver-149-landing-page
Nov 5, 2024
Merged

[f] Ver 149 - Landing page#109
nlgthuan merged 10 commits intomainfrom
ver-149-landing-page

Conversation

@nlgthuan
Copy link
Collaborator

@nlgthuan nlgthuan commented Nov 5, 2024

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

  1. Landing Page Implementation:

    • Introduced a new LandingPage component (src/pages/Landing.tsx) to serve as the application’s entry point for users.
    • Integrated a carousel component (LandingPageCarousel.tsx) on the landing page to dynamically display snippets with titles and tags in both English and Spanish.
    • Utilized a custom hook (useLandingPageContent.ts) to fetch and manage landing page content based on the user's language preference.
  2. Language Management Enhancements:

    • Created a LanguageDropdown component (src/components/LanguageDropdown.tsx) to allow users to switch between English and Spanish easily.
    • Updated the HeaderBar component to replace the inline language selection with the new LanguageDropdown component, simplifying the code and improving usability.
    • Extended the translations in src/constants/translations.ts to support additional UI text, such as "Log In" and "Create Account."
  3. Routing and Layout Adjustments:

    • Changed the default route fallback in App.tsx to redirect users to the newly created LandingPage instead of the LoginPage.
    • Removed unused imports such as Outlet from react-router-dom in App.tsx to optimize the code.
  4. Miscellaneous Updates:

    • Introduced utility function getUserLanguage in src/utils/language.ts to detect and return the user's language preference based on their browser settings.
    • Updated the HeaderBar.tsx component to remove redundant language handling logic, streamlining the component's functionality.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new landing page with a carousel for displaying snippets.
    • Added a language selection dropdown for improved user experience.
  • Bug Fixes

    • Updated routing to direct users to the new landing page instead of the login page.
  • Documentation

    • Expanded translation support with new entries for 'Log In' and 'Create Account' in English and Spanish.
  • Chores

    • Removed outdated language change functionality from the header.

@linear
Copy link

linear bot commented Nov 5, 2024

VER-149 Landing page

@nlgthuan nlgthuan linked an issue Nov 5, 2024 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Nov 5, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The pull request introduces several changes primarily focused on the routing and component structure of the application. The App component's routing has been updated to replace the default catch-all route with a new LandingPage. Additionally, the HeaderBar component has been modified to remove the previous language selection functionality, which has been encapsulated in a new LanguageDropdown component. A new LandingPageCarousel component has been created, alongside the addition of a custom hook for fetching landing page content and a new utility function for determining user language preferences.

Changes

File Change Summary
src/App.tsx Removed Outlet import, added LandingPage import, and updated routing to direct to LandingPage.
src/components/HeaderBar.tsx Removed Globe icon and language change functionality; added LanguageDropdown component.
src/components/LandingPageCarousel.tsx Introduced a new component for a vertically scrolling carousel displaying snippets.
src/components/LanguageDropdown.tsx Added a new language selection dropdown component with options for English and Spanish.
src/constants/translations.ts Added new translation keys for 'Log In' and 'Create Account' in English and Spanish.
src/hooks/useLandingPageContent.ts Introduced a custom hook for fetching landing page content from Supabase.
src/pages/Landing.tsx Added a new landing page component that fetches content and incorporates LandingPageCarousel.
src/utils/language.ts Added a function to determine user language preference based on browser settings.

Possibly related PRs

Suggested reviewers

  • nhphong
  • Dat-H-Tran

🐇 In the land of code where rabbits play,
New routes and components brighten the day.
With a dropdown for language and a carousel to see,
Our app hops forward, as happy as can be!
So let’s celebrate changes, both big and small,
For a smoother experience, we’ll have a ball! 🎉


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

  1. Extract language options to a constant:
const LANGUAGE_OPTIONS = [
  { code: 'spanish', label: 'Español' },
  { code: 'english', label: 'English' }
] as const;
  1. Use an icon component for the checkmark for consistency with the design system.
  2. 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 .ts extension 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 key property should be constrained to valid keys of LandingPageContent.

 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:

  1. Add ARIA live region for dynamic content
  2. Include controls for manual navigation
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between c55c9da and c6d23a3.

📒 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.ts uses the standard client configuration
  • The Supabase client in src/lib/supabase.ts is correctly configured with VITE_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:

  1. Protected routes (SEARCH_PATH, SNIPPET_DETAIL_PATH) remain secure
  2. Users can still access the login page when needed
  3. 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 in PrivateRoute component which checks authentication status
  • Landing page provides clear navigation to login/signup through dedicated buttons
  • useAuth hook and AuthContext are 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:

  1. The buttons are using the size='lg' prop which provides ample space
  2. The buttons are wrapped in a flex container with gap-4 and responsive layout (flex-col on mobile, flex-row on larger screens)
  3. No fixed width constraints are applied to the buttons that could cause text truncation
  4. 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-between for 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:

  1. Move this data to a separate content file or CMS
  2. This would improve maintainability and allow for easier content updates
  3. 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/content directory 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

Comment on lines +12 to +32
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>
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance accessibility for better screen reader support.

Consider these accessibility improvements:

  1. Add ARIA labels to dropdown items
  2. Announce language changes to screen readers
  3. 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:

  • languageChangedToEnglish
  • languageChangedToSpanish
  • languageSelection
  • selectSpanish
  • selectEnglish
  • currentlySelected

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +16 to +39
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: ''
}
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve type safety and error handling in fetchLandingPageContent.

Several improvements are needed:

  1. Add proper typing for Supabase RPC response
  2. Validate that all required content fields are present
  3. 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.

Suggested change
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)

Comment on lines +97 to +98
{[...snippets, ...snippets].map((snippet, idx) => (
<Card key={`${snippet.titleEn}-${idx}`} className='mb-4 border-white/10 bg-white/5 p-4'>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Landing page

1 participant