-
Notifications
You must be signed in to change notification settings - Fork 52
feat(pages): add sponsors #124
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
Open
avivkeller
wants to merge
8
commits into
main
Choose a base branch
from
sponsors
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
7959475
feat(pages): add sponsors
avivkeller 518116b
fixup!
avivkeller 4654144
fixup!
avivkeller 20974b5
fixup!
avivkeller 38b517a
fixup!
avivkeller 0be32d8
fixup!
avivkeller 88abe84
fixup!
avivkeller efbcb0d
fixup!
avivkeller 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
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 |
|---|---|---|
|
|
@@ -3,3 +3,4 @@ out | |
| *.generated.* | ||
| /.cache | ||
| /pages/api | ||
| /generated | ||
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 |
|---|---|---|
|
|
@@ -3,4 +3,5 @@ out | |
| *.generated.* | ||
| /.cache | ||
| /pages/api | ||
| versions.json | ||
| versions.json | ||
| /generated | ||
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 @@ | ||
| /** | ||
| * Small diamond/gem glyph used to mark a sponsorship tier. | ||
| * | ||
| * @param {import('react').SVGProps<SVGSVGElement>} props | ||
| */ | ||
| export default function DiamondIcon(props) { | ||
| return ( | ||
| <svg | ||
| viewBox="0 0 24 24" | ||
| width="1em" | ||
| height="1em" | ||
| fill="none" | ||
| aria-hidden="true" | ||
| {...props} | ||
| > | ||
| <path | ||
| d="M5.5 3h13l3 5.2L12 21 2.5 8.2 5.5 3Z" | ||
| fill="currentColor" | ||
| opacity="0.18" | ||
| /> | ||
| <path | ||
| d="M5.5 3h13l3 5.2L12 21 2.5 8.2 5.5 3Zm0 0 6.5 5.2L18.5 3M2.5 8.2h19M12 21V8.2" | ||
| stroke="currentColor" | ||
| strokeWidth="1.3" | ||
| strokeLinejoin="round" | ||
| strokeLinecap="round" | ||
| /> | ||
| </svg> | ||
| ); | ||
| } |
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
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,28 @@ | ||
| import classNames from 'classnames'; | ||
|
|
||
| import styles from './index.module.css'; | ||
|
|
||
| /** | ||
| * Centered section heading: an uppercase eyebrow, a title and an optional description. | ||
| * | ||
| * @param {import('react').ComponentProps<'header'> & { | ||
| * eyebrow?: import('react').ReactNode, | ||
| * title: import('react').ReactNode, | ||
| * description?: import('react').ReactNode, | ||
| * }} props | ||
| */ | ||
| export default function SectionHeader({ | ||
| eyebrow, | ||
| title, | ||
| description, | ||
| className, | ||
| ...props | ||
| }) { | ||
| return ( | ||
| <header {...props} className={classNames(styles.header, className)}> | ||
| {eyebrow && <p className={styles.eyebrow}>{eyebrow}</p>} | ||
| <h2 className={styles.title}>{title}</h2> | ||
| {description && <p className={styles.description}>{description}</p>} | ||
| </header> | ||
| ); | ||
| } |
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,17 @@ | ||
| @reference "../../styles/index.css"; | ||
|
|
||
| .header { | ||
| @apply mx-auto flex max-w-2xl flex-col items-center gap-3 text-center; | ||
| } | ||
|
|
||
| .eyebrow { | ||
| @apply m-0 text-xs font-semibold tracking-[0.12em] uppercase text-blue-600 dark:text-blue-400; | ||
| } | ||
|
|
||
| .title { | ||
| @apply m-0 text-3xl font-bold tracking-tight text-neutral-900 sm:text-4xl dark:text-white; | ||
| } | ||
|
|
||
| .description { | ||
| @apply m-0 text-base leading-relaxed text-neutral-600 dark:text-neutral-300; | ||
| } |
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,52 @@ | ||
| import AvatarGroup from '@node-core/ui-components/Common/AvatarGroup'; | ||
| import classNames from 'classnames'; | ||
|
|
||
| import styles from './index.module.css'; | ||
|
|
||
| const OC_BASE = 'https://opencollective.com'; | ||
|
|
||
| const initialsOf = name => | ||
| name | ||
| .split(/\s+/) | ||
| .slice(0, 2) | ||
| .map(word => word[0]) | ||
| .join('') | ||
| .toUpperCase(); | ||
|
|
||
| /** | ||
| * Wall of individual backer avatars with an overflow chip, plus a link to the full list | ||
| * on Open Collective. Built on the shared {@link AvatarGroup}. | ||
| * | ||
| * @param {import('react').ComponentProps<'div'> & { | ||
| * backers: Array<{ name: string, slug: string, imageUrl: string|null, allTime: { value: number, tier: string|null } }>, | ||
| * limit?: number, | ||
| * }} props | ||
| */ | ||
| export default function BackerWall({ | ||
| backers, | ||
| limit = 48, | ||
| className, | ||
| ...props | ||
| }) { | ||
| const avatars = backers.map(backer => ({ | ||
| image: backer.imageUrl ?? undefined, | ||
| name: backer.name, | ||
| nickname: backer.slug, | ||
| fallback: initialsOf(backer.name), | ||
| url: `${OC_BASE}/${backer.slug}`, | ||
| })); | ||
|
|
||
| return ( | ||
| <div {...props} className={classNames(styles.wall, className)}> | ||
| {CLIENT && <AvatarGroup avatars={avatars} limit={limit} size="medium" />} | ||
| <a | ||
| href={`${OC_BASE}/webpack/contributors`} | ||
| target="_blank" | ||
| rel="noreferrer noopener" | ||
| className={styles.link} | ||
| > | ||
| See all backers on Open Collective → | ||
| </a> | ||
| </div> | ||
| ); | ||
| } | ||
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"; | ||
|
|
||
| .wall { | ||
| @apply flex flex-col items-center gap-6; | ||
| } | ||
|
|
||
| .link { | ||
| @apply text-sm font-medium text-blue-600 no-underline hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300; | ||
| } |
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,86 @@ | ||
| import classNames from 'classnames'; | ||
| import Avatar from '@node-core/ui-components/Common/AvatarGroup/Avatar'; | ||
|
|
||
| import styles from './index.module.css'; | ||
|
|
||
| const formatUSD = value => `$${Math.round(value).toLocaleString('en-US')}`; | ||
|
|
||
| const amountLabel = (sponsor, metric) => | ||
| metric === 'monthly' | ||
| ? `${formatUSD(sponsor.monthly.value)} / mo` | ||
| : `${formatUSD(sponsor.allTime.value)} total`; | ||
|
|
||
| /** | ||
| * A single sponsor tile. The visual weight scales with `size` so higher tiers read larger, | ||
| * matching the sponsor wall in the design. `lg` renders an expanded card (used by the top | ||
| * tier); the smaller sizes render compact rows. | ||
| * | ||
| * @param {import('react').ComponentProps<'a'> & { | ||
| * sponsor: { name: string, slug: string, imageUrl: string|null, url: string, monthly: { value: number, tier: string|null }, allTime: { value: number, tier: string|null }, description?: string }, | ||
| * size?: 'lg'|'md'|'sm'|'xs', | ||
| * metric?: 'monthly'|'allTime', | ||
| * }} props | ||
| */ | ||
| export default function SponsorCard({ | ||
| sponsor, | ||
| size = 'md', | ||
| metric = 'monthly', | ||
| className, | ||
| ...props | ||
| }) { | ||
| const linkProps = { | ||
| href: sponsor.url, | ||
| target: '_blank', | ||
| rel: 'noreferrer noopener', | ||
| ...props, | ||
| }; | ||
|
avivkeller marked this conversation as resolved.
|
||
|
|
||
| if (size === 'lg') { | ||
| return ( | ||
| <a | ||
| {...linkProps} | ||
| className={classNames(styles.card, styles.lg, className)} | ||
| > | ||
| {CLIENT && ( | ||
|
avivkeller marked this conversation as resolved.
|
||
| <Avatar | ||
| name={sponsor.name} | ||
| image={sponsor.imageUrl} | ||
| size={'medium'} | ||
| /> | ||
| )} | ||
| <span className={styles.name}>{sponsor.name}</span> | ||
| {sponsor.description && ( | ||
| <p className={styles.description}>{sponsor.description}</p> | ||
| )} | ||
| <div className={styles.footer}> | ||
| <span className={styles.amount}>{amountLabel(sponsor, metric)}</span> | ||
| <span className={styles.visit}>Visit →</span> | ||
| </div> | ||
| </a> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <a | ||
| {...linkProps} | ||
| className={classNames(styles.card, styles[size], className)} | ||
| > | ||
| <div> | ||
| {CLIENT && ( | ||
|
avivkeller marked this conversation as resolved.
|
||
| <Avatar name={sponsor.name} image={sponsor.imageUrl} size={'small'} /> | ||
| )} | ||
| </div> | ||
| <span className={styles.body}> | ||
| <span className={styles.name}>{sponsor.name}</span> | ||
| {size !== 'xs' && ( | ||
| <span className={styles.amount}>{amountLabel(sponsor, metric)}</span> | ||
| )} | ||
| </span> | ||
| {size !== 'xs' && ( | ||
| <span className={styles.chevron} aria-hidden="true"> | ||
| › | ||
| </span> | ||
| )} | ||
| </a> | ||
| ); | ||
| } | ||
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,55 @@ | ||
| @reference "../../../styles/index.css"; | ||
|
|
||
| .card { | ||
| @apply flex rounded-xl border border-neutral-200 bg-white no-underline transition-colors duration-150 hover:border-blue-300 hover:bg-blue-50/40 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:border-blue-400/60 dark:hover:bg-blue-950/40; | ||
| } | ||
|
|
||
| /* Expanded card used by the top tier. */ | ||
| .lg { | ||
| @apply flex-col items-start gap-3 p-5; | ||
| } | ||
|
|
||
| /* Compact rows for the lower tiers. */ | ||
| .md { | ||
| @apply items-center gap-3 p-3.5; | ||
| } | ||
|
|
||
| .sm { | ||
| @apply items-center gap-2.5 p-3; | ||
| } | ||
|
|
||
| .xs { | ||
| @apply items-center gap-2 p-2.5; | ||
| } | ||
|
|
||
| .body { | ||
| @apply flex min-w-0 flex-col!; | ||
| } | ||
|
|
||
| .name { | ||
| @apply truncate text-sm font-semibold text-neutral-900 dark:text-white; | ||
| } | ||
|
|
||
| .lg .name { | ||
| @apply text-base; | ||
| } | ||
|
|
||
| .amount { | ||
| @apply text-xs text-neutral-500 dark:text-neutral-400; | ||
| } | ||
|
|
||
| .description { | ||
| @apply m-0 text-sm leading-relaxed text-neutral-600 dark:text-neutral-300; | ||
| } | ||
|
|
||
| .footer { | ||
| @apply mt-auto flex w-full items-center justify-between pt-1; | ||
| } | ||
|
|
||
| .visit { | ||
| @apply text-sm font-medium text-blue-600 dark:text-blue-400; | ||
| } | ||
|
|
||
| .chevron { | ||
| @apply ml-auto text-xl leading-none text-neutral-300 dark:text-neutral-600; | ||
| } |
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,31 @@ | ||
| import Tabs from '@node-core/ui-components/Common/Tabs'; | ||
| import classNames from 'classnames'; | ||
|
|
||
| import styles from './index.module.css'; | ||
|
|
||
| const TABS = [ | ||
| { key: 'monthly', label: 'Sort by Monthly' }, | ||
| { key: 'allTime', label: 'Sort by All-Time' }, | ||
| ]; | ||
|
|
||
| /** | ||
| * Controlled segmented control that picks the metric used to rank sponsors. Built on the | ||
| * shared {@link Tabs} primitive. | ||
| * | ||
| * @param {import('react').ComponentProps<typeof Tabs> & { | ||
| * value: 'monthly'|'allTime', | ||
| * onChange: (value: 'monthly'|'allTime') => void, | ||
| * }} props | ||
| */ | ||
| export default function SortToggle({ value, onChange, className, ...props }) { | ||
| return ( | ||
| <Tabs | ||
| aria-label="Sort sponsors by" | ||
| {...props} | ||
| tabs={TABS} | ||
| value={value} | ||
| onValueChange={onChange} | ||
| className={classNames(styles.toggle, className)} | ||
| /> | ||
| ); | ||
| } |
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,5 @@ | ||
| @reference "../../../styles/index.css"; | ||
|
|
||
| .toggle { | ||
| @apply inline-flex; | ||
| } |
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.