Skip to content

feat: supporter page and updated home page #7552

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/site/components/Common/NodejsLogo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { FC } from 'react';
import type { ComponentProps, FC } from 'react';

import Nodejs from '@/components/Icons/Logos/Nodejs';
import type { LogoVariant } from '@/types';

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

type NodejsLogoProps = {
type NodejsLogoProps = ComponentProps<typeof Nodejs> & {
variant?: LogoVariant;
};

const NodejsLogo: FC<NodejsLogoProps> = ({ variant = 'default' }) => (
<Nodejs variant={variant} className={style.nodejsLogo} />
const NodejsLogo: FC<NodejsLogoProps> = ({ variant = 'default', ...props }) => (
<Nodejs variant={variant} className={style.nodejsLogo} {...props} />
);

export default NodejsLogo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.supporterIcon {
@apply h-12
w-12
rounded-lg
bg-neutral-100
p-1;
}
24 changes: 24 additions & 0 deletions apps/site/components/Common/Supporters/SupporterIcon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Link from 'next/link';
import { cloneElement, type ComponentProps, type FC } from 'react';

import Skeleton from '@/components/Common/Skeleton';
import type { Supporter } from '@/types';

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

type SupporterIconProps = Supporter & ComponentProps<typeof Skeleton>;

const SupporterIcon: FC<SupporterIconProps> = ({
name,
href,
icon,
loading = false,
}) => (
<Skeleton loading={loading}>
<Link href={href} aria-label={name}>
{cloneElement(icon, { className: style.supporterIcon })}
</Link>
</Skeleton>
);

export default SupporterIcon;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.supporterIconList {
@apply flex
flex-row
flex-wrap
items-center
justify-center
gap-4;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use client';

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

import SupporterIcon from '@/components/Common/Supporters/SupporterIcon';
import { randomSupporterList } from '@/components/Common/Supporters/utils';
import { DEFAULT_SUPPORTERS_LIST } from '@/next.supporters.constants';
import type { Supporter } from '@/types';

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

type SupporterIconListProps = {
supporters: Array<Supporter>;
maxLength?: number;
};

const SupporterIconList: FC<SupporterIconListProps> = ({
supporters,
maxLength = 4,
}) => {
const initialRenderer = useRef(true);

const [seedList, setSeedList] = useState<Array<Supporter>>(
DEFAULT_SUPPORTERS_LIST
);

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(
randomSupporterList(
supporters,
Math.max(supporters.length, maxLength * 2)
)
);
}, 1000);

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.supporterIconList}>
{seedList.slice(0, maxLength).map((supporter, index) => (
<SupporterIcon
{...supporter}
loading={initialRenderer.current}
key={index}
/>
))}
</div>
);
};

export default SupporterIconList;
28 changes: 28 additions & 0 deletions apps/site/components/Common/Supporters/SupporterLogo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import classNames from 'classnames';
import { cloneElement, type FC } from 'react';

import Link from '@/components/Link';
import type { Supporter } from '@/types';

const SupporterLogoSizeByThreshold: Record<Supporter['threshold'], string> = {
0.1: 'h-8 w-8',
0.2: 'h-10 w-10',
0.3: 'h-12 w-12',
0.4: 'h-14 w-14',
0.5: 'h-16 w-16',
0.6: 'h-18 w-18',
0.7: 'h-20 w-20',
0.8: 'h-22 w-22',
0.9: 'h-24 w-24',
1: 'h-24 w-24',
};

const SupporterLogo: FC<Supporter> = ({ href, logo, name, threshold }) => (
<Link href={href} aria-label={name}>
{cloneElement(logo, {
className: classNames('p-1', SupporterLogoSizeByThreshold[threshold]),
})}
</Link>
);

export default SupporterLogo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.supporterLogoList {
@apply flex
flex-col
gap-2;
}

.supporterTier {
@apply flex
flex-wrap
items-center
justify-center
gap-4
last-of-type:mb-0
md:gap-6;
}

.supporterLogo {
@apply flex
items-center
justify-center
p-2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use client';

import type { FC } from 'react';
import { useMemo } from 'react';

import SupporterLogo from '@/components/Common/Supporters/SupporterLogo';
import type { Supporter } from '@/types';

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

type SupporterLogoListProps = {
supporters: Array<Supporter>;
};

const SupporterLogoList: FC<SupporterLogoListProps> = ({ supporters }) => {
const tiers = useMemo(() => {
const sortedSupporters = [...supporters].sort(
(a, b) => b.threshold - a.threshold
);

return sortedSupporters.reduce<Record<string, Array<Supporter>>>(
(acc, supporter) => ({
...acc,
[supporter.threshold]: [...(acc[supporter.threshold] ?? []), supporter],
}),
{}
);
}, [supporters]);

return (
<div className={styles.supporterLogoList}>
{Object.keys(tiers).map((threshold, index) => (
<div className={styles.supporterTier} key={index}>
{tiers[threshold].map((supporter, idx) => (
<div key={idx} className={styles.supporterLogo}>
<SupporterLogo
name={supporter.name}
logo={supporter.logo}
icon={supporter.icon}
href={supporter.href}
threshold={supporter.threshold}
/>
</div>
))}
</div>
))}
</div>
);
};

export default SupporterLogoList;
10 changes: 10 additions & 0 deletions apps/site/components/Common/Supporters/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Supporter } from '@/types';

export const randomSupporterList = (
supporters: Array<Supporter>,
pick: number
) =>
supporters
.sort(() => 0.5 - Math.random())
.slice(0, pick)
.sort((a, b) => b.threshold - a.threshold);
15 changes: 10 additions & 5 deletions apps/site/components/Containers/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import LinkedIn from '@/components/Icons/Social/LinkedIn';
import Mastodon from '@/components/Icons/Social/Mastodon';
import Slack from '@/components/Icons/Social/Slack';
import Twitter from '@/components/Icons/Social/Twitter';
import { siteNavigation } from '@/next.json.mjs';
import type { FooterConfig, SocialConfig } from '@/types';

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

Expand All @@ -21,15 +21,20 @@ const footerSocialIcons: Record<string, React.FC<SVGProps<SVGSVGElement>>> = {
bluesky: Bluesky,
};

const Footer: FC = () => {
type FooterProps = {
socialLinks: Array<SocialConfig>;
footerLinks: Array<FooterConfig>;
};

const Footer: FC<FooterProps> = ({ socialLinks, footerLinks }) => {
const t = useTranslations();

const openJSlink = siteNavigation.footerLinks.at(-1)!;
const openJSlink = footerLinks.at(-1)!;

return (
<footer className={styles.footer}>
<div className={styles.sectionPrimary}>
{siteNavigation.footerLinks.slice(0, -1).map(item => (
{footerLinks.slice(0, -1).map(item => (
<NavItem type="footer" href={item.link} key={item.link}>
{t(item.text)}
</NavItem>
Expand All @@ -42,7 +47,7 @@ const Footer: FC = () => {
</NavItem>

<div className={styles.social}>
{siteNavigation.socialLinks.map(link => {
{socialLinks.map(link => {
const SocialIcon = footerSocialIcons[link.icon];

return (
Expand Down
17 changes: 0 additions & 17 deletions apps/site/components/Downloads/DownloadButton/index.module.css

This file was deleted.

30 changes: 0 additions & 30 deletions apps/site/components/Downloads/DownloadButton/index.stories.tsx

This file was deleted.

51 changes: 0 additions & 51 deletions apps/site/components/Downloads/DownloadButton/index.tsx

This file was deleted.

Loading
Loading