From cfc0b431831a7f8b319dcb6aef76f75a0fa8a796 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:26:25 +0000 Subject: [PATCH 1/4] Initial plan From a1aef914cd4bca9c278e4094e205d7a450325cba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:35:32 +0000 Subject: [PATCH 2/4] Refactor: Create reusable PageBreadcrumb component Co-authored-by: marcelo-m7 <117441129+marcelo-m7@users.noreply.github.com> --- src/components/PageBreadcrumb.tsx | 58 +++++++++++++++++++++++++++++++ src/pages/Blog.tsx | 23 ++---------- src/pages/Contact.tsx | 41 ++++------------------ src/pages/Projects.tsx | 25 +++---------- src/pages/Solutions.tsx | 25 +++---------- src/pages/blog/[slug].tsx | 34 ++++-------------- src/pages/solutions/[slug].tsx | 34 ++++-------------- 7 files changed, 89 insertions(+), 151 deletions(-) create mode 100644 src/components/PageBreadcrumb.tsx diff --git a/src/components/PageBreadcrumb.tsx b/src/components/PageBreadcrumb.tsx new file mode 100644 index 0000000..bc9e455 --- /dev/null +++ b/src/components/PageBreadcrumb.tsx @@ -0,0 +1,58 @@ +import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { + Breadcrumb, + BreadcrumbList, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbPage, + BreadcrumbSeparator, +} from '@/components/ui/breadcrumb'; + +export interface BreadcrumbSegment { + label: string; + href?: string; +} + +interface PageBreadcrumbProps { + segments: BreadcrumbSegment[]; +} + +/** + * Reusable breadcrumb navigation component. + * Always starts with a "Home" link and then renders the provided segments. + */ +const PageBreadcrumb = ({ segments }: PageBreadcrumbProps) => { + const { t } = useTranslation(); + + return ( + + + + + {t('navigation.home')} + + + {segments.map((segment, index) => { + const isLast = index === segments.length - 1; + return ( +
+ + + {isLast || !segment.href ? ( + {segment.label} + ) : ( + + {segment.label} + + )} + +
+ ); + })} +
+
+ ); +}; + +export default PageBreadcrumb; diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx index 287c7e5..11dfc78 100644 --- a/src/pages/Blog.tsx +++ b/src/pages/Blog.tsx @@ -4,6 +4,7 @@ import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import Layout from '@/components/Layout'; import Meta from '@/components/Meta'; +import PageBreadcrumb from '@/components/PageBreadcrumb'; import NewsletterSection from '@/components/NewsletterSection'; import { ArrowRight, @@ -15,14 +16,6 @@ import { import { supabase } from '@/integrations/supabase'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; -import { - Breadcrumb, - BreadcrumbList, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbPage, - BreadcrumbSeparator, -} from '@/components/ui/breadcrumb'; import { Pagination, PaginationContent, @@ -239,19 +232,7 @@ const Blog = () => { ogImage="/placeholder.svg" />
- - - - - {t('navigation.home')} - - - - - {t('navigation.blog')} - - - +
diff --git a/src/pages/Contact.tsx b/src/pages/Contact.tsx index 6a1ffd6..94682fe 100644 --- a/src/pages/Contact.tsx +++ b/src/pages/Contact.tsx @@ -5,20 +5,13 @@ import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import Layout from '@/components/Layout'; import Meta from '@/components/Meta'; +import PageBreadcrumb from '@/components/PageBreadcrumb'; import { Mail, Phone, MapPin, Send, CheckCircle } from 'lucide-react'; import { supabase } from '@/integrations/supabase'; import { useToast } from '@/hooks/use-toast'; import { useTranslation } from 'react-i18next'; import { useMemo } from 'react'; import { Link } from 'react-router-dom'; -import { - Breadcrumb, - BreadcrumbList, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbPage, - BreadcrumbSeparator, -} from '@/components/ui/breadcrumb'; const projectTypes = [ 'Custom AI Assistant', @@ -157,19 +150,9 @@ const Contact = () => { ogImage="/placeholder.svg" />
- - - - - {t('navigation.home')} - - - - - {t('navigation.contact')} - - - +
@@ -204,19 +187,9 @@ const Contact = () => { ogImage="/placeholder.svg" />
- - - - - {t('navigation.home')} - - - - - {t('navigation.contact')} - - - +
{/* Hero Section */}
diff --git a/src/pages/Projects.tsx b/src/pages/Projects.tsx index 3d210d2..e39bb9c 100644 --- a/src/pages/Projects.tsx +++ b/src/pages/Projects.tsx @@ -9,17 +9,10 @@ import { } from 'lucide-react'; import Layout from '../components/Layout'; import Meta from '@/components/Meta'; +import PageBreadcrumb from '@/components/PageBreadcrumb'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; -import { - Breadcrumb, - BreadcrumbList, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbPage, - BreadcrumbSeparator, -} from '@/components/ui/breadcrumb'; import { supabase } from '@/integrations/supabase'; import { useTranslation, Trans } from 'react-i18next'; import { Link } from 'react-router-dom'; @@ -232,19 +225,9 @@ const Projects = () => { ogImage="/placeholder.svg" />
- - - - - {t('navigation.home')} - - - - - {t('navigation.projects')} - - - +
diff --git a/src/pages/Solutions.tsx b/src/pages/Solutions.tsx index a1c8094..4e2ec22 100644 --- a/src/pages/Solutions.tsx +++ b/src/pages/Solutions.tsx @@ -4,15 +4,8 @@ import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import Layout from '@/components/Layout'; import Meta from '@/components/Meta'; +import PageBreadcrumb from '@/components/PageBreadcrumb'; import { Link } from 'react-router-dom'; -import { - Breadcrumb, - BreadcrumbList, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbPage, - BreadcrumbSeparator, -} from '@/components/ui/breadcrumb'; import { ArrowRight, CheckCircle } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { supabase } from '@/integrations/supabase'; @@ -105,19 +98,9 @@ const Solutions = () => { ogImage="/placeholder.svg" />
- - - - - {t('navigation.home')} - - - - - {t('navigation.solutions')} - - - +
{/* Hero Section */}
diff --git a/src/pages/blog/[slug].tsx b/src/pages/blog/[slug].tsx index a8c04ca..bab95c3 100644 --- a/src/pages/blog/[slug].tsx +++ b/src/pages/blog/[slug].tsx @@ -5,19 +5,12 @@ import { useTranslation } from 'react-i18next'; import { ArrowLeft, Clock, User } from 'lucide-react'; import Layout from '@/components/Layout'; import Meta from '@/components/Meta'; +import PageBreadcrumb from '@/components/PageBreadcrumb'; import NewsletterSection from '@/components/NewsletterSection'; import CommentsSection from '@/components/blog/CommentsSection'; import { supabase } from '@/integrations/supabase'; import { getNormalizedLocale } from '@/lib/i18n'; import type { Database } from '@/integrations/supabase/types'; -import { - Breadcrumb, - BreadcrumbList, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbPage, - BreadcrumbSeparator, -} from '@/components/ui/breadcrumb'; type BlogPost = Database['public']['Tables']['blog_posts']['Row']; @@ -336,25 +329,12 @@ const BlogPostPage = () => { ogImage={metaImage} />
- - - - - {t('navigation.home')} - - - - - - {t('navigation.blog')} - - - - - {post.title} - - - +
diff --git a/src/pages/solutions/[slug].tsx b/src/pages/solutions/[slug].tsx index 01859ca..f8d8da5 100644 --- a/src/pages/solutions/[slug].tsx +++ b/src/pages/solutions/[slug].tsx @@ -3,16 +3,9 @@ import { useParams, Link } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import Layout from '@/components/Layout'; import Meta from '@/components/Meta'; +import PageBreadcrumb from '@/components/PageBreadcrumb'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from '@/components/ui/breadcrumb'; import { ArrowLeft, ArrowRight, CheckCircle } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { supabase } from '@/integrations/supabase'; @@ -164,25 +157,12 @@ const SolutionDetail = () => { ogImage={displaySolution.imageUrl ?? '/placeholder.svg'} />
- - - - - {t('navigation.home')} - - - - - - {t('navigation.solutions')} - - - - - {displaySolution.title} - - - +
From 29a90b5b00e381d9acea75f2002156a36384bc5a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:37:48 +0000 Subject: [PATCH 3/4] Refactor: Create reusable date formatting utility Co-authored-by: marcelo-m7 <117441129+marcelo-m7@users.noreply.github.com> --- src/components/blog/CommentsSection.tsx | 20 +++++++------------- src/lib/i18n.ts | 17 +++++++++++++++++ src/pages/Blog.tsx | 18 +++++------------- src/pages/blog/[slug].tsx | 16 +++++----------- 4 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/components/blog/CommentsSection.tsx b/src/components/blog/CommentsSection.tsx index bd3aafe..e52f737 100644 --- a/src/components/blog/CommentsSection.tsx +++ b/src/components/blog/CommentsSection.tsx @@ -11,7 +11,7 @@ import { Textarea } from '@/components/ui/textarea'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Loader2 } from 'lucide-react'; import { Link } from 'react-router-dom'; -import { getNormalizedLocale } from '@/lib/i18n'; +import { getNormalizedLocale, createDateFormatter } from '@/lib/i18n'; interface CommentsSectionProps { postId: string; @@ -52,20 +52,14 @@ const CommentsSection = ({ postId }: CommentsSectionProps) => { [i18n.language] ); - const dateFormatter = useMemo(() => { - try { - return new Intl.DateTimeFormat(normalizedLocale, { + const dateFormatter = useMemo( + () => + createDateFormatter(normalizedLocale, { dateStyle: 'medium', timeStyle: 'short', - }); - } catch (error) { - console.error('Unsupported locale for comment date formatting', error); - return new Intl.DateTimeFormat('en-US', { - dateStyle: 'medium', - timeStyle: 'short', - }); - } - }, [normalizedLocale]); + }), + [normalizedLocale] + ); const { data: comments, diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index b4013a5..df4866a 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -20,3 +20,20 @@ export const getNormalizedLocale = (language: string | undefined | null) => { return language; } }; + +/** + * Creates a date formatter with fallback to en-US if the locale is not supported. + * This utility centralizes date formatting logic used across the application. + */ +export const createDateFormatter = ( + locale: string, + options: Intl.DateTimeFormatOptions +): Intl.DateTimeFormat => { + try { + return new Intl.DateTimeFormat(locale, options); + } catch (error) { + console.error(`Unsupported locale "${locale}" for date formatting`, error); + return new Intl.DateTimeFormat('en-US', options); + } +}; + diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx index 11dfc78..ba0032f 100644 --- a/src/pages/Blog.tsx +++ b/src/pages/Blog.tsx @@ -24,7 +24,7 @@ import { PaginationEllipsis, } from '@/components/ui/pagination'; import { cn } from '@/lib/utils'; -import { getNormalizedLocale } from '@/lib/i18n'; +import { getNormalizedLocale, createDateFormatter } from '@/lib/i18n'; import type { Database } from '@/integrations/supabase/types'; const FALLBACK_IMAGE = @@ -83,18 +83,10 @@ const Blog = () => { [i18n.language] ); - const dateFormatter = useMemo(() => { - try { - return new Intl.DateTimeFormat(normalizedLocale, { - dateStyle: 'medium', - }); - } catch (error) { - console.error('Unsupported locale for blog date formatting', error); - return new Intl.DateTimeFormat('en-US', { - dateStyle: 'medium', - }); - } - }, [normalizedLocale]); + const dateFormatter = useMemo( + () => createDateFormatter(normalizedLocale, { dateStyle: 'medium' }), + [normalizedLocale] + ); const categories = useMemo( () => [ diff --git a/src/pages/blog/[slug].tsx b/src/pages/blog/[slug].tsx index bab95c3..6259f4e 100644 --- a/src/pages/blog/[slug].tsx +++ b/src/pages/blog/[slug].tsx @@ -9,7 +9,7 @@ import PageBreadcrumb from '@/components/PageBreadcrumb'; import NewsletterSection from '@/components/NewsletterSection'; import CommentsSection from '@/components/blog/CommentsSection'; import { supabase } from '@/integrations/supabase'; -import { getNormalizedLocale } from '@/lib/i18n'; +import { getNormalizedLocale, createDateFormatter } from '@/lib/i18n'; import type { Database } from '@/integrations/supabase/types'; type BlogPost = Database['public']['Tables']['blog_posts']['Row']; @@ -211,16 +211,10 @@ const BlogPostPage = () => { [i18n.language] ); - const dateFormatter = useMemo(() => { - try { - return new Intl.DateTimeFormat(normalizedLocale, { - dateStyle: 'long', - }); - } catch (error) { - console.error('Unsupported locale for blog post date formatting', error); - return new Intl.DateTimeFormat('en-US', { dateStyle: 'long' }); - } - }, [normalizedLocale]); + const dateFormatter = useMemo( + () => createDateFormatter(normalizedLocale, { dateStyle: 'long' }), + [normalizedLocale] + ); const { data: post, From 7c07769df8f8eed8f978c5c7399aa04edfec196f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:39:52 +0000 Subject: [PATCH 4/4] Refactor: Extract common query options into shared constants Co-authored-by: marcelo-m7 <117441129+marcelo-m7@users.noreply.github.com> --- src/lib/queryOptions.ts | 16 ++++++++++++++++ src/pages/Projects.tsx | 9 +++------ src/pages/Solutions.tsx | 5 ++--- src/pages/solutions/[slug].tsx | 5 ++--- 4 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 src/lib/queryOptions.ts diff --git a/src/lib/queryOptions.ts b/src/lib/queryOptions.ts new file mode 100644 index 0000000..a212f31 --- /dev/null +++ b/src/lib/queryOptions.ts @@ -0,0 +1,16 @@ +/** + * Common React Query configuration options used across the application. + * Centralizes query behavior to ensure consistency. + */ + +/** + * Standard query options for data that doesn't change frequently. + * - staleTime: 10 minutes - data stays fresh for this duration + * - retry: 1 - only retry failed queries once + * - refetchOnWindowFocus: false - don't refetch when window regains focus + */ +export const LONG_STALE_QUERY_OPTIONS = { + staleTime: 1000 * 60 * 10, // 10 minutes + retry: 1, + refetchOnWindowFocus: false, +} as const; diff --git a/src/pages/Projects.tsx b/src/pages/Projects.tsx index e39bb9c..8f638db 100644 --- a/src/pages/Projects.tsx +++ b/src/pages/Projects.tsx @@ -22,6 +22,7 @@ import { getFallbackSolutions, mapGitHubRepoToContent, } from '@/lib/solutions'; +import { LONG_STALE_QUERY_OPTIONS } from '@/lib/queryOptions'; import type { GitHubRepository } from '@/lib/solutions'; import type { SolutionContent } from '@/types/solutions'; @@ -72,9 +73,7 @@ const Projects = () => { } = useQuery({ queryKey: ['solutions'], queryFn: fetchSupabaseSolutions, - staleTime: 1000 * 60 * 10, - retry: 1, - refetchOnWindowFocus: false, + ...LONG_STALE_QUERY_OPTIONS, }); // Removed duplicate githubSolutions useQuery block @@ -120,9 +119,7 @@ const Projects = () => { mapGitHubRepoToContent(repository, index) ); }, - staleTime: 1000 * 60 * 10, - retry: 1, - refetchOnWindowFocus: false, + ...LONG_STALE_QUERY_OPTIONS, }); const combinedSolutions = useMemo(() => { diff --git a/src/pages/Solutions.tsx b/src/pages/Solutions.tsx index 4e2ec22..79f60e7 100644 --- a/src/pages/Solutions.tsx +++ b/src/pages/Solutions.tsx @@ -13,6 +13,7 @@ import { getFallbackSolutions, mapSupabaseSolutionToContent, } from '@/lib/solutions'; +import { LONG_STALE_QUERY_OPTIONS } from '@/lib/queryOptions'; import type { SolutionContent } from '@/types/solutions'; @@ -46,9 +47,7 @@ const Solutions = () => { ); }, - staleTime: 1000 * 60 * 10, - retry: 1, - refetchOnWindowFocus: false, + ...LONG_STALE_QUERY_OPTIONS, }); const displaySolutions = diff --git a/src/pages/solutions/[slug].tsx b/src/pages/solutions/[slug].tsx index f8d8da5..f33b38c 100644 --- a/src/pages/solutions/[slug].tsx +++ b/src/pages/solutions/[slug].tsx @@ -16,6 +16,7 @@ import { mapGitHubRepoToContent, mapSupabaseSolutionToContent, } from '@/lib/solutions'; +import { LONG_STALE_QUERY_OPTIONS } from '@/lib/queryOptions'; const getRepositoryUrl = (repositorySlug: string) => `https://api.github.com/repos/Monynha-Softwares/${encodeURIComponent( @@ -80,10 +81,8 @@ const SolutionDetail = () => { return mapGitHubRepoToContent(repository, 0); }, - staleTime: 1000 * 60 * 10, - retry: 1, + ...LONG_STALE_QUERY_OPTIONS, keepPreviousData: true, - refetchOnWindowFocus: false, }); const displaySolution = solution ?? fallbackSolution ?? null;