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;