diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
index c77cb64..c4f3f04 100644
--- a/src/pages/Dashboard.tsx
+++ b/src/pages/Dashboard.tsx
@@ -4,6 +4,7 @@ import { supabase } from '@/integrations/supabase';
import type { Database } from '@/integrations/supabase/types';
import { useAuth } from '@/hooks/useAuth';
import { useToast } from '@/hooks/use-toast';
+import { useLocalizedDateFormatter } from '@/hooks/useLocalizedDateFormatter';
import { Button } from '@/components/ui/button';
import {
Card,
@@ -26,7 +27,6 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import Loading from '@/components/Loading';
import { AlertTriangle, Loader2, LogOut, RefreshCcw } from 'lucide-react';
import { useTranslation } from 'react-i18next';
-import { getNormalizedLocale } from '@/lib/i18n';
type Profile = Database['public']['Tables']['profiles']['Row'];
type Lead = Database['public']['Tables']['leads']['Row'];
@@ -34,7 +34,7 @@ type NewsletterSubscriber =
Database['public']['Tables']['newsletter_subscribers']['Row'];
const Dashboard = () => {
- const { t, i18n } = useTranslation();
+ const { t } = useTranslation();
const { user, signOut } = useAuth();
const { toast } = useToast();
const navigate = useNavigate();
@@ -149,45 +149,10 @@ const Dashboard = () => {
void loadDashboard();
}, [loadDashboard]);
- const normalizedLocale = useMemo(
- () => getNormalizedLocale(i18n.language),
- [i18n.language]
- );
-
- const fallbackDateFormatter = useMemo(
- () =>
- new Intl.DateTimeFormat('en-US', {
- dateStyle: 'short',
- timeStyle: 'short',
- }),
- []
- );
-
- const dateTimeFormatter = useMemo(() => {
- try {
- return new Intl.DateTimeFormat(normalizedLocale, {
- dateStyle: 'short',
- timeStyle: 'short',
- });
- } catch (error) {
- console.error('Unsupported locale for dashboard date formatting', error);
- return fallbackDateFormatter;
- }
- }, [fallbackDateFormatter, normalizedLocale]);
-
- const formatDate = useCallback(
- (value: string | null | undefined) => {
- if (!value) return '—';
-
- try {
- return dateTimeFormatter.format(new Date(value));
- } catch (error) {
- console.error('Error formatting dashboard date', error);
- return fallbackDateFormatter.format(new Date(value));
- }
- },
- [dateTimeFormatter, fallbackDateFormatter]
- );
+ const { formatDate } = useLocalizedDateFormatter({
+ dateStyle: 'short',
+ timeStyle: 'short',
+ });
const handleRefresh = () => {
void loadDashboard();
diff --git a/src/pages/Projects.tsx b/src/pages/Projects.tsx
index 1c3de55..eabae28 100644
--- a/src/pages/Projects.tsx
+++ b/src/pages/Projects.tsx
@@ -1,4 +1,4 @@
-import { useMemo, useCallback } from 'react';
+import { useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Github, ExternalLink, Calendar, ArrowRight } from 'lucide-react';
import Layout from '../components/Layout';
@@ -6,18 +6,12 @@ import Meta from '@/components/Meta';
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 { PageBreadcrumb } from '@/components/PageBreadcrumb';
import { supabase } from '@/integrations/supabase';
import { useTranslation, Trans } from 'react-i18next';
import { Link } from 'react-router-dom';
import useRepositorySync from '@/hooks/useRepositorySync';
+import { useLocalizedDateFormatter } from '@/hooks/useLocalizedDateFormatter';
import {
fetchSupabaseSolutions,
getFallbackSolutions,
@@ -25,7 +19,6 @@ import {
} from '@/lib/solutions';
import type { GitHubRepository } from '@/lib/solutions';
import type { SolutionContent } from '@/types/solutions';
-import { getNormalizedLocale } from '@/lib/i18n';
import { SolutionCard, sectionContainer, sectionPaddingY } from '@monynha/ui';
import { cn } from '@/lib/utils';
@@ -43,50 +36,16 @@ const GITHUB_REPOS_URL =
'https://api.github.com/orgs/Monynha-Softwares/repos?per_page=100';
const Projects = () => {
- const { t, i18n } = useTranslation();
+ const { t } = useTranslation();
useRepositorySync();
const memoizedFallbackSolutions = useMemo(() => getFallbackSolutions(), []);
- const normalizedLocale = useMemo(
- () => getNormalizedLocale(i18n.language),
- [i18n.language]
- );
-
- const fallbackDateFormatter = useMemo(
- () =>
- new Intl.DateTimeFormat('en-US', {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- }),
- []
- );
-
- const dateFormatter = useMemo(() => {
- try {
- return new Intl.DateTimeFormat(normalizedLocale, {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- });
- } catch (error) {
- console.error('Unsupported locale for project date formatting', error);
- return fallbackDateFormatter;
- }
- }, [fallbackDateFormatter, normalizedLocale]);
-
- const formatDate = useCallback(
- (dateString: string) => {
- try {
- return dateFormatter.format(new Date(dateString));
- } catch (error) {
- console.error('Error formatting project date', error);
- return fallbackDateFormatter.format(new Date(dateString));
- }
- },
- [dateFormatter, fallbackDateFormatter]
- );
+ const { formatDate } = useLocalizedDateFormatter({
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ });
const {
data: repositories = [],
@@ -251,21 +210,7 @@ const Projects = () => {
ogDescription={t('projects.description')}
ogImage="/placeholder.svg"
/>
-
-
-
-
-
- {t('navigation.home')}
-
-
-
-
- {t('navigation.projects')}
-
-
-
-
+
diff --git a/src/pages/Solutions.tsx b/src/pages/Solutions.tsx
index ce173c0..f4f3eef 100644
--- a/src/pages/Solutions.tsx
+++ b/src/pages/Solutions.tsx
@@ -3,15 +3,8 @@ import { useQuery } from '@tanstack/react-query';
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 } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { supabase } from '@/integrations/supabase';
@@ -102,21 +95,7 @@ const Solutions = () => {
ogDescription={t('solutionsPage.description')}
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 42a3727..f7bb750 100644
--- a/src/pages/blog/[slug].tsx
+++ b/src/pages/blog/[slug].tsx
@@ -8,7 +8,7 @@ import Meta from '@/components/Meta';
import NewsletterSection from '@/components/NewsletterSection';
import CommentsSection from '@/components/blog/CommentsSection';
import { supabase } from '@/integrations/supabase';
-import { getNormalizedLocale } from '@/lib/i18n';
+import { useLocalizedDateFormatter } from '@/hooks/useLocalizedDateFormatter';
import type { Database } from '@/integrations/supabase/types';
import {
Breadcrumb,
@@ -227,23 +227,9 @@ const renderBlogContent = (content: string): ReactNode[] => {
const BlogPostPage = () => {
const { slug } = useParams<{ slug: string }>();
- const { t, i18n } = useTranslation();
-
- const normalizedLocale = useMemo(
- () => getNormalizedLocale(i18n.language),
- [i18n.language]
- );
+ const { t } = useTranslation();
- 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 { formatDate } = useLocalizedDateFormatter({ dateStyle: 'long' });
const {
data: post,
@@ -279,9 +265,7 @@ const BlogPostPage = () => {
[post?.content]
);
- const updatedDate = post
- ? dateFormatter.format(new Date(post.updated_at))
- : '';
+ const updatedDate = post ? formatDate(post.updated_at) : '';
const fallbackMetaTitle = t('blog.metaTitle');
const metaTitle = post
From 23a22a4c6ec5219c906700b751ee1184379a8dad Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 1 Nov 2025 20:15:42 +0000
Subject: [PATCH 3/4] Address code review feedback: improve
useLocalizedDateFormatter with configurable options
Co-authored-by: marcelo-m7 <117441129+marcelo-m7@users.noreply.github.com>
---
src/components/blog/CommentsSection.tsx | 6 ++-
src/hooks/useLocalizedDateFormatter.ts | 52 ++++++++++++++++++-------
src/pages/Blog.tsx | 4 +-
src/pages/Dashboard.tsx | 6 ++-
src/pages/Projects.tsx | 8 ++--
src/pages/blog/[slug].tsx | 4 +-
6 files changed, 57 insertions(+), 23 deletions(-)
diff --git a/src/components/blog/CommentsSection.tsx b/src/components/blog/CommentsSection.tsx
index 4e7947e..b202378 100644
--- a/src/components/blog/CommentsSection.tsx
+++ b/src/components/blog/CommentsSection.tsx
@@ -51,8 +51,10 @@ const CommentsSection = ({ postId }: CommentsSectionProps) => {
const [newComment, setNewComment] = useState('');
const { formatDate } = useLocalizedDateFormatter({
- dateStyle: 'medium',
- timeStyle: 'short',
+ dateOptions: {
+ dateStyle: 'medium',
+ timeStyle: 'short',
+ },
});
const {
diff --git a/src/hooks/useLocalizedDateFormatter.ts b/src/hooks/useLocalizedDateFormatter.ts
index 3b38748..8d292f7 100644
--- a/src/hooks/useLocalizedDateFormatter.ts
+++ b/src/hooks/useLocalizedDateFormatter.ts
@@ -13,21 +13,46 @@ interface DateFormatterOptions {
day?: 'numeric' | '2-digit';
}
+interface UseLocalizedDateFormatterOptions {
+ /**
+ * Intl.DateTimeFormat options for date formatting
+ */
+ dateOptions?: DateFormatterOptions;
+ /**
+ * Fallback options if normalized locale fails (defaults to en-US with same options)
+ */
+ fallbackOptions?: DateFormatterOptions;
+ /**
+ * String to return when date is null/undefined (defaults to '—')
+ */
+ nullFallback?: string;
+}
+
interface UseLocalizedDateFormatterReturn {
formatDate: (date: string | Date | null | undefined) => string;
dateFormatter: Intl.DateTimeFormat;
}
+/**
+ * Helper function to convert date input to Date object
+ */
+const toDateObject = (date: string | Date): Date =>
+ typeof date === 'string' ? new Date(date) : date;
+
/**
* Custom hook for localized date formatting with automatic fallback
- * @param options - Intl.DateTimeFormat options
- * @param fallbackOptions - Fallback options if normalized locale fails (defaults to en-US with same options)
+ * @param options - Configuration options for the date formatter
* @returns Object containing formatDate function and dateFormatter instance
*/
export const useLocalizedDateFormatter = (
- options: DateFormatterOptions = { dateStyle: 'medium' },
- fallbackOptions?: DateFormatterOptions
+ options: UseLocalizedDateFormatterOptions = {}
): UseLocalizedDateFormatterReturn => {
+ const {
+ dateOptions = { dateStyle: 'medium' },
+ fallbackOptions,
+ nullFallback = '—',
+ } = options;
+
const { i18n } = useTranslation();
const normalizedLocale = useMemo(
@@ -36,38 +61,37 @@ export const useLocalizedDateFormatter = (
);
const fallbackDateFormatter = useMemo(
- () =>
- new Intl.DateTimeFormat('en-US', fallbackOptions ?? options),
- [fallbackOptions, options]
+ () => new Intl.DateTimeFormat('en-US', fallbackOptions ?? dateOptions),
+ [fallbackOptions, dateOptions]
);
const dateFormatter = useMemo(() => {
try {
- return new Intl.DateTimeFormat(normalizedLocale, options);
+ return new Intl.DateTimeFormat(normalizedLocale, dateOptions);
} catch (error) {
console.error('Unsupported locale for date formatting', error);
return fallbackDateFormatter;
}
- }, [normalizedLocale, options, fallbackDateFormatter]);
+ }, [normalizedLocale, dateOptions, fallbackDateFormatter]);
const formatDate = useCallback(
(date: string | Date | null | undefined) => {
- if (!date) return '—';
+ if (!date) return nullFallback;
try {
- const dateObject = typeof date === 'string' ? new Date(date) : date;
+ const dateObject = toDateObject(date);
return dateFormatter.format(dateObject);
} catch (error) {
console.error('Error formatting date', error);
try {
- const dateObject = typeof date === 'string' ? new Date(date) : date;
+ const dateObject = toDateObject(date);
return fallbackDateFormatter.format(dateObject);
} catch {
- return '—';
+ return nullFallback;
}
}
},
- [dateFormatter, fallbackDateFormatter]
+ [dateFormatter, fallbackDateFormatter, nullFallback]
);
return { formatDate, dateFormatter };
diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx
index 1326852..443a3bb 100644
--- a/src/pages/Blog.tsx
+++ b/src/pages/Blog.tsx
@@ -87,7 +87,9 @@ const Blog = () => {
const { t } = useTranslation();
const [page, setPage] = useState(1);
- const { formatDate } = useLocalizedDateFormatter({ dateStyle: 'medium' });
+ const { formatDate } = useLocalizedDateFormatter({
+ dateOptions: { dateStyle: 'medium' },
+ });
const categories = useMemo(
() => BLOG_CATEGORY_KEYS.map((key) => t(key)),
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
index c4f3f04..9184e11 100644
--- a/src/pages/Dashboard.tsx
+++ b/src/pages/Dashboard.tsx
@@ -150,8 +150,10 @@ const Dashboard = () => {
}, [loadDashboard]);
const { formatDate } = useLocalizedDateFormatter({
- dateStyle: 'short',
- timeStyle: 'short',
+ dateOptions: {
+ dateStyle: 'short',
+ timeStyle: 'short',
+ },
});
const handleRefresh = () => {
diff --git a/src/pages/Projects.tsx b/src/pages/Projects.tsx
index eabae28..92c788f 100644
--- a/src/pages/Projects.tsx
+++ b/src/pages/Projects.tsx
@@ -42,9 +42,11 @@ const Projects = () => {
const memoizedFallbackSolutions = useMemo(() => getFallbackSolutions(), []);
const { formatDate } = useLocalizedDateFormatter({
- year: 'numeric',
- month: 'short',
- day: 'numeric',
+ dateOptions: {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ },
});
const {
diff --git a/src/pages/blog/[slug].tsx b/src/pages/blog/[slug].tsx
index f7bb750..9c4c274 100644
--- a/src/pages/blog/[slug].tsx
+++ b/src/pages/blog/[slug].tsx
@@ -229,7 +229,9 @@ const BlogPostPage = () => {
const { slug } = useParams<{ slug: string }>();
const { t } = useTranslation();
- const { formatDate } = useLocalizedDateFormatter({ dateStyle: 'long' });
+ const { formatDate } = useLocalizedDateFormatter({
+ dateOptions: { dateStyle: 'long' },
+ });
const {
data: post,
From 1bf10d22ad3efd0e50f1c605779b86185c135d07 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 1 Nov 2025 20:17:26 +0000
Subject: [PATCH 4/4] Improve date validation and error handling in
useLocalizedDateFormatter
Co-authored-by: marcelo-m7 <117441129+marcelo-m7@users.noreply.github.com>
---
src/hooks/useLocalizedDateFormatter.ts | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/src/hooks/useLocalizedDateFormatter.ts b/src/hooks/useLocalizedDateFormatter.ts
index 8d292f7..48432f6 100644
--- a/src/hooks/useLocalizedDateFormatter.ts
+++ b/src/hooks/useLocalizedDateFormatter.ts
@@ -35,9 +35,18 @@ interface UseLocalizedDateFormatterReturn {
/**
* Helper function to convert date input to Date object
+ * @throws Error if date is invalid
*/
-const toDateObject = (date: string | Date): Date =>
- typeof date === 'string' ? new Date(date) : date;
+const toDateObject = (date: string | Date): Date => {
+ const dateObj = typeof date === 'string' ? new Date(date) : date;
+
+ // Check if date is valid
+ if (Number.isNaN(dateObj.getTime())) {
+ throw new Error('Invalid date');
+ }
+
+ return dateObj;
+};
/**
* Custom hook for localized date formatting with automatic fallback
@@ -80,15 +89,16 @@ export const useLocalizedDateFormatter = (
try {
const dateObject = toDateObject(date);
- return dateFormatter.format(dateObject);
- } catch (error) {
- console.error('Error formatting date', error);
+
try {
- const dateObject = toDateObject(date);
+ return dateFormatter.format(dateObject);
+ } catch (formattingError) {
+ console.error('Error formatting date with primary formatter', formattingError);
return fallbackDateFormatter.format(dateObject);
- } catch {
- return nullFallback;
}
+ } catch (dateError) {
+ console.error('Invalid date provided', dateError);
+ return nullFallback;
}
},
[dateFormatter, fallbackDateFormatter, nullFallback]