-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
chore: partners/sponsors page #7991
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+2,838
−1
Merged
Changes from 23 commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
33dcf64
chore: add partner section on homepage
bjohansebas 5094798
chore: template for partner page
bjohansebas 6a5568c
ui: add name tooltip for partnerIcon
bjohansebas e646e29
chore: implement list of logos on download and partners page
bjohansebas 8302e8a
chore: add partner list on security blog post
bjohansebas 2f0e2be
feat: enhance PartnersLogoList to filter partners by category and all…
bjohansebas 435eb91
feat: add new partner categories and update partner weights
bjohansebas 5fd31f8
feat: update partner selection logic to use weighted randomization
bjohansebas 91b6535
feat: update button href to include UTM parameters for tracking
bjohansebas 5c79be3
feat: implement padding to a company tooltip
bjohansebas 8c55aaf
chore: implement supporters component
bjohansebas 86cebfa
feat: enhance partners page with detailed descriptions
bjohansebas 9570258
ui-components: add more partner logos
bjohansebas 96bc9f9
ui-components: add more partners logos
bjohansebas edf5259
clean up
bjohansebas 625ed9c
feat: update supporters and partners sections
bjohansebas cdaa6d6
feat: enhance partner list functionality with sorting options
bjohansebas d737364
fix typos and small nits
bjohansebas 4da9fad
refactor: update partners and supporters sections by removing unused …
bjohansebas 26c9f8c
feat: add comprehensive partners documentation outlining addition, re…
bjohansebas 844cd09
style: center-align partner support heading and adjust text balance
bjohansebas 13bd737
Merge branch 'main' of https://github.com/nodejs/nodejs.org into part…
bjohansebas 33016f4
fixup lint error
bjohansebas 2e0d066
Merge branch 'main' of https://github.com/nodejs/nodejs.org into part…
bjohansebas 32b876d
apply suggestions
bjohansebas 31db2c5
refactor: simplify partner list components by introducing usePartners…
bjohansebas 0f3b343
chore: empty spaces removed from svg files
canerakdas 02222b0
chore: gql to rest
canerakdas 6264c99
chore: comment lines removed
canerakdas c12045e
fix: remove auto width from svg components
canerakdas 9195ee0
chore: tooltip styling
canerakdas 5b470d5
chore: reorganized partner logos
canerakdas 392d60e
feat: supporters provider created
canerakdas 430839f
refactor: filter and slice logic separated
canerakdas 2790467
chore: new line
canerakdas a528163
chore: json files moved into the public/static
canerakdas 864ef6c
chore: reorganize svg attributes
canerakdas d0b5621
refactor: randomPartnerList utils
canerakdas 31f59a5
chore: unused file removed
canerakdas 51a983d
docs: update partners json path
canerakdas fca86e7
chore: docs comments
canerakdas e43ac52
chore: resolve conflict
canerakdas 4aa8ba8
chore: todos moved into the issues
canerakdas f2dc41b
chore: remove checking uniqueness
canerakdas 3bef31f
refactor: deterministic shuffle algorithm
canerakdas 881ddd2
chore: revert mdx changes
canerakdas 207b401
chore: revert mdx changes
canerakdas 5719e96
chore: revert mdx changes
canerakdas dd941c7
chore: revert mdx changes
canerakdas dfb8ec9
fix: lint issue
canerakdas 335e1a5
chore: revert the /ja/ lint issue
canerakdas 6dee5ea
docs: Update apps/site/pages/en/about/partners.mdx
canerakdas b0be46a
chore: remove github sponsors mismention
canerakdas 0cac120
chore/debug: use named import from node:crypto
bmuenzenmeyer 19efadf
refactor: web crypto api
canerakdas 3e007d7
Update apps/site/next-data/generators/supportersData.mjs
canerakdas 4c2d6cb
refactor: move shuffle util into the array utils
canerakdas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
13 changes: 13 additions & 0 deletions
13
apps/site/components/Common/Partners/PartnerIcon/index.module.css
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
apps/site/components/Common/Partners/PartnerIcon/index.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<typeof Skeleton>; | ||
|
||
const PartnersIcon: FC<PartnersIconProps> = ({ name, href, logo, loading }) => ( | ||
<Skeleton loading={loading} className="size-9 p-2"> | ||
<Tooltip content={<span className="px-2">{name}</span>}> | ||
<Button | ||
kind="secondary" | ||
href={`${href}/?utm_source=nodejs-website&utm_medium=Link`} | ||
className={style.partnerIcon} | ||
> | ||
{cloneElement(logo, { | ||
ovflowd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
width: 'auto', | ||
height: '16px', | ||
})} | ||
</Button> | ||
</Tooltip> | ||
</Skeleton> | ||
); | ||
|
||
export default PartnersIcon; |
19 changes: 19 additions & 0 deletions
19
apps/site/components/Common/Partners/PartnerLogo/index.module.css
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
27
apps/site/components/Common/Partners/PartnerLogo/index.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: 'auto', | ||
height: '16px', | ||
})} | ||
</Button> | ||
</Skeleton> | ||
); | ||
|
||
export default PartnersLogo; |
9 changes: 9 additions & 0 deletions
9
apps/site/components/Common/Partners/PartnersIconList/index.module.css
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
69 changes: 69 additions & 0 deletions
69
apps/site/components/Common/Partners/PartnersIconList/index.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<PartnersIconListProps> = ({ | ||
maxLength = 6, | ||
categories, | ||
}) => { | ||
const initialRenderer = useRef(true); | ||
|
||
const [seedList, setSeedList] = useState<Array<Partners>>( | ||
ICON_PARTNERS.slice(0, maxLength) | ||
); | ||
|
||
useEffect(() => { | ||
ovflowd marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
// 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 ( | ||
<div className={style.partnersIconList}> | ||
{seedList.map((partner, index) => ( | ||
<PartnerIcon | ||
{...partner} | ||
key={index} | ||
loading={initialRenderer.current} | ||
/> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
export default PartnersIconList; |
8 changes: 8 additions & 0 deletions
8
apps/site/components/Common/Partners/PartnersLogoList/index.module.css
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
77 changes: 77 additions & 0 deletions
77
apps/site/components/Common/Partners/PartnersLogoList/index.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<PartnersLogoListProps> = ({ | ||
maxLength = 3, | ||
sort = 'weight', | ||
categories, | ||
}) => { | ||
const initialRenderer = useRef(true); | ||
|
||
const [seedList, setSeedList] = useState<Array<Partners>>(() => { | ||
ovflowd marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
if (maxLength === null) { | ||
return LOGO_PARTNERS.filter( | ||
partner => !categories || partner.categories.includes(categories) | ||
); | ||
} | ||
return LOGO_PARTNERS.slice(0, maxLength); | ||
}); | ||
|
||
useEffect(() => { | ||
ovflowd marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
// 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, | ||
}) | ||
); | ||
}, 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 ( | ||
<div className={style.partnersLogoList}> | ||
{seedList.map((partner, index) => ( | ||
<PartnerLogo | ||
{...partner} | ||
key={index} | ||
loading={initialRenderer.current} | ||
/> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
export default PartnersLogoList; |
canerakdas marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import type { PartnerCategory, Partners } from '#site/types/partners.js'; | ||
|
||
function randomPartnerList( | ||
ovflowd marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
partners: Array<Partners>, | ||
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 }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Array<Supporters>>; | ||
}; | ||
|
||
// TODO: Sort supporters by all time contribution amount and link to their Open Collective page | ||
canerakdas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
const SupportersList: FC<SupportersProps> = ({ supporters }) => { | ||
const supportersList = use(supporters); | ||
|
||
return ( | ||
<div className="flex max-w-full flex-wrap items-center justify-center gap-1"> | ||
{supportersList.map(({ name, image }, i) => ( | ||
<Avatar nickname={name} image={image} key={`${name}-${i}`} /> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
export default SupportersList; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import type { FC, PropsWithChildren } from 'react'; | ||
|
||
import { fetchOpenCollectiveData } from '#site/next-data/generators/supportersData.mjs'; | ||
canerakdas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
import type { Supporters } from '#site/types'; | ||
|
||
import SupportersList from './Common/Supporters'; | ||
|
||
const WithSupporters: FC<PropsWithChildren> = () => { | ||
const supporters = fetchOpenCollectiveData() as Promise<Array<Supporters>>; | ||
|
||
return ( | ||
<div className="flex max-w-full flex-wrap items-center gap-1"> | ||
<SupportersList supporters={supporters} /> | ||
</div> | ||
); | ||
}; | ||
|
||
export default WithSupporters; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.