Skip to content

Commit d45ec56

Browse files
authored
add list of partnet in homepage
1 parent 627dbe2 commit d45ec56

File tree

9 files changed

+162
-0
lines changed

9 files changed

+162
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Skeleton from '@node-core/ui-components/Common/Skeleton';
2+
import type { ComponentProps, FC, ReactElement } from 'react';
3+
import { cloneElement } from 'react';
4+
5+
import type { Partners } from '#site/types';
6+
7+
import Button from '../../Button';
8+
9+
type ParnetsIconProps = Partners & ComponentProps<typeof Skeleton>;
10+
11+
const PartnersIcon: FC<ParnetsIconProps> = ({ href, logo, loading }) => {
12+
return (
13+
<Skeleton loading={loading}>
14+
<Button href={href} kind="secondary">
15+
{cloneElement(logo as ReactElement)}
16+
</Button>
17+
</Skeleton>
18+
);
19+
};
20+
21+
export default PartnersIcon;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.partnersIconList {
2+
@apply flex
3+
flex-row
4+
flex-wrap
5+
items-center
6+
justify-center
7+
gap-4;
8+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use client';
2+
3+
import { useEffect, useRef, useState, type FC } from 'react';
4+
5+
import PARTNERS from '#site/next.partners.constants';
6+
import type { Partners } from '#site/types';
7+
8+
import PartnerIcon from '../PartnerIcon';
9+
import style from './index.module.css';
10+
import { randomPartnerList } from '../utils';
11+
12+
type PartnersIconListProps = {
13+
maxLength?: number;
14+
};
15+
16+
const PartnersIconList: FC<PartnersIconListProps> = ({ maxLength = 4 }) => {
17+
const initialRenderer = useRef(true);
18+
19+
const [seedList, setSeedList] = useState<Array<Partners>>(
20+
PARTNERS.slice(0, maxLength)
21+
);
22+
23+
useEffect(() => {
24+
// We intentionally render the initial default "mock" list of sponsors
25+
// to have the Skeletons loading, and then we render the actual list
26+
// after an enough amount of time has passed to give a proper sense of Animation
27+
// We do this client-side effect, to ensure that a random-amount of sponsors is renderered
28+
// on every page load. Since our page is natively static, we need to ensure that
29+
// on the client-side we have a random amount of sponsors rendered.
30+
// Although whilst we are deployed on Vercel or other environment that supports ISR
31+
// (Incremental Static Generation) whose would invalidate the cache every 5 minutes
32+
// We want to ensure that this feature is compatible on a full-static environment
33+
const renderSponsorsAnimation = setTimeout(() => {
34+
initialRenderer.current = false;
35+
36+
setSeedList(randomPartnerList(PARTNERS, maxLength, 1));
37+
}, 0);
38+
39+
return () => clearTimeout(renderSponsorsAnimation);
40+
// We only want this to run once on initial render
41+
// We don't really care if the props change as realistically they shouldn't ever
42+
// eslint-disable-next-line react-hooks/exhaustive-deps
43+
}, []);
44+
45+
return (
46+
<div className={style.partnersIconList}>
47+
{seedList.map((partner, index) => (
48+
<PartnerIcon
49+
{...partner}
50+
key={index}
51+
loading={initialRenderer.current}
52+
/>
53+
))}
54+
</div>
55+
);
56+
};
57+
58+
export default PartnersIconList;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { Partners } from '#site/types/partners.js';
2+
3+
function randomPartnerList(partners: Array<Partners>, pick = 4, dateSeed = 5) {
4+
const now = new Date();
5+
const minutes = Math.floor(now.getUTCMinutes() / dateSeed) * dateSeed;
6+
7+
const fixedTime = new Date(
8+
Date.UTC(
9+
now.getUTCFullYear(),
10+
now.getUTCMonth(),
11+
now.getUTCDate(),
12+
now.getUTCHours(),
13+
minutes,
14+
0,
15+
0
16+
)
17+
);
18+
19+
// We create a seed from the rounded date (timestamp in ms)
20+
const seed = fixedTime.getTime();
21+
const rng = mulberry32(seed);
22+
23+
// Create a copy of the array to avoid modifying the original
24+
const shuffled = partners.slice().sort(() => rng() - 0.5);
25+
26+
return shuffled.slice(0, pick);
27+
}
28+
29+
// This function returns a random list of partners based on a fixed time seed
30+
31+
function mulberry32(seed: number) {
32+
return function () {
33+
let t = (seed += 0x6d2b79f5);
34+
t = Math.imul(t ^ (t >>> 15), t | 1);
35+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
36+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
37+
};
38+
}
39+
40+
export { randomPartnerList };

apps/site/next.mdx.use.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import BadgeGroup from '@node-core/ui-components/Common/BadgeGroup';
44

5+
import PartnersIconList from './components/Common/Partners/PartnersIconList';
56
import DownloadReleasesTable from './components/Downloads/DownloadReleasesTable';
67
import UpcomingMeetings from './components/MDX/Calendar/UpcomingMeetings';
78
import WithBadgeGroup from './components/withBadgeGroup';
@@ -21,6 +22,7 @@ export const mdxComponents = {
2122
WithBanner,
2223
// HOC for providing Badge Data
2324
WithBadgeGroup,
25+
PartnersIconList,
2426
// Standalone Badge Group
2527
BadgeGroup,
2628
// Renders an container for Upcoming Node.js Meetings

apps/site/next.partners.constants.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { Partners } from '#site/types';
2+
import { partnersList } from '#site/util/partners';
3+
import partners from '#site/util/partners/constants.json' with { type: 'json' };
4+
5+
const PARTNERS = partnersList(partners as Array<Omit<Partners, 'logo'>>);
6+
7+
export default PARTNERS as Array<Partners>;

apps/site/pages/en/index.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ layout: home
2727
<small className="!text-xs">for Node.js 18 and below</small>
2828
</Button>
2929

30+
<div className='flex flex-row gap-4 items-center'>
31+
<PartnersIconList />
32+
<Link href="/about/partners">and more...</Link>
33+
</div>
34+
3035
<div className="flex flex-col xs:flex-row gap-2 justify-center xs:mt-6">
3136
<WithNodeRelease status="Active LTS">
3237
{({ release }) =>

apps/site/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export * from './redirects';
1010
export * from './server';
1111
export * from './github';
1212
export * from './calendar';
13+
export * from './partners';
1314
export * from './author';
1415
export * from './download';
1516
export * from './userAgent';

apps/site/types/partners.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// import type { ReactElement, SVGProps } from 'react';
2+
3+
import type { ReactElement, SVGProps } from 'react';
4+
5+
export interface Partners {
6+
id: string;
7+
// The name of the partner
8+
name: string;
9+
// A logo to render on the partners page
10+
logo: ReactElement<SVGProps<SVGSVGElement>>;
11+
// The promoted link to their website or social media
12+
href: string;
13+
// The categories this partner belongs to
14+
categories: Array<PartnerCategory>;
15+
// An optional description of the partner
16+
description?: string;
17+
threshold: number;
18+
}
19+
20+
export type PartnerCategory = 'infrastructure' | 'security' | 'esp partner';

0 commit comments

Comments
 (0)