Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions components/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import moment from 'moment';
import React from 'react';
import { twMerge } from 'tailwind-merge';

import type { IEvent } from '@/types/event';
import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';

import eventsData from '../config/meetings.json';
import { formatDate, formatDateWithTimezone, isDateAfter } from '../utils/dateHelpers';
import { useTranslation } from '../utils/i18n';
import { getEvents } from '../utils/staticHelpers';
import GoogleCalendarButton from './buttons/GoogleCalendarButton';
Expand All @@ -29,7 +29,7 @@ export default function Calendar({ className = '', size }: ICalendarProps) {
const CALENDAR_URL =
'https://calendar.google.com/calendar/embed?src=c_q9tseiglomdsj6njuhvbpts11c%40group.calendar.google.com&ctz=UTC';
const currentDate = new Date();
const eventsExist = eventsData?.filter((event: IEvent) => moment(event.date).isAfter(currentDate)).length > 0;
const eventsExist = eventsData?.filter((event: IEvent) => isDateAfter(event.date, currentDate)).length > 0;

return (
<div
Expand All @@ -46,14 +46,11 @@ export default function Calendar({ className = '', size }: ICalendarProps) {
<li key={index} data-testid='Calendar-list-item'>
<a href={event.url} className='mb-1 mt-2 flex grow flex-col items-start sm:flex-row sm:items-center'>
<div className='inline-flex h-12 min-w-12 flex-row rounded-full bg-pink-500 font-bold text-white'>
<span className='flex-1 self-center text-center'>{moment(event.date).format('D')}</span>
<span className='flex-1 self-center text-center'>{formatDate(event.date, 'd')}</span>
</div>
<div className='grow text-left sm:mt-0 sm:pl-6'>
<h2 className='title-font font-medium text-gray-900 hover:text-gray-500'>{event.title}</h2>
<p className='text-gray-600'>
{moment(event.date).local().format('LLLL')} UTC
{moment(event.date).local().format('Z')}
</p>
<p className='text-gray-600'>{formatDateWithTimezone(event.date)}</p>
</div>
</a>
</li>
Expand Down
13 changes: 7 additions & 6 deletions components/campaigns/AnnouncementRemainingDays.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import moment from 'moment';
import React from 'react';

import { getDaysDifference, getHoursDifference, getMinutesDifference, parseDate } from '../../utils/dateHelpers';

interface AnnouncementRemainingDaysProps {
dateTime: string;
eventName: string;
Expand All @@ -12,11 +13,11 @@ interface AnnouncementRemainingDaysProps {
* @param {string} props.eventName - The name of the event
*/
export default function AnnouncementRemainingDays({ dateTime, eventName }: AnnouncementRemainingDaysProps) {
const date = moment(dateTime);
const now = moment();
const days = date.diff(now, 'days');
const hours = date.diff(now, 'hours');
const minutes = date.diff(now, 'minutes');
const date = parseDate(dateTime);
const now = new Date();
const days = getDaysDifference(date, now);
const hours = getHoursDifference(date, now);
const minutes = getMinutesDifference(date, now);

let text = '';

Expand Down
4 changes: 2 additions & 2 deletions components/layout/BlogLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import moment from 'moment';
import ErrorPage from 'next/error';
import HtmlHead from 'next/head';
import { useRouter } from 'next/router';
Expand All @@ -7,6 +6,7 @@ import React from 'react';
import type { IPosts } from '@/types/post';

import BlogContext from '../../context/BlogContext';
import { formatDate } from '../../utils/dateHelpers';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check frontmatter date fields in markdown files
fd -e md -e mdx | xargs rg -n '^date:\s*' -A 0 || true

Repository: asyncapi/website

Length of output: 12099


🏁 Script executed:

# Find and examine the formatDate implementation
fd -e ts -e tsx -e js -e jsx | xargs rg -l 'formatDate|parseISO' | head -20

Repository: asyncapi/website

Length of output: 321


🏁 Script executed:

# Get context around line 74 in BlogLayout.tsx
fd -e tsx | xargs grep -l 'BlogLayout' | head -1 | xargs sed -n '70,80p'

Repository: asyncapi/website

Length of output: 543


🏁 Script executed:

# Check dateHelpers implementation
fd -e ts -e tsx -e js -e jsx -path '*/utils/*' | xargs grep -l 'formatDate\|parseISO' || true

Repository: asyncapi/website

Length of output: 228


🏁 Script executed:

# Read dateHelpers.ts implementation
fd -name 'dateHelpers.ts' -o -name 'dateHelpers.js' | xargs cat

Repository: asyncapi/website

Length of output: 290


🏁 Script executed:

# Check how blog posts are loaded/parsed
fd -name '*blog*' -type f | xargs rg -l 'getAllPosts\|getBlogPosts\|frontmatter\|gray-matter' | head -5

Repository: asyncapi/website

Length of output: 290


🏁 Script executed:

# Search for blog data loading in pages or API routes
fd -name 'index.tsx' -o -name 'index.ts' | xargs rg -l 'blog\|post' | head -5

Repository: asyncapi/website

Length of output: 290


🏁 Script executed:

# Find and read dateHelpers.ts
find . -name 'dateHelpers.ts' -o -name 'dateHelpers.js' | head -1 | xargs cat

Repository: asyncapi/website

Length of output: 2086


🏁 Script executed:

# Search for blog data loading utilities
find . -type f -name '*.ts' -o -name '*.tsx' | xargs grep -l 'getAllPosts\|getBlogPosts' 2>/dev/null | head -5

Repository: asyncapi/website

Length of output: 234


🏁 Script executed:

# Check BlogLayout context around line 74 and 9
find . -name 'BlogLayout.tsx' | xargs cat

Repository: asyncapi/website

Length of output: 3720


🏁 Script executed:

# Find where blog posts are loaded/parsed
find . -type f \( -name '*.ts' -o -name '*.tsx' \) | xargs grep -l 'post.date\|getPosts\|getAllPosts' 2>/dev/null | head -10

Repository: asyncapi/website

Length of output: 410


🏁 Script executed:

# Check for blog data transformation/frontmatter parsing
find . -type f \( -name '*.ts' -o -name '*.tsx' \) | xargs grep -l 'frontmatter\|gray-matter\|parseISO' 2>/dev/null | head -10

Repository: asyncapi/website

Length of output: 188


🏁 Script executed:

# Look for the types definition to understand what post.date is
find . -type f -name 'post.ts' -o -name 'post.d.ts' -o -name '*.ts' | xargs grep -l 'interface IPosts\|type IPosts' 2>/dev/null | head -5

Repository: asyncapi/website

Length of output: 75


🏁 Script executed:

# Read the post types definition
cat ./types/post.ts

Repository: asyncapi/website

Length of output: 621


🏁 Script executed:

# Read build-post-list.ts to see date processing
cat ./scripts/build-post-list.ts

Repository: asyncapi/website

Length of output: 13127


🏁 Script executed:

# Check utils/api.ts for date handling
cat ./utils/api.ts

Repository: asyncapi/website

Length of output: 1359


Add error handling for date parsing in formatDate.
parseISO from date-fns will fail on non-ISO dates and return Invalid Date, which breaks both the rendered date display and the dateTime attribute. While most blog dates are ISO-8601 compliant, malformed dates with missing leading zeros (e.g., 7:26:58 instead of 07:26:58) have been found in the frontmatter and will cause rendering issues. Add a fallback to gracefully handle parsing failures:

export function formatDate(date: string | Date, formatStr: string): string {
  let parsedDate: Date;
  try {
    parsedDate = typeof date === 'string' ? parseISO(date) : date;
  } catch (e) {
    parsedDate = new Date(date); // fallback to native Date parsing
  }
  return isValid(parsedDate) ? format(parsedDate, formatStr) : 'Invalid date';
}

Also applies to: 74-74

🤖 Prompt for AI Agents
In `@components/layout/BlogLayout.tsx` at line 9, The formatDate helper currently
uses parseISO and can produce an Invalid Date for non-ISO inputs; update
formatDate to catch parse errors and fall back to native Date parsing, then
verify validity with isValid before formatting: in the formatDate function (and
any duplicate at line ~74), try parsing with parseISO for strings but catch
exceptions and assign new Date(date) as a fallback, then return
format(parsedDate, formatStr) only if isValid(parsedDate), otherwise return a
safe placeholder like 'Invalid date' to avoid broken rendered output and invalid
dateTime attributes.

import AuthorAvatars from '../AuthorAvatars';
import AnnouncementHero from '../campaigns/AnnouncementHero';
import Head from '../Head';
Expand Down Expand Up @@ -71,7 +71,7 @@ export default function BlogLayout({ post, children }: IBlogLayoutProps) {
</span>
</p>
<div className='flex text-sm leading-5 text-gray-500'>
<time dateTime={post.date}>{moment(post.date).format('MMMM D, YYYY')}</time>
<time dateTime={post.date}>{formatDate(post.date, 'MMMM d, yyyy')}</time>
<span className='mx-1'>&middot;</span>
<span>{post.readingTime} min read</span>
</div>
Expand Down
4 changes: 2 additions & 2 deletions components/navigation/BlogPostItem.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import moment from 'moment';
import Link from 'next/link';
import type { Ref } from 'react';
import React, { forwardRef } from 'react';
Expand All @@ -8,6 +7,7 @@ import { BlogPostType } from '@/types/components/navigation/BlogPostType';
import type { IBlogPost } from '@/types/post';
import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';
import { ParagraphTypeStyle } from '@/types/typography/Paragraph';
import { formatDate } from '@/utils/dateHelpers';

import AuthorAvatars from '../AuthorAvatars';
import Heading from '../typography/Heading';
Expand Down Expand Up @@ -129,7 +129,7 @@ const BlogPostItem = ({ post, className = '', id = '' }: BlogPostItemProps, ref:
</span>
</Heading>
<Paragraph typeStyle={ParagraphTypeStyle.sm} className='flex'>
<time dateTime={post.date}>{moment(post.date).format('MMMM D, YYYY')}</time>
<time dateTime={post.date}>{formatDate(post.date, 'MMMM d, yyyy')}</time>
<span className='mx-1'>&middot;</span>
<span>{post.readingTime} min read</span>
</Paragraph>
Expand Down
12 changes: 6 additions & 6 deletions components/navigation/EventFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import moment from 'moment';
import React, { useEffect, useState } from 'react';

import type { IEvent } from '@/types/event';
import { isDateAfter, isDateBefore } from '@/utils/dateHelpers';

import { getEvents } from '../../utils/staticHelpers';

Expand All @@ -23,35 +23,35 @@ interface EventFilterProps {
* @param {React.Dispatch<React.SetStateAction<IEvent[]>>} props.setData - The function to update the filtered events.
*/
export default function EventFilter({ data, setData }: EventFilterProps) {
const localTime = moment().format('YYYY-MM-DD');
const currentDate = `${localTime}T00:00:00.000Z`;
const filterList: string[] = ['All', 'Upcoming', 'Recorded'];
const [active, setActive] = useState<string>('All');

useEffect(() => {
const currentDate = new Date();

switch (active) {
case ActiveState.All:
setData(getEvents(data));
break;
case ActiveState.Upcoming:
setData(
getEvents(data).filter((event: IEvent) => {
return moment(event.date).format() > currentDate;
return isDateAfter(event.date, currentDate);
})
);
break;
case ActiveState.Recorded:
setData(
getEvents(data).filter((event: IEvent) => {
return moment(event.date).format() < currentDate;
return isDateBefore(event.date, currentDate);
})
);
break;
default:
setData(getEvents(data));
break;
}
}, [active, data, setData, currentDate]);
}, [active, data, setData]);

return (
<div
Expand Down
19 changes: 10 additions & 9 deletions components/navigation/EventPostItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ArrowRightIcon } from '@heroicons/react/outline';
import moment from 'moment';
import React from 'react';

import type { IEvent } from '@/types/event';
import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';
import { formatDate, isDateBefore, parseDate } from '@/utils/dateHelpers';

import IconCalendar from '../icons/Calendar';
import Community from '../icons/Community';
Expand All @@ -25,8 +25,7 @@ interface EventPostItemProps {
*
*/
function EventPostItem({ post, className = '', id }: EventPostItemProps): React.JSX.Element {
const localTime = moment().format('YYYY-MM-DD'); // store localTime
const currentDate = `${localTime}T00:00:00.000Z`;
const currentDate = new Date();
const title = post.title || '';
let color = '';
let icon: React.ReactElement | null = null;
Expand All @@ -48,12 +47,14 @@ function EventPostItem({ post, className = '', id }: EventPostItemProps): React.

const defaultCover = 'https://github.com/asyncapi/community/assets/40604284/01c2b8de-fa5c-44dd-81a5-70cb96df4813';
let active = true;
const postDate = moment(post.date); // Convert post.date to a moment object if necessary
let postDate: Date = new Date();

if (!postDate.isValid()) {
// Handle invalid date if necessary
active = false;
} else if (currentDate > postDate.format()) {
try {
postDate = parseDate(post.date);
if (isDateBefore(postDate, currentDate)) {
active = false;
}
} catch {
active = false;
}

Expand All @@ -80,7 +81,7 @@ function EventPostItem({ post, className = '', id }: EventPostItemProps): React.
<div className='flex items-center'>
<IconCalendar />
<span className='ml-4 text-sm font-semibold' data-testid='Event-span'>
{active ? moment(postDate).format('MMMM D, YYYY') : 'View Recording'}
{active ? formatDate(postDate, 'MMMM d, yyyy') : 'View Recording'}
</span>
<ArrowRightIcon className='ml-3 w-4' />
</div>
Expand Down
4 changes: 2 additions & 2 deletions components/newsroom/FeaturedBlogPost.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import moment from 'moment';
import Link from 'next/link';
import React from 'react';
import TextTruncate from 'react-text-truncate';
Expand All @@ -7,6 +6,7 @@ import { BlogPostType } from '@/types/components/navigation/BlogPostType';
import type { IBlogPost } from '@/types/post';
import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';
import { ParagraphTypeStyle } from '@/types/typography/Paragraph';
import { formatDate } from '@/utils/dateHelpers';

import AuthorAvatars from '../AuthorAvatars';
import Heading from '../typography/Heading';
Expand Down Expand Up @@ -100,7 +100,7 @@ export default function FeaturedBlogPost({ post, className = '' }: FeaturedBlogP
</Heading>
<Paragraph typeStyle={ParagraphTypeStyle.sm} className='flex'>
<time dateTime={post.date} data-testid='FeaturedBlogPost-date'>
{moment(post.date).format('MMMM D, YYYY')}
{formatDate(post.date, 'MMMM d, yyyy')}
</time>
<span className='mx-1'>&middot;</span>
<span data-testid='FeaturedBlogPost-RT'>{post.readingTime} min read</span>
Expand Down
21 changes: 11 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"axios": "^1.12.1",
"clsx": "^2.1.0",
"cssnano": "^6.0.3",
"date-fns": "^4.1.0",
"dedent": "^1.5.1",
"dotenv": "^16.4.4",
"fs-extra": "^11.2.0",
Expand All @@ -89,7 +90,6 @@
"markdown-toc": "^1.2.0",
"md5": "^2.3.0",
"mermaid": "9.3.0",
"moment": "^2.30.1",
"next": "15.5.9",
"next-i18next": "^15.3.0",
"next-language-detector": "^1.1.1",
Expand Down
11 changes: 4 additions & 7 deletions pages/community/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type moment from 'moment';
import React from 'react';

import { CardType } from '@/types/components/community/CardPropsType';
Expand All @@ -12,11 +11,12 @@ import GenericLayout from '../../components/layout/GenericLayout';
import NewsletterSubscribe from '../../components/NewsletterSubscribe';
import Heading from '../../components/typography/Heading';
import eventsData from '../../config/meetings.json';
import { formatDate, formatDateWithTimezone } from '../../utils/dateHelpers';
import { getEvents } from '../../utils/staticHelpers';

interface Event {
title: string;
date: moment.Moment;
date: Date;
url: string;
}

Expand Down Expand Up @@ -127,14 +127,11 @@ export default function CommunityIndexPage() {
<li key={index} className='mt-2 w-full rounded-l-md bg-white p-2 md:p-10'>
<a href={event.url} className='flex'>
<div className='inline-flex h-12 min-w-12 flex-row rounded-full bg-pink-500 font-bold text-white'>
<span className='flex-1 self-center text-center'>{event.date.format('D')}</span>
<span className='flex-1 self-center text-center'>{formatDate(event.date, 'd')}</span>
</div>
<div className='ml-4 text-left'>
<h1 className='text-md md:text-lg'>{event.title}</h1>
<span className='text-xs text-gray-500 md:text-sm'>
{event.date.local().format('LLLL')} UTC
{event.date.local().format('Z')}
</span>
<span className='text-xs text-gray-500 md:text-sm'>{formatDateWithTimezone(event.date)}</span>
</div>
</a>
</li>
Expand Down
4 changes: 2 additions & 2 deletions scripts/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
*/

import dedent from 'dedent';
import { format } from 'date-fns';
import fs from 'fs';
import inquirer from 'inquirer';
import moment from 'moment';

import { logger } from './helpers/logger';

Expand Down Expand Up @@ -41,7 +41,7 @@ function genFrontMatter(answers: ComposePromptType): string {

let frontMatter = dedent`---
title: ${answers.title ? answers.title : 'Untitled'}
date: ${moment().format('YYYY-MM-DDTh:mm:ssZ')}
date: ${format(new Date(), "yyyy-MM-dd'T'HH:mm:ssXXX")}
type: ${answers.type}
canonical: ${answers.canonical ? answers.canonical : ''}
tags: [${answers.tags ? tags : ''}]
Expand Down
Loading