diff --git a/src/api/leaderboard.ts b/src/api/leaderboard.ts index 5e87b120f..5589ab174 100644 --- a/src/api/leaderboard.ts +++ b/src/api/leaderboard.ts @@ -20,6 +20,10 @@ export type ListLeaderboardStatsResponse = { githubContributors: { currentMonth: LeaderboardUserDetails[]; }; + referrals: { + currentMonth: LeaderboardUserDetails[]; + lifetime: LeaderboardUserDetails[]; + }; }; export function leaderboardApi(context: APIContext) { diff --git a/src/components/AccountStreak/AccountStreak.tsx b/src/components/AccountStreak/AccountStreak.tsx index 750d966c4..306d660ec 100644 --- a/src/components/AccountStreak/AccountStreak.tsx +++ b/src/components/AccountStreak/AccountStreak.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import { isLoggedIn } from '../../lib/jwt'; import { httpGet } from '../../lib/http'; import { useToast } from '../../hooks/use-toast'; -import { Flame, X, Zap, ZapOff } from 'lucide-react'; +import { Zap, ZapOff } from 'lucide-react'; import { useOutsideClick } from '../../hooks/use-outside-click'; import { StreakDay } from './StreakDay'; import { @@ -11,15 +11,8 @@ import { } from '../../stores/page.ts'; import { useStore } from '@nanostores/react'; import { cn } from '../../lib/classname.ts'; -import { $accountStreak } from '../../stores/streak.ts'; - -type StreakResponse = { - count: number; - longestCount: number; - previousCount?: number | null; - firstVisitAt: Date; - lastVisitAt: Date; -}; +import { $accountStreak, type StreakResponse } from '../../stores/streak.ts'; +import { InviteFriends } from './InviteFriends.tsx'; type AccountStreakProps = {}; @@ -184,11 +177,10 @@ export function AccountStreak(props: AccountStreakProps) {

Visit every day to keep your streak going!

-

- - See how you compare to others - -

+ + )} diff --git a/src/components/AccountStreak/InviteFriends.tsx b/src/components/AccountStreak/InviteFriends.tsx new file mode 100644 index 000000000..ff3885818 --- /dev/null +++ b/src/components/AccountStreak/InviteFriends.tsx @@ -0,0 +1,92 @@ +import { Copy, Heart } from 'lucide-react'; +import { useAuth } from '../../hooks/use-auth'; +import { useCopyText } from '../../hooks/use-copy-text'; +import { cn } from '../../lib/classname'; +import { CheckIcon } from '../ReactIcons/CheckIcon'; +import {TrophyEmoji} from "../ReactIcons/TrophyEmoji.tsx"; + +type InviteFriendsProps = { + refByUserCount: number; +}; + +export function InviteFriends(props: InviteFriendsProps) { + const { refByUserCount } = props; + + const user = useAuth(); + const { copyText, isCopied } = useCopyText(); + + const referralLink = new URL( + `/signup?rc=${user?.id}`, + import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh', + ).toString(); + + return ( +
+

+ Invite people to join roadmap.sh +

+
+
+ {Array.from({ length: 10 }).map((_, index) => ( + + ))} +
+ {refByUserCount === 0 && ( +

You haven't invited anyone yet.

+ )} + + {refByUserCount > 0 && refByUserCount < 10 && ( +

{refByUserCount} of 10 users joined

+ )} + + {refByUserCount >= 10 && ( +

+ 🎉 You've invited {refByUserCount} users +

+ )} +
+

+ Share{' '} + {' '} + with anyone you think would benefit from roadmap.sh +

+ +

+ + See how you rank on the leaderboard + +

+
+ ); +} diff --git a/src/components/AuthenticationFlow/EmailSignupForm.tsx b/src/components/AuthenticationFlow/EmailSignupForm.tsx index f98857be6..f1c0f86f6 100644 --- a/src/components/AuthenticationFlow/EmailSignupForm.tsx +++ b/src/components/AuthenticationFlow/EmailSignupForm.tsx @@ -1,5 +1,7 @@ -import { type FormEvent, useState } from 'react'; +import { type FormEvent, useEffect, useState } from 'react'; import { httpPost } from '../../lib/http'; +import { deleteUrlParam, getUrlParams } from '../../lib/browser'; +import { isLoggedIn, setAIReferralCode } from '../../lib/jwt'; type EmailSignupFormProps = { isDisabled?: boolean; @@ -9,6 +11,9 @@ type EmailSignupFormProps = { export function EmailSignupForm(props: EmailSignupFormProps) { const { isDisabled, setIsDisabled } = props; + const { rc: referralCode } = getUrlParams() as { + rc?: string; + }; const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [name, setName] = useState(''); @@ -47,6 +52,16 @@ export function EmailSignupForm(props: EmailSignupFormProps) { )}`; }; + useEffect(() => { + if (!referralCode || isLoggedIn()) { + deleteUrlParam('rc'); + return; + } + + setAIReferralCode(referralCode); + deleteUrlParam('rc'); + }, []); + return (