"use client"; import type {FC} from "react"; import type {AvatarProps} from "@heroui/react"; import type {Sponsor} from "@/libs/docs/sponsors"; import {useMemo, useRef} from "react"; import {Avatar, Button, Spacer, Tooltip} from "@heroui/react"; import {clamp} from "@heroui/shared-utils"; import {usePostHog} from "posthog-js/react"; import {sectionWrapper, titleWrapper, title, subtitle} from "../primitives"; import {FeaturesGrid} from "./features-grid"; import {OpenCollectiveIcon, PatreonIcon, HeartBoldIcon, PlusLinearIcon} from "@/components/icons"; import {SPONSOR_TIERS, SPONSOR_COLORS, getTier} from "@/libs/docs/sponsors"; import {SonarPulse} from "@/components/sonar-pulse"; import {useIsMobile} from "@/hooks/use-media-query"; export interface SupportProps { sponsors: Sponsor[]; } const supportAccounts = [ { title: "Open Collective", description: "Sponsor the HeroUI maintainers.", icon: , href: "https://opencollective.com/heroui", isExternal: true, }, { title: "Patreon", description: "Sponsor the creator, Junior Garcia.", icon: , href: "https://www.patreon.com/jrgarciadev?fan_landing=true", isExternal: true, }, ]; const SONAR_PULSE_SIZE = 80; const SONAR_PULSE_CIRCLES_COUNT = 4; const SONAR_PULSE_RADIUS = 130; const getSponsorName = (sponsor: Sponsor) => { if (!sponsor.name) { return ""; } return sponsor.name.slice(0, 2).toUpperCase(); }; const getSponsorSize = (sponsor: Sponsor, isMobile: boolean) => { let size: AvatarProps["size"] = "md"; const tier = sponsor.tier || getTier(sponsor.totalAmountDonated); switch (tier) { case SPONSOR_TIERS.BRONZE: size = isMobile ? "sm" : "md"; break; case SPONSOR_TIERS.SILVER: size = isMobile ? "sm" : "md"; break; case SPONSOR_TIERS.GOLD: size = isMobile ? "md" : "lg"; break; case SPONSOR_TIERS.PLATINUM: size = isMobile ? "md" : "lg"; break; default: size = isMobile ? "sm" : "md"; } return size; }; const getSponsorColor = (sponsor: Sponsor) => { const tier = sponsor.tier || getTier(sponsor.totalAmountDonated); return SPONSOR_COLORS[tier] || "default"; }; const getSponsorAvatarStyles = (index: number, sponsors: Sponsor[] = []) => { const angle = (index * 360) / sponsors.length; const radius = SONAR_PULSE_RADIUS; // position the avatar randomly inside the sonar pulse const randomRadius = clamp(Math.floor((index + 1) * radius), radius * 0.4, radius); const x = randomRadius * Math.cos((angle * Math.PI) / 180); const y = randomRadius * Math.sin((angle * Math.PI) / 180); return { transform: `translate(${x}px, ${y}px)`, }; }; export const Support: FC = ({sponsors = []}) => { const sonarRef = useRef(null); const isMobile = useIsMobile(); const posthog = usePostHog(); const handleExternalLinkClick = (href: string) => { if (!href) return; window.open(href, "_blank"); }; const handleBecomeSponsor = () => { posthog.capture("Support - Become a sponsor", { action: "click", category: "landing-page", }); handleExternalLinkClick(supportAccounts[0].href); }; const renderSponsors = useMemo(() => { if (!sponsors.length) return null; return (
{sponsors.map((sponsor, index) => ( handleExternalLinkClick(sponsor["website"] || sponsor["profile"])} /> ))}
); }, [isMobile, sponsors]); return (

Support HeroUI 

Using HeroUI in a profit-making product, as a freelancer, or for fun projects? Your contributions will help to make HeroUI better.

} playState="running" size={SONAR_PULSE_SIZE} > {renderSponsors}
); };