diff --git a/components/Common/Banner/index.stories.tsx b/components/Common/Banner/index.stories.tsx index 4a823cd36cc9f..cf489c8d983a6 100644 --- a/components/Common/Banner/index.stories.tsx +++ b/components/Common/Banner/index.stories.tsx @@ -7,31 +7,31 @@ type Meta = MetaObj; export const Default: Story = { args: { - text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + children: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', type: 'default', - url: 'https://github.com/openjs-foundation/summit/issues/360', + link: 'https://github.com/openjs-foundation/summit/issues/360', }, }; export const Error: Story = { args: { - text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + children: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', type: 'error', - url: 'https://github.com/nodejs/nodejs.org/issues/4495', + link: 'https://github.com/nodejs/nodejs.org/issues/4495', }, }; export const Warning: Story = { args: { - text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + children: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', type: 'warning', - url: 'https://github.com/nodejs/nodejs.org/issues/4495', + link: 'https://github.com/nodejs/nodejs.org/issues/4495', }, }; export const NoLink: Story = { args: { - text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + children: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', type: 'default', }, }; diff --git a/components/Common/Banner/index.tsx b/components/Common/Banner/index.tsx index 6c5f8fb3aacd9..f72b98e15a79e 100644 --- a/components/Common/Banner/index.tsx +++ b/components/Common/Banner/index.tsx @@ -1,20 +1,23 @@ import { ArrowUpRightIcon } from '@heroicons/react/24/outline'; -import type { FC } from 'react'; +import type { FC, PropsWithChildren } from 'react'; import Link from '@/components/Link'; import styles from './index.module.css'; type BannerProps = { - type: 'default' | 'error' | 'warning'; - text: string; - url?: string; + link?: string; + type?: 'default' | 'warning' | 'error'; }; -const Banner: FC = ({ type, text, url = '' }) => ( +const Banner: FC> = ({ + type = 'default', + link, + children, +}) => (
- {(url.length > 0 && {text}) || text} - {url.length > 0 && } + {link ? {children} : children} + {link && }
); diff --git a/components/withBadge.tsx b/components/withBadge.tsx new file mode 100644 index 0000000000000..04e68b70ec206 --- /dev/null +++ b/components/withBadge.tsx @@ -0,0 +1,21 @@ +import type { FC } from 'react'; + +import Badge from '@/components/Common/Badge'; +import { siteConfig } from '@/next.json.mjs'; +import { dateIsBetween } from '@/util/dateIsBetween'; + +const WithBadge: FC<{ section: string }> = ({ section }) => { + const badge = siteConfig.websiteBadges[section]; + + if (badge && dateIsBetween(badge.startDate, badge.endDate)) { + return ( + + {badge.text} + + ); + } + + return null; +}; + +export default WithBadge; diff --git a/components/withBanner.tsx b/components/withBanner.tsx new file mode 100644 index 0000000000000..6ec0a358da0c7 --- /dev/null +++ b/components/withBanner.tsx @@ -0,0 +1,21 @@ +import type { FC } from 'react'; + +import Banner from '@/components/Common/Banner'; +import { siteConfig } from '@/next.json.mjs'; +import { dateIsBetween } from '@/util/dateIsBetween'; + +const WithBanner: FC<{ section: string }> = ({ section }) => { + const banner = siteConfig.websiteBanners[section]; + + if (banner && dateIsBetween(banner.startDate, banner.endDate)) { + return ( + + {banner.text} + + ); + } + + return null; +}; + +export default WithBanner; diff --git a/components/withNavBar.tsx b/components/withNavBar.tsx index 26387b1555358..54fa0a51c7c5c 100644 --- a/components/withNavBar.tsx +++ b/components/withNavBar.tsx @@ -5,6 +5,7 @@ import { useTheme } from 'next-themes'; import type { FC } from 'react'; import NavBar from '@/components/Containers/NavBar'; +import WithBanner from '@/components/withBanner'; import { useClientContext, useSiteNavigation } from '@/hooks'; import { useRouter } from '@/navigation.mjs'; import { availableLocales } from '@/next.locales.mjs'; @@ -21,18 +22,22 @@ const WithNavBar: FC = () => { setTheme(resolvedTheme === 'dark' ? 'light' : 'dark'); return ( - replace(pathname!, { locale: locale.code }), - }} - navItems={navigationItems.map(([, { label, link }]) => ({ - link, - text: label, - }))} - /> + <> + + + replace(pathname!, { locale: locale.code }), + }} + navItems={navigationItems.map(([, { label, link }]) => ({ + link, + text: label, + }))} + /> + ); }; diff --git a/site.json b/site.json index 42c359909835d..bab69adfe931e 100644 --- a/site.json +++ b/site.json @@ -25,14 +25,8 @@ "category": "vulnerability" } ], - "websiteBanners": { - "index": { - "startDate": "2023-11-26T00:00:00.000Z", - "endDate": "2023-12-05T00:00:00.000Z", - "link": "https://training.linuxfoundation.org/cyber-monday-js-2023/?utm_source=openjsf&utm_medium=homepage-ticker&utm_campaign=cybermonday23", - "html": "Node.js Training Banner" - } - }, + "websiteBanners": {}, + "websiteBadges": {}, "footerLinks": [ { "link": "https://openjsf.org/wp-content/uploads/sites/84/2021/01/OpenJS-Foundation-Trademark-Policy-2021-01-12.docx.pdf", diff --git a/types/config.ts b/types/config.ts index b4e1c566febdf..408b8fff9951e 100644 --- a/types/config.ts +++ b/types/config.ts @@ -1,4 +1,4 @@ -import type { RSSFeed, WebsiteBanner } from './features'; +import type { RSSFeed, WebsiteBadge, WebsiteBanner } from './features'; export interface TwitterConfig { username: string; @@ -34,6 +34,7 @@ export interface SiteConfig { twitter: TwitterConfig; rssFeeds: Array; websiteBanners: Record; + websiteBadges: Record; footerLinks: Array; socialLinks: Array; } diff --git a/types/features.ts b/types/features.ts index e893478477ea1..1e0ab12a8f735 100644 --- a/types/features.ts +++ b/types/features.ts @@ -5,10 +5,22 @@ export interface RSSFeed { blogCategory?: string; } -export interface WebsiteBanner { +interface WithRange { startDate: string; endDate: string; - text?: string; +} + +export interface WebsiteBanner extends WithRange { + text: string; + link?: string; + /** @deprecated the html field is unsupported on the website redesign */ html?: string; + type?: 'default' | 'warning' | 'error'; +} + +export interface WebsiteBadge extends WithRange { + text: string; link: string; + title?: string; + kind?: 'default' | 'warning' | 'error'; } diff --git a/types/releases.ts b/types/releases.ts index 78db7a528d923..b3ff09379375f 100644 --- a/types/releases.ts +++ b/types/releases.ts @@ -1,14 +1,3 @@ -export interface UpcomingReleaseData { - releaseDate: string; - releaseType: 'Current' | 'LTS' | 'Maintenance' | 'End-of-life'; - alreadyReleased: boolean; -} - -export interface UpcomingRelease { - name: string; - releases: UpcomingReleaseData[]; -} - export type NodeReleaseStatus = | 'Maintenance LTS' | 'Active LTS' diff --git a/util/__tests__/dateIsBetween.test.mjs b/util/__tests__/dateIsBetween.test.mjs index a677c418ab9db..f66215b64d276 100644 --- a/util/__tests__/dateIsBetween.test.mjs +++ b/util/__tests__/dateIsBetween.test.mjs @@ -19,12 +19,12 @@ describe('dateIsBetween', () => { expect(result).toBe(false); }); - it('returns false when either start or end date is invalid', () => { + it('throws when either start or end date is invalid', () => { const invalidStartDate = 'Invalid Start Date'; const validEndDate = '2024-01-01T00:00:00.000Z'; - const result = dateIsBetween(invalidStartDate, validEndDate); - - expect(result).toBe(false); + expect(() => dateIsBetween(invalidStartDate, validEndDate)).toThrow( + 'dateIsBetween got called with invalid dates' + ); }); }); diff --git a/util/dateIsBetween.ts b/util/dateIsBetween.ts index 6bbbe75e01d92..77ff329361671 100644 --- a/util/dateIsBetween.ts +++ b/util/dateIsBetween.ts @@ -4,7 +4,7 @@ export const dateIsBetween = (startDate: string, endDate: string): boolean => { const end = new Date(endDate); if ([start.toString(), end.toString()].includes(invalidDateStr)) { - return false; + throw new Error('dateIsBetween got called with invalid dates'); } const now = new Date();