Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
33dcf64
chore: add partner section on homepage
bjohansebas Jul 21, 2025
5094798
chore: template for partner page
bjohansebas Jul 21, 2025
6a5568c
ui: add name tooltip for partnerIcon
bjohansebas Jul 21, 2025
e646e29
chore: implement list of logos on download and partners page
bjohansebas Jul 22, 2025
8302e8a
chore: add partner list on security blog post
bjohansebas Jul 22, 2025
2f0e2be
feat: enhance PartnersLogoList to filter partners by category and all…
bjohansebas Jul 22, 2025
435eb91
feat: add new partner categories and update partner weights
bjohansebas Jul 22, 2025
5fd31f8
feat: update partner selection logic to use weighted randomization
bjohansebas Jul 22, 2025
91b6535
feat: update button href to include UTM parameters for tracking
bjohansebas Jul 22, 2025
5c79be3
feat: implement padding to a company tooltip
bjohansebas Jul 23, 2025
8c55aaf
chore: implement supporters component
bjohansebas Jul 24, 2025
86cebfa
feat: enhance partners page with detailed descriptions
bjohansebas Jul 24, 2025
9570258
ui-components: add more partner logos
bjohansebas Jul 24, 2025
96bc9f9
ui-components: add more partners logos
bjohansebas Jul 24, 2025
edf5259
clean up
bjohansebas Jul 25, 2025
625ed9c
feat: update supporters and partners sections
bjohansebas Jul 26, 2025
cdaa6d6
feat: enhance partner list functionality with sorting options
bjohansebas Jul 26, 2025
d737364
fix typos and small nits
bjohansebas Jul 30, 2025
4da9fad
refactor: update partners and supporters sections by removing unused …
bjohansebas Jul 30, 2025
26c9f8c
feat: add comprehensive partners documentation outlining addition, re…
bjohansebas Jul 30, 2025
844cd09
style: center-align partner support heading and adjust text balance
bjohansebas Jul 30, 2025
13bd737
Merge branch 'main' of https://github.com/nodejs/nodejs.org into part…
bjohansebas Sep 2, 2025
33016f4
fixup lint error
bjohansebas Sep 2, 2025
2e0d066
Merge branch 'main' of https://github.com/nodejs/nodejs.org into part…
bjohansebas Sep 16, 2025
32b876d
apply suggestions
bjohansebas Sep 16, 2025
31db2c5
refactor: simplify partner list components by introducing usePartners…
bjohansebas Sep 16, 2025
0f3b343
chore: empty spaces removed from svg files
canerakdas Sep 29, 2025
02222b0
chore: gql to rest
canerakdas Sep 29, 2025
6264c99
chore: comment lines removed
canerakdas Sep 29, 2025
c12045e
fix: remove auto width from svg components
canerakdas Sep 29, 2025
9195ee0
chore: tooltip styling
canerakdas Sep 29, 2025
5b470d5
chore: reorganized partner logos
canerakdas Oct 1, 2025
392d60e
feat: supporters provider created
canerakdas Oct 1, 2025
430839f
refactor: filter and slice logic separated
canerakdas Oct 1, 2025
2790467
chore: new line
canerakdas Oct 1, 2025
a528163
chore: json files moved into the public/static
canerakdas Oct 1, 2025
864ef6c
chore: reorganize svg attributes
canerakdas Oct 1, 2025
d0b5621
refactor: randomPartnerList utils
canerakdas Oct 2, 2025
31f59a5
chore: unused file removed
canerakdas Oct 2, 2025
51a983d
docs: update partners json path
canerakdas Oct 2, 2025
fca86e7
chore: docs comments
canerakdas Oct 2, 2025
e43ac52
chore: resolve conflict
canerakdas Oct 2, 2025
4aa8ba8
chore: todos moved into the issues
canerakdas Oct 2, 2025
f2dc41b
chore: remove checking uniqueness
canerakdas Oct 2, 2025
3bef31f
refactor: deterministic shuffle algorithm
canerakdas Oct 2, 2025
881ddd2
chore: revert mdx changes
canerakdas Oct 2, 2025
207b401
chore: revert mdx changes
canerakdas Oct 2, 2025
5719e96
chore: revert mdx changes
canerakdas Oct 2, 2025
dd941c7
chore: revert mdx changes
canerakdas Oct 2, 2025
dfb8ec9
fix: lint issue
canerakdas Oct 2, 2025
335e1a5
chore: revert the /ja/ lint issue
canerakdas Oct 2, 2025
6dee5ea
docs: Update apps/site/pages/en/about/partners.mdx
canerakdas Oct 2, 2025
b0be46a
chore: remove github sponsors mismention
canerakdas Oct 2, 2025
0cac120
chore/debug: use named import from node:crypto
bmuenzenmeyer Oct 3, 2025
19efadf
refactor: web crypto api
canerakdas Oct 5, 2025
3e007d7
Update apps/site/next-data/generators/supportersData.mjs
canerakdas Oct 6, 2025
4c2d6cb
refactor: move shuffle util into the array utils
canerakdas Oct 6, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@reference "../../../../styles/index.css";

.partnerIcon {
@apply h-9
w-auto
min-w-9
p-2;

svg {
@apply !h-4
!w-auto;
}
}
34 changes: 34 additions & 0 deletions apps/site/components/Common/Partners/PartnerIcon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Skeleton from '@node-core/ui-components/Common/Skeleton';
import Tooltip from '@node-core/ui-components/Common/Tooltip';
import type { ComponentProps, FC } from 'react';
import { cloneElement } from 'react';

import Button from '#site/components/Common/Button';
import type { Partners } from '#site/types';

import style from './index.module.css';

type PartnersIconProps = Partners & ComponentProps<typeof Skeleton>;

const PartnersIcon: FC<PartnersIconProps> = ({ name, href, logo, loading }) => (
<Skeleton loading={loading} className="size-9 p-2">
<Tooltip
content={
<div className="p-2 text-neutral-900 dark:text-neutral-200">{name}</div>
}
>
<Button
kind="secondary"
href={`${href}/?utm_source=nodejs-website&utm_medium=Link`}
className={style.partnerIcon}
>
{cloneElement(logo, {
width: '100%',
height: '16px',
})}
</Button>
</Tooltip>
</Skeleton>
);

export default PartnersIcon;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@reference "../../../../styles/index.css";

.partnerIcon {
@apply flex
h-28
max-h-28
w-auto
min-w-12
items-center
justify-center
rounded-lg
p-6
sm:p-10;

svg {
@apply !h-12
!w-auto;
}
}
27 changes: 27 additions & 0 deletions apps/site/components/Common/Partners/PartnerLogo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Skeleton from '@node-core/ui-components/Common/Skeleton';
import type { ComponentProps, FC } from 'react';
import { cloneElement } from 'react';

import Button from '#site/components/Common/Button';
import type { Partners } from '#site/types';

import style from './index.module.css';

type PartnersLogoProps = Partners & ComponentProps<typeof Skeleton>;

const PartnersLogo: FC<PartnersLogoProps> = ({ href, logo, loading }) => (
<Skeleton loading={loading} className="h-28 w-full p-2">
<Button
kind="secondary"
href={`${href}/?utm_source=nodejs-website&utm_medium=Link`}
className={style.partnerIcon}
>
{cloneElement(logo, {
width: '100%',
height: '16px',
})}
</Button>
</Skeleton>
);

export default PartnersLogo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@reference "../../../../styles/index.css";

.partnersIconList {
@apply flex
flex-row
flex-wrap
items-center
gap-2;
}
40 changes: 40 additions & 0 deletions apps/site/components/Common/Partners/PartnersIconList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client';

import type { FC } from 'react';

import usePartnersList from '#site/hooks/react-client/usePartnersList';
import { ICON_PARTNERS } from '#site/next.partners.constants';
import type { PartnerCategory } from '#site/types';

import PartnerIcon from '../PartnerIcon';
import style from './index.module.css';

type PartnersIconListProps = {
maxLength?: number;
categories?: PartnerCategory;
};

const PartnersIconList: FC<PartnersIconListProps> = ({
maxLength = 6,
categories,
}) => {
const { seedList, initialRenderer } = usePartnersList({
logos: ICON_PARTNERS,
maxLength,
categories,
});

return (
<div className={style.partnersIconList}>
{seedList.map((partner, index) => (
<PartnerIcon
{...partner}
key={index}
loading={initialRenderer.current}
/>
))}
</div>
);
};

export default PartnersIconList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@reference "../../../../styles/index.css";

.partnersLogoList {
@apply grid
w-full
grid-cols-[repeat(auto-fill,minmax(240px,1fr))]
gap-4;
}
43 changes: 43 additions & 0 deletions apps/site/components/Common/Partners/PartnersLogoList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use client';

import type { FC } from 'react';

import usePartnersList from '#site/hooks/react-client/usePartnersList';
import { LOGO_PARTNERS } from '#site/next.partners.constants';
import type { PartnerCategory } from '#site/types';

import PartnerLogo from '../PartnerLogo';
import style from './index.module.css';

type PartnersLogoListProps = {
maxLength?: number;
categories?: PartnerCategory;
sort?: 'name' | 'weight';
};

const PartnersLogoList: FC<PartnersLogoListProps> = ({
maxLength = 3,
sort = 'weight',
categories,
}) => {
const { seedList, initialRenderer } = usePartnersList({
logos: LOGO_PARTNERS,
maxLength,
sort,
categories,
});

return (
<div className={style.partnersLogoList}>
{seedList.map((partner, index) => (
<PartnerLogo
{...partner}
key={index}
loading={initialRenderer.current}
/>
))}
</div>
);
};

export default PartnersLogoList;
23 changes: 23 additions & 0 deletions apps/site/components/Common/Partners/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { RandomPartnerListConfig, Partners } from '#site/types';
import { shuffle } from '#site/util/array';

async function randomPartnerList(
partners: Array<Partners>,
config: RandomPartnerListConfig
): Promise<Array<Partners>> {
const { pick = 4, dateSeed = 5, category } = config;

// Generate a deterministic seed based on current time that changes every X minutes
const seed = Math.floor(Date.now() / (dateSeed * 60 * 1000));

// Filter by category if provided
const filtered = category
? partners.filter(p => p.categories.includes(category))
: partners;

const shuffled = await shuffle(filtered, seed);

return shuffled.slice(0, pick ?? filtered.length);
}

export { randomPartnerList };
20 changes: 20 additions & 0 deletions apps/site/components/Common/Supporters/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import Avatar from '@node-core/ui-components/Common/AvatarGroup/Avatar';
import type { FC } from 'react';

import type { Supporters } from '#site/types';

type SupportersListProps = {
supporters: Array<Supporters>;
};

const SupportersList: FC<SupportersListProps> = ({ supporters }) => (
<div className="flex max-w-full flex-wrap items-center justify-center gap-1">
{supporters.map(({ name, image }, i) => (
<Avatar nickname={name} image={image} key={`${name}-${i}`} />
))}
</div>
);

export default SupportersList;
19 changes: 19 additions & 0 deletions apps/site/components/withSupporters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use server';

import type { FC, PropsWithChildren } from 'react';

import SupportersList from './Common/Supporters';

import provideSupporters from '#site/next-data/providers/supportersData';

const WithSupporters: FC<PropsWithChildren> = () => {
const supporters = provideSupporters();

return (
<div className="flex max-w-full flex-wrap items-center gap-1">
<SupportersList supporters={supporters} />
</div>
);
};

export default WithSupporters;
61 changes: 61 additions & 0 deletions apps/site/hooks/react-client/usePartnersList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use client';

import { useEffect, useRef, useState } from 'react';

import { randomPartnerList } from '#site/components/Common/Partners/utils';
import type { PartnerCategory, Partners } from '#site/types/partners';

const usePartnersList = ({
logos,
maxLength,
sort,
categories,
}: {
logos: Array<Partners>;
maxLength: number;
sort?: 'name' | 'weight';
categories?: PartnerCategory;
}) => {
const initialRenderer = useRef(true);

const [seedList, setSeedList] = useState<Array<Partners>>(() => {
const filteredLogos = logos.filter(
partner => !categories || partner.categories.includes(categories)
);

return filteredLogos.slice(0, maxLength || filteredLogos.length);
});

useEffect(() => {
// We intentionally render the initial default "mock" list of sponsors
// to have the Skeletons loading, and then we render the actual list
// after an enough amount of time has passed to give a proper sense of Animation
// We do this client-side effect, to ensure that a random-amount of sponsors is renderered
// on every page load. Since our page is natively static, we need to ensure that
// on the client-side we have a random amount of sponsors rendered.
// Although whilst we are deployed on Vercel or other environment that supports ISR
// (Incremental Static Generation) whose would invalidate the cache every 5 minutes
// We want to ensure that this feature is compatible on a full-static environment
const renderSponsorsAnimation = setTimeout(async () => {
initialRenderer.current = false;

setSeedList(
await randomPartnerList(logos, {
pick: maxLength,
dateSeed: 1,
category: categories,
sort,
})
);
}, 0);

return () => clearTimeout(renderSponsorsAnimation);
// We only want this to run once on initial render
// We don't really care if the props change as realistically they shouldn't ever
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return { seedList, initialRenderer };
};

export default usePartnersList;
9 changes: 9 additions & 0 deletions apps/site/mdx/components.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import MDXCodeTabs from '@node-core/ui-components/MDX/CodeTabs';

import Button from '#site/components/Common/Button';
import LinkWithArrow from '#site/components/Common/LinkWithArrow';
import PartnersIconList from '#site/components/Common/Partners/PartnersIconList';
import PartnersLogoList from '#site/components/Common/Partners/PartnersLogoList';
import DownloadButton from '#site/components/Downloads/DownloadButton';
import DownloadsTable from '#site/components/Downloads/DownloadsTable';
import BlogPostLink from '#site/components/Downloads/Release/BlogPostLink';
Expand Down Expand Up @@ -33,6 +35,7 @@ import WithDownloadArchive from '#site/components/withDownloadArchive';
import WithNodeRelease from '#site/components/withNodeRelease';
import WithReleaseAlertBox from '#site/components/withReleaseAlertBox';
import WithReleaseSelect from '#site/components/withReleaseSelect';
import WithSupporters from '#site/components/withSupporters';
import { ReleaseProvider } from '#site/providers/releaseProvider';

/**
Expand Down Expand Up @@ -67,6 +70,10 @@ export default {
ChangelogLink,
DownloadLink: ReleaseDownloadLink,
},
// Shows a list of Node.js Partners with Icons
PartnersIconList,
// Shows a list of Node.js Partners with Logos
PartnersLogoList,
// HOC for providing the Download Archive Page properties
WithDownloadArchive,
DownloadsTable,
Expand All @@ -75,6 +82,8 @@ export default {
WithReleaseAlertBox,
WithBanner,
WithBadgeGroup,
// HOC for providing Backers Data
WithSupporters,
BadgeGroup,
ReleaseOverview,
MinorReleasesTable,
Expand Down
4 changes: 4 additions & 0 deletions apps/site/navigation.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@
"link": "/about/governance",
"label": "components.navigation.about.links.governance"
},
"partners": {
"link": "/about/partners",
"label": "components.navigation.about.links.partners"
},
"branding": {
"link": "/about/branding",
"label": "components.navigation.about.links.branding"
Expand Down
Loading
Loading