diff --git a/apps/site/components/Common/Partners/PartnerIcon/index.module.css b/apps/site/components/Common/Partners/PartnerIcon/index.module.css new file mode 100644 index 0000000000000..6c04f8a429085 --- /dev/null +++ b/apps/site/components/Common/Partners/PartnerIcon/index.module.css @@ -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; + } +} diff --git a/apps/site/components/Common/Partners/PartnerIcon/index.tsx b/apps/site/components/Common/Partners/PartnerIcon/index.tsx new file mode 100644 index 0000000000000..aa0784384fb86 --- /dev/null +++ b/apps/site/components/Common/Partners/PartnerIcon/index.tsx @@ -0,0 +1,30 @@ +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; + +const PartnersIcon: FC = ({ name, href, logo, loading }) => ( + + {name}}> + + + +); + +export default PartnersIcon; diff --git a/apps/site/components/Common/Partners/PartnerLogo/index.module.css b/apps/site/components/Common/Partners/PartnerLogo/index.module.css new file mode 100644 index 0000000000000..8f7b772426abe --- /dev/null +++ b/apps/site/components/Common/Partners/PartnerLogo/index.module.css @@ -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; + } +} diff --git a/apps/site/components/Common/Partners/PartnerLogo/index.tsx b/apps/site/components/Common/Partners/PartnerLogo/index.tsx new file mode 100644 index 0000000000000..1f9d9ee13d18f --- /dev/null +++ b/apps/site/components/Common/Partners/PartnerLogo/index.tsx @@ -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; + +const PartnersLogo: FC = ({ href, logo, loading }) => ( + + + +); + +export default PartnersLogo; diff --git a/apps/site/components/Common/Partners/PartnersIconList/index.module.css b/apps/site/components/Common/Partners/PartnersIconList/index.module.css new file mode 100644 index 0000000000000..ebba90ad3787b --- /dev/null +++ b/apps/site/components/Common/Partners/PartnersIconList/index.module.css @@ -0,0 +1,9 @@ +@reference "../../../../styles/index.css"; + +.partnersIconList { + @apply flex + flex-row + flex-wrap + items-center + gap-2; +} diff --git a/apps/site/components/Common/Partners/PartnersIconList/index.tsx b/apps/site/components/Common/Partners/PartnersIconList/index.tsx new file mode 100644 index 0000000000000..2473526573f81 --- /dev/null +++ b/apps/site/components/Common/Partners/PartnersIconList/index.tsx @@ -0,0 +1,69 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; +import type { FC } from 'react'; + +import { ICON_PARTNERS } from '#site/next.partners.constants'; +import type { PartnerCategory, Partners } from '#site/types'; + +import PartnerIcon from '../PartnerIcon'; +import style from './index.module.css'; +import { randomPartnerList } from '../utils'; + +type PartnersIconListProps = { + maxLength?: number; + categories?: PartnerCategory; +}; + +const PartnersIconList: FC = ({ + maxLength = 6, + categories, +}) => { + const initialRenderer = useRef(true); + + const [seedList, setSeedList] = useState>( + ICON_PARTNERS.slice(0, maxLength) + ); + + 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(() => { + initialRenderer.current = false; + + setSeedList( + randomPartnerList(ICON_PARTNERS, { + pick: maxLength, + dateSeed: 5, + category: categories, + }) + ); + }, 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.map((partner, index) => ( + + ))} +
+ ); +}; + +export default PartnersIconList; diff --git a/apps/site/components/Common/Partners/PartnersLogoList/index.module.css b/apps/site/components/Common/Partners/PartnersLogoList/index.module.css new file mode 100644 index 0000000000000..52efa7168171c --- /dev/null +++ b/apps/site/components/Common/Partners/PartnersLogoList/index.module.css @@ -0,0 +1,8 @@ +@reference "../../../../styles/index.css"; + +.partnersLogoList { + @apply grid + w-full + grid-cols-[repeat(auto-fill,minmax(240px,1fr))] + gap-4; +} diff --git a/apps/site/components/Common/Partners/PartnersLogoList/index.tsx b/apps/site/components/Common/Partners/PartnersLogoList/index.tsx new file mode 100644 index 0000000000000..379b4d26fec93 --- /dev/null +++ b/apps/site/components/Common/Partners/PartnersLogoList/index.tsx @@ -0,0 +1,77 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; +import type { FC } from 'react'; + +import { LOGO_PARTNERS } from '#site/next.partners.constants'; +import type { PartnerCategory, Partners } from '#site/types'; + +import PartnerLogo from '../PartnerLogo'; +import style from './index.module.css'; +import { randomPartnerList } from '../utils'; + +type PartnersLogoListProps = { + maxLength?: number; + categories?: PartnerCategory; + sort?: 'name' | 'weight'; +}; + +const PartnersLogoList: FC = ({ + maxLength = 3, + sort = 'weight', + categories, +}) => { + const initialRenderer = useRef(true); + + const [seedList, setSeedList] = useState>(() => { + if (maxLength === null) { + return LOGO_PARTNERS.filter( + partner => !categories || partner.categories.includes(categories) + ); + } + return LOGO_PARTNERS.slice(0, maxLength); + }); + + 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(() => { + initialRenderer.current = false; + + setSeedList( + randomPartnerList(LOGO_PARTNERS, { + pick: maxLength, + dateSeed: 5, + category: categories, + sort: 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.map((partner, index) => ( + + ))} +
+ ); +}; + +export default PartnersLogoList; diff --git a/apps/site/components/Common/Partners/utils.ts b/apps/site/components/Common/Partners/utils.ts new file mode 100644 index 0000000000000..ab070e1595d9c --- /dev/null +++ b/apps/site/components/Common/Partners/utils.ts @@ -0,0 +1,92 @@ +import type { PartnerCategory, Partners } from '#site/types/partners.js'; + +function randomPartnerList( + partners: Array, + config: { + /** + * Number of partners to pick from the list. + * If null, all partners will be returned. + */ + pick?: number | null; + /** + * Date seed to use for the randomization. + * This is used to ensure that the same partners are returned for the same date. + */ + dateSeed?: number; + /** + * Category of partners to filter by. + * If not provided, all partners will be returned. + */ + category?: PartnerCategory; + /** + * Whether to randomize the partners or not. + */ + sort?: 'name' | 'weight' | null; + } +) { + const { pick = 4, dateSeed = 5, category, sort = 'weight' } = config; + + const filteredPartners = [...partners].filter(partner => { + return !category || partner.categories.includes(category); + }); + + if (sort === null) { + return pick !== null ? filteredPartners.slice(0, pick) : filteredPartners; + } + + if (sort === 'name') { + const shuffled = [...filteredPartners].sort((a, b) => + a.name.localeCompare(b.name) + ); + + return pick !== null ? shuffled.slice(0, pick) : shuffled; + } + + const now = new Date(); + const minutes = Math.floor(now.getUTCMinutes() / dateSeed) * dateSeed; + + const fixedTime = new Date( + Date.UTC( + now.getUTCFullYear(), + now.getUTCMonth(), + now.getUTCDate(), + now.getUTCHours(), + minutes, + 0, + 0 + ) + ); + + // We create a seed from the rounded date (timestamp in ms) + const seed = fixedTime.getTime(); + const rng = mulberry32(seed); + + const weightedPartners = filteredPartners.flatMap(partner => { + const weight = partner.weight; + return Array(weight).fill(partner); + }); + + // Create a copy of the array to avoid modifying the original + const shuffled = [...weightedPartners].sort(() => rng() - 0.5); + + // Remove duplicates while preserving order + const unique = Array.from(new Set(shuffled)); + + if (pick !== null) { + return unique.slice(0, pick); + } + + return unique; +} + +// This function returns a random list of partners based on a fixed time seed +function mulberry32(seed: number) { + return function () { + let t = (seed += 0x6d2b79f5); + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +export { randomPartnerList }; diff --git a/apps/site/components/Common/Supporters/index.tsx b/apps/site/components/Common/Supporters/index.tsx new file mode 100644 index 0000000000000..9afb941a5d5c2 --- /dev/null +++ b/apps/site/components/Common/Supporters/index.tsx @@ -0,0 +1,26 @@ +'use client'; + +import Avatar from '@node-core/ui-components/Common/AvatarGroup/Avatar'; +import type { FC } from 'react'; +import { use } from 'react'; + +import type { Supporters } from '#site/types'; + +type SupportersProps = { + supporters: Promise>; +}; + +// TODO: Sort supporters by all time contribution amount and link to their Open Collective page +const SupportersList: FC = ({ supporters }) => { + const supportersList = use(supporters); + + return ( +
+ {supportersList.map(({ name, image }, i) => ( + + ))} +
+ ); +}; + +export default SupportersList; diff --git a/apps/site/components/withSupporters.tsx b/apps/site/components/withSupporters.tsx new file mode 100644 index 0000000000000..63f1a4792c18b --- /dev/null +++ b/apps/site/components/withSupporters.tsx @@ -0,0 +1,18 @@ +import type { FC, PropsWithChildren } from 'react'; + +import { fetchOpenCollectiveData } from '#site/next-data/generators/supportersData.mjs'; +import type { Supporters } from '#site/types'; + +import SupportersList from './Common/Supporters'; + +const WithSupporters: FC = () => { + const supporters = fetchOpenCollectiveData() as Promise>; + + return ( +
+ +
+ ); +}; + +export default WithSupporters; diff --git a/apps/site/layouts/Post.tsx b/apps/site/layouts/Post.tsx index 9b5234b727757..7f6bea5bfcafe 100644 --- a/apps/site/layouts/Post.tsx +++ b/apps/site/layouts/Post.tsx @@ -1,6 +1,7 @@ import Preview from '@node-core/ui-components/Common/Preview'; import type { FC, PropsWithChildren } from 'react'; +// import Link from '#site/components/Link'; import WithAvatarGroup from '#site/components/withAvatarGroup'; import WithBlogCrossLinks from '#site/components/withBlogCrossLinks'; import WithFooter from '#site/components/withFooter'; @@ -11,6 +12,7 @@ import { mapAuthorToCardAuthors } from '#site/util/author'; import { mapBlogCategoryToPreviewType } from '#site/util/blog'; import styles from './layouts.module.css'; +// import PartnersLogoList from '../components/Common/Partners/PartnersLogoList'; const PostLayout: FC = ({ children }) => { const { frontmatter } = useClientContext(); @@ -39,6 +41,22 @@ const PostLayout: FC = ({ children }) => { {children} + {/* {type === 'vulnerability' && ( +
+
+
+

+ These security releases are possible by: +

+

+ We are able to offer security releases proudly due to the + support of these partners + and more. +

+ +
+
+ )} */} diff --git a/apps/site/navigation.json b/apps/site/navigation.json index cfc6114362475..e2a26b00bcd1b 100644 --- a/apps/site/navigation.json +++ b/apps/site/navigation.json @@ -117,6 +117,10 @@ "branding": { "link": "/about/branding", "label": "components.navigation.about.links.branding" + }, + "partners": { + "link": "/about/partners", + "label": "components.navigation.about.links.partners" } } }, diff --git a/apps/site/next-data/generators/supportersData.mjs b/apps/site/next-data/generators/supportersData.mjs new file mode 100644 index 0000000000000..4eed21d53421f --- /dev/null +++ b/apps/site/next-data/generators/supportersData.mjs @@ -0,0 +1,95 @@ +// This is used to ensure that URLs are always in the correct format +function fixUrl(url) { + if (!url) { + return null; + } + + if (!url.startsWith('http://') && !url.startsWith('https://')) { + return `https://${url}`; + } + + return url; +} + +async function fetchOpenCollectiveData() { + const endpoint = 'https://api.opencollective.com/graphql/v2'; + + const query = `{ + account(slug: "nodejs") { + orders(status: ACTIVE, filter: INCOMING) { + totalCount + nodes { + fromAccount { + name + website + imageUrl + } + amount { + value + } + tier { + slug + } + frequency + totalDonations { + value + } + } + } + } + donations: orders( + account: { slug: "nodejs" } + frequency: ONETIME + status: PAID + filter: INCOMING + ) { + totalCount + nodes { + id + updatedAt + frequency + status + amount { + value + currency + } + fromAccount { + name + website + imageUrl + } + } + } + }`; + + const response = await fetch(endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query }), + }); + + const payload = await response.json(); + + const sponsors = payload.data.account.orders.nodes.map(order => ({ + name: order.fromAccount.name, + url: fixUrl(order.fromAccount.website), + image: order.fromAccount.imageUrl, + source: 'opencollective', + })); + + const donations = payload.data.donations.nodes.map(transaction => ({ + name: transaction.fromAccount.name, + url: fixUrl(transaction.fromAccount.website), + image: transaction.fromAccount.imageUrl, + source: 'opencollective', + })); + + sponsors.push(...donations); + + return sponsors; +} + +// TODO: implement github sponsors data fetching +// TODO: implement ramdomizing of supporters + +export { fetchOpenCollectiveData }; diff --git a/apps/site/next.mdx.use.mjs b/apps/site/next.mdx.use.mjs index b67817de4e9ff..5d80d6dd86b48 100644 --- a/apps/site/next.mdx.use.mjs +++ b/apps/site/next.mdx.use.mjs @@ -3,6 +3,8 @@ import BadgeGroup from '@node-core/ui-components/Common/BadgeGroup'; import Button from './components/Common/Button'; +import PartnersIconList from './components/Common/Partners/PartnersIconList'; +import PartnersLogoList from './components/Common/Partners/PartnersLogoList'; import DownloadReleasesTable from './components/Downloads/DownloadReleasesTable'; import Link from './components/Link'; import LinkWithArrow from './components/LinkWithArrow'; @@ -10,6 +12,7 @@ import UpcomingMeetings from './components/MDX/Calendar/UpcomingMeetings'; import WithBadgeGroup from './components/withBadgeGroup'; import WithBanner from './components/withBanner'; import WithNodeRelease from './components/withNodeRelease'; +import WithSupporters from './components/withSupporters'; /** * A full list of React Components that we want to pass through to MDX @@ -24,8 +27,14 @@ export const mdxComponents = { WithBanner, // HOC for providing Badge Data WithBadgeGroup, + // HOC for providing Backers Data + WithSupporters, // Standalone Badge Group BadgeGroup, + // Shows a list of Node.js Partners with Icons + PartnersIconList, + // Shows a list of Node.js Partners with Logos + PartnersLogoList, // Renders an container for Upcoming Node.js Meetings UpcomingMeetings, // Renders a Button Component for `button` tags diff --git a/apps/site/next.partners.constants.tsx b/apps/site/next.partners.constants.tsx new file mode 100644 index 0000000000000..94d1319ba75f2 --- /dev/null +++ b/apps/site/next.partners.constants.tsx @@ -0,0 +1,11 @@ +import type { Partners } from '#site/types'; +import { createPartnersList } from '#site/util/partners'; +import partners from '#site/util/partners/constants.json' with { type: 'json' }; + +const getPartnersByType = (type?: 'Logo' | 'Favicon') => + createPartnersList(partners as Array>, type); + +const ICON_PARTNERS = getPartnersByType('Favicon'); +const LOGO_PARTNERS = getPartnersByType('Logo'); + +export { ICON_PARTNERS, LOGO_PARTNERS }; diff --git a/apps/site/pages/en/about/partners.mdx b/apps/site/pages/en/about/partners.mdx new file mode 100644 index 0000000000000..ddc498fe5832c --- /dev/null +++ b/apps/site/pages/en/about/partners.mdx @@ -0,0 +1,53 @@ +--- +title: Partners & Supporters +layout: about +--- + +# Partners & Supporters + +The Node.js community is enriched by a vibrant network of partners who contribute +in diverse ways. Through these collaborations, we maintain our resilient infrastructure, +strengthen security, drive new releases, and sustainably grow our ecosystem. + +We are grateful for the support of our partners, who play a crucial role in the success and +continuous development of Node.js. + +## Infrastructure + +The infrastructure partners provide important support for the Node.js project, +providing hardware and machines for our continuous integration and testing processes, +without we can't test and release new versions of Node.js. + + + +## Supporters + +Supporters are individuals and organizations that provide financial support through +[OpenCollective](https://opencollective.com/nodejs) and [GitHub Sponsors](https://github.com/sponsors/nodejs) +of the Node.js project. + + + +## Ecosystem Sustainability Program (ESP) + +Do you run a node.js version on End-of-Life (EOL)? The OpenJS Ecosystem Sustainability Program (ESP) +is a program that helps organizations to maintain their Node.js applications on EOL versions. +The program provides access to security patches, compliance assistance, and technical support +to help bridge the gap while you plan your upgrade strategy. For more information about +End-Of-Life versions, please visit [End-Of-Life Node.js Releases](/eol) + +> Using EOL releases through NES should be viewed as a temporary solution, the goal should always +> be to upgrade to actively supported versions. + + + +## Become a Partner + +Become a partner of the Node.js project and help us to continue to develop and maintain +this project. Your support is crucial to ensure that Node.js remains a reliable and secure platform +for developers and organizations around the world. If you are interested in becoming a partner, +please reach out to us through the OpenJS Foundation. + +
+ +
diff --git a/apps/site/pages/en/download/current.mdx b/apps/site/pages/en/download/current.mdx index 16e78d6a8e1d7..f03bffb9ef6cd 100644 --- a/apps/site/pages/en/download/current.mdx +++ b/apps/site/pages/en/download/current.mdx @@ -34,3 +34,17 @@ all previous releases or the unofficial binaries for other platforms. + +--- + +
+

Proudly supported by the partners below:

+ + + We are able to serve Node.js's downloads and maintain our infrastructure + proudly due to the support of these partners, and more. + +
+ +
+
diff --git a/apps/site/pages/en/download/index.mdx b/apps/site/pages/en/download/index.mdx index 16e78d6a8e1d7..87044079c9284 100644 --- a/apps/site/pages/en/download/index.mdx +++ b/apps/site/pages/en/download/index.mdx @@ -19,7 +19,7 @@ Or get a prebuilt Node.js® for running a -
+
Read the changelog or blog post for this version. @@ -34,3 +34,17 @@ all previous releases or the unofficial binaries for other platforms.
+ +--- + +
+

Proudly supported by the partners below:

+ + + We are able to serve Node.js's downloads and maintain our infrastructure + proudly due to the support of these partners, and more. + +
+ +
+
diff --git a/apps/site/pages/en/index.mdx b/apps/site/pages/en/index.mdx index 02f5c43bd78a2..402c4fe8cefc0 100644 --- a/apps/site/pages/en/index.mdx +++ b/apps/site/pages/en/index.mdx @@ -26,6 +26,11 @@ layout: home
for Node.js 18 and below + +
+ +
+ Node.js is proudly supported by the partners above and more. diff --git a/apps/site/types/index.ts b/apps/site/types/index.ts index 38f5767b732db..4ca7f7a004c69 100644 --- a/apps/site/types/index.ts +++ b/apps/site/types/index.ts @@ -10,6 +10,7 @@ export * from './redirects'; export * from './server'; export * from './github'; export * from './calendar'; +export * from './partners'; export * from './author'; export * from './download'; export * from './userAgent'; diff --git a/apps/site/types/partners.ts b/apps/site/types/partners.ts new file mode 100644 index 0000000000000..0f9c1a6dd7d08 --- /dev/null +++ b/apps/site/types/partners.ts @@ -0,0 +1,26 @@ +import type { ReactElement, SVGProps } from 'react'; + +export interface Partners { + id: string; + // The name of the partner + name: string; + // A logo to render on the partners page + logo: ReactElement>; + // The promoted link to their website or social media + href: string; + // The categories this partner belongs to + categories: Array; + // An optional description of the partner + description?: string; + // The weight of the partner, used for random selection + weight: number; +} + +export type PartnerCategory = 'infrastructure' | 'esp'; + +export interface Supporters { + name: string; + image: string; + url: string; + source: 'opencollective' | 'github'; +} diff --git a/apps/site/util/partners/constants.json b/apps/site/util/partners/constants.json new file mode 100644 index 0000000000000..fcb3a733d7cac --- /dev/null +++ b/apps/site/util/partners/constants.json @@ -0,0 +1,37 @@ +[ + { + "id": "CLOUDFLARE", + "name": "Cloudflare", + "href": "https://www.cloudflare.com", + "weight": 3, + "categories": ["infrastructure"] + }, + { + "id": "VERCEL", + "name": "Vercel", + "href": "https://vercel.com", + "weight": 2, + "categories": ["infrastructure"] + }, + { + "id": "HERODEVS", + "name": "HeroDevs", + "href": "https://herodevs.com", + "weight": 3, + "categories": ["security", "esp", "release"] + }, + { + "id": "DIGITALOCEAN", + "name": "DigitalOcean", + "href": "https://www.digitalocean.com", + "categories": ["infrastructure"], + "weight": 3 + }, + { + "id": "MICROSOFT", + "name": "Microsoft Azure", + "href": "https://microsoft.com", + "categories": ["infrastructure"], + "weight": 2 + } +] diff --git a/apps/site/util/partners/index.tsx b/apps/site/util/partners/index.tsx new file mode 100644 index 0000000000000..9562a9b7e9c6f --- /dev/null +++ b/apps/site/util/partners/index.tsx @@ -0,0 +1,29 @@ +import * as PartnersLogo from '@node-core/ui-components/Icons/PartnerLogos'; +import type { ElementType } from 'react'; + +import type { Partners } from '#site/types'; + +/** + * Creates an icon element for a component + */ +const createIcon = ( + IconModule: Record>, + iconName: string, + type?: 'Logo' | 'Favicon' +) => { + const IconComponent = IconModule[iconName][type || 'Favicon']; + return ; +}; + +// Creates a list of partners with their respective icons +export const createPartnersList = ( + partnerLists: Array>, + type?: 'Logo' | 'Favicon' +) => + partnerLists.map(({ id, ...partner }) => { + return { + id: id, + logo: createIcon(PartnersLogo, id, type), + ...partner, + }; + }); diff --git a/apps/site/util/partners/original.json b/apps/site/util/partners/original.json new file mode 100644 index 0000000000000..a63657ac50ff3 --- /dev/null +++ b/apps/site/util/partners/original.json @@ -0,0 +1,171 @@ +[ + { + "id": "RACKSPACE", + "name": "Rackspace", + "href": "https://www.rackspace.com", + "weight": 3, + "categories": ["infrastructure"] + }, + { + "id": "CLOUDFLARE", + "name": "Cloudflare", + "href": "https://www.cloudflare.com", + "weight": 3, + "categories": ["infrastructure"] + }, + { + "id": "VERCEL", + "name": "Vercel", + "href": "https://vercel.com", + "weight": 2, + "categories": ["infrastructure"] + }, + { + "id": "SENTRY", + "name": "Sentry", + "href": "https://sentry.io", + "weight": 1, + "categories": ["infrastructure"] + }, + { + "id": "CROWDIN", + "name": "Crowdin", + "href": "https://crowdin.com", + "weight": 1, + "categories": ["service"] + }, + { + "id": "HERODEVS", + "name": "HeroDevs", + "href": "https://herodevs.com", + "weight": 3, + "categories": ["security", "esp", "release"] + }, + { + "id": "NODESOURCE", + "name": "NodeSource", + "href": "https://nodesource.com", + "weight": 2, + "categories": ["security", "release"] + }, + { + "id": "DATADOG", + "name": "Datadog", + "href": "https://www.datadoghq.com", + "weight": 2, + "categories": ["security"] + }, + { + "id": "DIGITALOCEAN", + "name": "DigitalOcean", + "href": "https://www.digitalocean.com", + "categories": ["infrastructure"], + "weight": 3 + }, + { + "id": "MICROSOFT", + "name": "Microsoft", + "href": "https://microsoft.com", + "categories": ["infrastructure"], + "weight": 2 + }, + { + "id": "MNX", + "name": "MNX", + "href": "ToDo", + "logo": "ToDo", + "categories": ["infrastructure"], + "weight": 2 + }, + { + "id": "IBM", + "name": "IBM", + "href": "https://www.ibm.com", + "categories": ["infrastructure", "release"], + "weight": 3 + }, + { + "id": "SCALEWAY", + "name": "Scaleway", + "href": "https://www.scaleway.com", + "categories": ["infrastructure"], + "weight": 2 + }, + { + "id": "ARM", + "name": "ARM", + "href": "https://www.arm.com", + "categories": ["infrastructure"], + "weight": 2 + }, + { + "id": "INTEL", + "name": "Intel", + "href": "https://www.intel.com", + "categories": ["infrastructure"], + "weight": 2 + }, + { + "id": "MACSTADIUM", + "name": "MacStadium", + "href": "https://www.macstadium.com", + "categories": ["infrastructure"], + "weight": 2 + }, + { + "id": "EQUINIXMETAL", + "name": "Equinix Metal", + "href": "https://www.equinix.com", + "categories": ["infrastructure"], + "weight": 2 + }, + { + "id": "CHROMATIC", + "name": "Chromatic", + "href": "https://www.chromatic.com", + "categories": ["service"], + "weight": 1 + }, + { + "id": "ORAMA", + "name": "Orama", + "href": "https://orama.com", + "categories": ["service"], + "weight": 1 + }, + { + "id": "PLATFORMATIC", + "name": "Platformatic", + "href": "https://platformatic.dev", + "categories": ["security"], + "weight": 2 + }, + { + "id": "REDHAT", + "name": "Red Hat", + "href": "https://www.redhat.com", + "categories": ["security"], + "weight": 2 + }, + { + "id": "OPENSSF", + "name": "Open SSF", + "href": "https://openssf.org/", + "weight": 2, + "categories": ["security"] + }, + { + "id": "VLT", + "name": "Vlt", + "href": "https://www.vlt.sh", + "weight": 2, + "categories": ["release"] + }, + { + "id": "ZAKODIUM", + "name": "zakodium", + "href": "https://www.zakodium.com", + "weight": 2, + "categories": ["release"] + } +] diff --git a/docs/partners.md b/docs/partners.md new file mode 100644 index 0000000000000..824914f2c1f4a --- /dev/null +++ b/docs/partners.md @@ -0,0 +1,66 @@ +# Partners + +This document describes how partners and supporters of the Node.js project are referenced and managed. + +## Adding or Removing Partners + +All partners must be approved by the Node.js Marketing Team and the Node.js Technical Steering Committee (TSC). Therefore, partners cannot be added or removed without their approval. + +To add or remove a partner: + +1. Modify the `apps/site/util/partners/constants.json` file. This file contains an array of partner objects, each with the following fields: + - `id`: A unique identifier for the partner (used for logo import). + - `name`: The display name of the partner. + - `href`: A URL linking to the partner’s website. + - `categories`: An array of categories the partner belongs to. These are used to filter partners on the Partners and Downloads pages. Valid categories include: + - `infrastructure`: Partners providing infrastructure for the Node.js project. + - `esp`: Partners offering support for EOL Node.js versions through the Ecosystem Sustainability Program (ESP). + - `weight`: A number representing the partner's visibility weight. This is used in the randomization algorithm — higher values increase the partner's likelihood of being shown. + +2. Add the partner's logo to the `packages/ui-components/icons/PartnersLogos/` directory. + The filename must match the partner's `id`, in lowercase. + +3. Register the logo in `packages/ui-components/icons/PartnersLogos/index.ts`. + For example, if the partner’s `id` is `MICROSOFT`, the import must be written as: + + ```ts + import * as MICROSOFT from './microsoft'; + ``` + +## Home Page + +On the homepage, all partners are displayed regardless of category. The `weight` field is used by the randomization algorithm to determine how likely a partner is to appear e.g., a partner with a `weight` of 3 will be three times more likely to be shown than one with a `weight` of 1. + +## Partners Page + +The Partners page organizes partners by category. Each category has a description that must be approved by the Node.js Marketing Team and the TSC. +Each partner is displayed with: + +- Their logo +- Their name +- A link to their website with UTM parameters for tracking + +Partners are sorted alphabetically within each category. + +## Downloads Section + +The Downloads section only features partners categorized under `infrastructure`. These partners provide essential infrastructure for testing, releasing, and maintaining Node.js website, etc. +The same randomization algorithm used on the homepage is applied here as well. + +## Supporters + +> [!NOTE] +> Donations made through GitHub Sponsors are not displayed yet; they will be added soon. + +Supporters are individuals and organizations that provide financial support to the Node.js project through OpenCollective and GitHub Sponsors. They are displayed on the Partner page, but not on the homepage or Downloads section. + +## Partner Randomization Algorithm + +The randomization algorithm determines the order in which partners are displayed on both the homepage and the Downloads section. It takes into account: + +- The `weight` of each partner (higher weight = higher probability of being shown). +- A time-based seed: the order is refreshed every 5 minutes, not on every request. + This ensures that the display remains consistent for a short period, rather than changing constantly. E.g., if a user visits the homepage multiple times within 5 minutes, they will see the same order of partners. + +> [!NOTE] +> Partner randomization and rendering is performed on the client side, not during server-side rendering (SSR). diff --git a/packages/i18n/src/locales/en.json b/packages/i18n/src/locales/en.json index cadcca42859f3..6eff244decf52 100644 --- a/packages/i18n/src/locales/en.json +++ b/packages/i18n/src/locales/en.json @@ -144,7 +144,8 @@ "branding": "Branding of Node.js", "governance": "Project Governance", "releases": "Node.js Releases", - "security": "Security Reporting" + "security": "Security Reporting", + "partners": "Partners & Supporters" } }, "getInvolved": { diff --git a/packages/ui-components/src/Icons/PartnerLogos/ARM/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/ARM/Favicon.tsx new file mode 100644 index 0000000000000..b2cee516e7c43 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/ARM/Favicon.tsx @@ -0,0 +1,37 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const ARM: FC> = props => ( + + + + + +); + +export default ARM; diff --git a/packages/ui-components/src/Icons/PartnerLogos/ARM/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/ARM/Logo.tsx new file mode 100644 index 0000000000000..b2cee516e7c43 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/ARM/Logo.tsx @@ -0,0 +1,37 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const ARM: FC> = props => ( + + + + + +); + +export default ARM; diff --git a/packages/ui-components/src/Icons/PartnerLogos/ARM/index.tsx b/packages/ui-components/src/Icons/PartnerLogos/ARM/index.tsx new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/ARM/index.tsx @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Cloudflare/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/Cloudflare/Favicon.tsx new file mode 100644 index 0000000000000..59e5deca5bc35 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Cloudflare/Favicon.tsx @@ -0,0 +1,23 @@ +// https://www.cloudflare.com/logo/ +import type { FC, SVGProps } from 'react'; + +const Cloudflare: FC> = props => ( + + + + +); + +export default Cloudflare; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Cloudflare/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/Cloudflare/Logo.tsx new file mode 100644 index 0000000000000..0ee2f3ab21525 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Cloudflare/Logo.tsx @@ -0,0 +1,63 @@ +// https://www.cloudflare.com/logo/ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Cloudflare: FC> = props => ( + + + + + + + + + + + + + + +); + +export default Cloudflare; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Cloudflare/index.tsx b/packages/ui-components/src/Icons/PartnerLogos/Cloudflare/index.tsx new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Cloudflare/index.tsx @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Crowdin/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/Crowdin/Favicon.tsx new file mode 100644 index 0000000000000..c9eb2810e15a3 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Crowdin/Favicon.tsx @@ -0,0 +1,49 @@ +import type { FC, SVGProps } from 'react'; + +const Crowdin: FC> = props => ( + + + + + + + + + + + + + + + +); + +export default Crowdin; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Crowdin/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/Crowdin/Logo.tsx new file mode 100644 index 0000000000000..cd23dc2a0883c --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Crowdin/Logo.tsx @@ -0,0 +1,86 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Crowdin: FC> = props => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default Crowdin; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Crowdin/index.tsx b/packages/ui-components/src/Icons/PartnerLogos/Crowdin/index.tsx new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Crowdin/index.tsx @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/DataDog/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/DataDog/Favicon.tsx new file mode 100644 index 0000000000000..bc57bd91f3e91 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/DataDog/Favicon.tsx @@ -0,0 +1,93 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const DataDog: FC> = props => ( + + + + + +); + +export default DataDog; diff --git a/packages/ui-components/src/Icons/PartnerLogos/DataDog/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/DataDog/Logo.tsx new file mode 100644 index 0000000000000..0740b0cb317c9 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/DataDog/Logo.tsx @@ -0,0 +1,140 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const DataDog: FC> = props => ( + + + + + + + + + + + + + + + + + + + + + +); + +export default DataDog; diff --git a/packages/ui-components/src/Icons/PartnerLogos/DataDog/index.ts b/packages/ui-components/src/Icons/PartnerLogos/DataDog/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/DataDog/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/DigitalOcean/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/DigitalOcean/Favicon.tsx new file mode 100644 index 0000000000000..e1ef1270a9b64 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/DigitalOcean/Favicon.tsx @@ -0,0 +1,61 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Crowdin: FC> = props => ( + + + + + + + + + + + + + + + + + + + + + +); + +export default Crowdin; diff --git a/packages/ui-components/src/Icons/PartnerLogos/DigitalOcean/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/DigitalOcean/Logo.tsx new file mode 100644 index 0000000000000..88c40901903aa --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/DigitalOcean/Logo.tsx @@ -0,0 +1,216 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Crowdin: FC> = props => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default Crowdin; diff --git a/packages/ui-components/src/Icons/PartnerLogos/DigitalOcean/index.ts b/packages/ui-components/src/Icons/PartnerLogos/DigitalOcean/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/DigitalOcean/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/EquinixMetal/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/EquinixMetal/Favicon.tsx new file mode 100644 index 0000000000000..364f53a536ca1 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/EquinixMetal/Favicon.tsx @@ -0,0 +1,35 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const EquinixMetal: FC> = props => ( + + + + +); + +export default EquinixMetal; diff --git a/packages/ui-components/src/Icons/PartnerLogos/EquinixMetal/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/EquinixMetal/Logo.tsx new file mode 100644 index 0000000000000..371889dab83a5 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/EquinixMetal/Logo.tsx @@ -0,0 +1,84 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const EquinixMetal: FC> = props => ( + + + + + + + + + + + + + + + + + +); + +export default EquinixMetal; diff --git a/packages/ui-components/src/Icons/PartnerLogos/EquinixMetal/index.ts b/packages/ui-components/src/Icons/PartnerLogos/EquinixMetal/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/EquinixMetal/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/HeroDevs/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/HeroDevs/Favicon.tsx new file mode 100644 index 0000000000000..88d9cbcde3c88 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/HeroDevs/Favicon.tsx @@ -0,0 +1,56 @@ +import type { FC, SVGProps } from 'react'; + +const HeroDevs: FC> = props => ( + + + + + + + + + + + + + + + + + + + +); + +export default HeroDevs; diff --git a/packages/ui-components/src/Icons/PartnerLogos/HeroDevs/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/HeroDevs/Logo.tsx new file mode 100644 index 0000000000000..23de36b18ecb4 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/HeroDevs/Logo.tsx @@ -0,0 +1,85 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const HeroDevs: FC> = props => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default HeroDevs; diff --git a/packages/ui-components/src/Icons/PartnerLogos/HeroDevs/index.ts b/packages/ui-components/src/Icons/PartnerLogos/HeroDevs/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/HeroDevs/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/IBM/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/IBM/Favicon.tsx new file mode 100644 index 0000000000000..3479708ad3c95 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/IBM/Favicon.tsx @@ -0,0 +1,224 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const IBM: FC> = props => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default IBM; diff --git a/packages/ui-components/src/Icons/PartnerLogos/IBM/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/IBM/Logo.tsx new file mode 100644 index 0000000000000..3479708ad3c95 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/IBM/Logo.tsx @@ -0,0 +1,224 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const IBM: FC> = props => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default IBM; diff --git a/packages/ui-components/src/Icons/PartnerLogos/IBM/index.ts b/packages/ui-components/src/Icons/PartnerLogos/IBM/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/IBM/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Microsoft/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/Microsoft/Favicon.tsx new file mode 100644 index 0000000000000..fcf0db2ff3cee --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Microsoft/Favicon.tsx @@ -0,0 +1,30 @@ +import type { FC, SVGProps } from 'react'; + +const Microsoft: FC> = props => ( + + + + + + + + + + + + + +); + +export default Microsoft; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Microsoft/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/Microsoft/Logo.tsx new file mode 100644 index 0000000000000..d77913adcddd6 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Microsoft/Logo.tsx @@ -0,0 +1,36 @@ +import type { FC, SVGProps } from 'react'; + +const Microsoft: FC> = props => ( + + + + + + + + + + + + + + +); + +export default Microsoft; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Microsoft/index.ts b/packages/ui-components/src/Icons/PartnerLogos/Microsoft/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Microsoft/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/NodeSource/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/NodeSource/Favicon.tsx new file mode 100644 index 0000000000000..93919fc60dc77 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/NodeSource/Favicon.tsx @@ -0,0 +1,12 @@ +import type { FC, SVGProps } from 'react'; + +const NodeSource: FC> = props => ( + + + +); + +export default NodeSource; diff --git a/packages/ui-components/src/Icons/PartnerLogos/NodeSource/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/NodeSource/Logo.tsx new file mode 100644 index 0000000000000..f6907cd79c800 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/NodeSource/Logo.tsx @@ -0,0 +1,22 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const NodeSource: FC> = props => ( + + + + +); + +export default NodeSource; diff --git a/packages/ui-components/src/Icons/PartnerLogos/NodeSource/index.ts b/packages/ui-components/src/Icons/PartnerLogos/NodeSource/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/NodeSource/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/OpenSSF/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/OpenSSF/Favicon.tsx new file mode 100644 index 0000000000000..de3a0f39679c6 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/OpenSSF/Favicon.tsx @@ -0,0 +1,99 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const OpenSSF: FC> = props => ( + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default OpenSSF; diff --git a/packages/ui-components/src/Icons/PartnerLogos/OpenSSF/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/OpenSSF/Logo.tsx new file mode 100644 index 0000000000000..c5e6b86657f33 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/OpenSSF/Logo.tsx @@ -0,0 +1,234 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const OpenSSF: FC> = props => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default OpenSSF; diff --git a/packages/ui-components/src/Icons/PartnerLogos/OpenSSF/index.ts b/packages/ui-components/src/Icons/PartnerLogos/OpenSSF/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/OpenSSF/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Rackspace/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/Rackspace/Favicon.tsx new file mode 100644 index 0000000000000..1c805ca663c80 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Rackspace/Favicon.tsx @@ -0,0 +1,18 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Rackspace: FC> = props => ( + + + +); + +export default Rackspace; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Rackspace/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/Rackspace/Logo.tsx new file mode 100644 index 0000000000000..2f70a8ee278e7 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Rackspace/Logo.tsx @@ -0,0 +1,18 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Rackspace: FC> = props => ( + + + +); + +export default Rackspace; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Rackspace/index.ts b/packages/ui-components/src/Icons/PartnerLogos/Rackspace/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Rackspace/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Scaleway/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/Scaleway/Favicon.tsx new file mode 100644 index 0000000000000..65fe0585b99b0 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Scaleway/Favicon.tsx @@ -0,0 +1,30 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Scaleway: FC> = props => ( + + + + + +); + +export default Scaleway; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Scaleway/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/Scaleway/Logo.tsx new file mode 100644 index 0000000000000..8c54f7ce891f9 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Scaleway/Logo.tsx @@ -0,0 +1,68 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Scaleway: FC> = props => ( + + + + + + + + + + + + + +); + +export default Scaleway; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Scaleway/index.ts b/packages/ui-components/src/Icons/PartnerLogos/Scaleway/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Scaleway/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Sentry/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/Sentry/Favicon.tsx new file mode 100644 index 0000000000000..26b826e128f39 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Sentry/Favicon.tsx @@ -0,0 +1,20 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Sentry: FC> = props => ( + + + +); + +export default Sentry; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Sentry/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/Sentry/Logo.tsx new file mode 100644 index 0000000000000..429ecd0f3cc01 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Sentry/Logo.tsx @@ -0,0 +1,19 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Sentry: FC> = props => ( + + + +); + +export default Sentry; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Sentry/index.ts b/packages/ui-components/src/Icons/PartnerLogos/Sentry/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Sentry/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Vercel/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/Vercel/Favicon.tsx new file mode 100644 index 0000000000000..1685490bcd3fa --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Vercel/Favicon.tsx @@ -0,0 +1,18 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Vercel: FC> = props => ( + + + +); + +export default Vercel; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Vercel/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/Vercel/Logo.tsx new file mode 100644 index 0000000000000..858cca55d261c --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Vercel/Logo.tsx @@ -0,0 +1,23 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Vercel: FC> = props => ( + + + +); + +export default Vercel; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Vercel/index.ts b/packages/ui-components/src/Icons/PartnerLogos/Vercel/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Vercel/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Vlt/Favicon.tsx b/packages/ui-components/src/Icons/PartnerLogos/Vlt/Favicon.tsx new file mode 100644 index 0000000000000..ede3e459a7e93 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Vlt/Favicon.tsx @@ -0,0 +1,23 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Vlt: FC> = props => ( + + + +); + +export default Vlt; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Vlt/Logo.tsx b/packages/ui-components/src/Icons/PartnerLogos/Vlt/Logo.tsx new file mode 100644 index 0000000000000..ede3e459a7e93 --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Vlt/Logo.tsx @@ -0,0 +1,23 @@ +import classNames from 'classnames'; +import type { FC, SVGProps } from 'react'; + +const Vlt: FC> = props => ( + + + +); + +export default Vlt; diff --git a/packages/ui-components/src/Icons/PartnerLogos/Vlt/index.ts b/packages/ui-components/src/Icons/PartnerLogos/Vlt/index.ts new file mode 100644 index 0000000000000..b4cd37328b21d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/Vlt/index.ts @@ -0,0 +1,4 @@ +import Favicon from './Favicon'; +import Logo from './Logo'; + +export { Favicon, Logo }; diff --git a/packages/ui-components/src/Icons/PartnerLogos/index.ts b/packages/ui-components/src/Icons/PartnerLogos/index.ts new file mode 100644 index 0000000000000..fe46cde4ce60d --- /dev/null +++ b/packages/ui-components/src/Icons/PartnerLogos/index.ts @@ -0,0 +1,35 @@ +import * as ARM from './ARM'; +import * as CLOUDFLARE from './Cloudflare'; +import * as CROWDIN from './Crowdin'; +import * as DATADOG from './DataDog'; +import * as DIGITALOCEAN from './DigitalOcean'; +import * as EQUINIXMETAL from './EquinixMetal'; +import * as HERODEVS from './HeroDevs'; +import * as IBM from './IBM'; +import * as MICROSOFT from './Microsoft'; +import * as NODESOURCE from './NodeSource'; +import * as OPENSSF from './OpenSSF'; +import * as RACKSPACE from './Rackspace'; +import * as SCALEWAY from './Scaleway'; +import * as SENTRY from './Sentry'; +import * as VERCEL from './Vercel'; +import * as VLT from './Vlt'; + +export { + ARM, + CLOUDFLARE, + CROWDIN, + DATADOG, + DIGITALOCEAN, + EQUINIXMETAL, + HERODEVS, + IBM, + MICROSOFT, + NODESOURCE, + OPENSSF, + RACKSPACE, + SCALEWAY, + SENTRY, + VERCEL, + VLT, +};