@@ -38,6 +38,8 @@ import {
3838 type CommunityStats ,
3939 type CommunityGoal ,
4040 type Subscriber ,
41+ getReferralCount ,
42+ getMedianReferralCount ,
4143} from "./db.js" ;
4244import { PROJECTS , getProjectForBatch , type ProjectInfo } from "./project-metadata.js" ;
4345import { createSessionToken , getSessionEmail } from "./magic-link.js" ;
@@ -250,12 +252,15 @@ function renderDashboardPage(opts: {
250252 batchDenomMap : Map < string , string > ;
251253 totalRetiredCents : number ;
252254 subscriptions : Array < { plan : string ; amountCents : number ; billingInterval : "monthly" | "yearly" } > ;
255+ referralCode : string ;
256+ referralCount : number ;
257+ isTopReferrer : boolean ;
253258} ) : string {
254259 const {
255260 email, plan, memberSince, cumulative, monthly, badges, manageUrl,
256261 amountCents, billingInterval, baseUrl, nextRetirementDate, transactions, communityStats,
257262 regenAddress, projectCards, communityGoal, communityTotalCredits, communitySubscriberCount,
258- batchDenomMap, totalRetiredCents, subscriptions,
263+ batchDenomMap, totalRetiredCents, subscriptions, referralCode , referralCount , isTopReferrer ,
259264 } = opts ;
260265 const isYearly = billingInterval === "yearly" ;
261266
@@ -264,11 +269,6 @@ function renderDashboardPage(opts: {
264269 const retiredCents = totalRetiredCents > 0
265270 ? totalRetiredCents
266271 : ( cumulative . total_contribution_cents > 0 ? cumulative . total_contribution_cents : 0 ) ;
267- const shareText = encodeURIComponent (
268- `I'm funding ecological regeneration through my AI usage with @RegenCompute by @regen_network. Join the community and make your AI sessions count.`
269- ) ;
270- const shareUrl = encodeURIComponent ( baseUrl ) ;
271-
272272 // Profile link
273273 const profileUrl = regenAddress
274274 ? `https://app.regen.network/profiles/${ regenAddress } /portfolio`
@@ -568,12 +568,13 @@ function renderDashboardPage(opts: {
568568 <tbody>
569569 ${ transactions . map ( t => {
570570 const date = new Date ( t . created_at ) . toLocaleDateString ( "en-US" , { month : "short" , day : "numeric" , year : "numeric" } ) ;
571- const typeLabel = t . type === "subscription" ? "Subscription" : t . type === "topup" ? "One-time boost" : "Retirement" ;
572- const typeColor = t . type === "subscription" ? "var(--regen-teal)" : t . type === "topup" ? "var(--regen-green)" : "var(--regen-navy)" ;
571+ const isReferralBonus = t . type === "referral_bonus" ;
572+ const typeLabel = isReferralBonus ? "Referral bonus" : t . type === "subscription" ? "Subscription" : t . type === "topup" ? "One-time boost" : "Retirement" ;
573+ const typeColor = isReferralBonus ? "#7c3aed" : t . type === "subscription" ? "var(--regen-teal)" : t . type === "topup" ? "var(--regen-green)" : "var(--regen-navy)" ;
573574 const hasRetirementTx = ! ! t . retirement_tx_hash ;
574- const statusLabel = hasRetirementTx ? "Retired" : "Paid" ;
575- const statusBg = hasRetirementTx ? "#f0f7f2" : "#eff6ff" ;
576- const statusColor = hasRetirementTx ? "#2d6a4f" : "#1e40af" ;
575+ const statusLabel = isReferralBonus ? ( hasRetirementTx ? "Executed" : "Pending" ) : hasRetirementTx ? "Retired" : "Paid" ;
576+ const statusBg = isReferralBonus ? ( hasRetirementTx ? "#f5f3ff" : "#fef3c7" ) : hasRetirementTx ? "#f0f7f2" : "#eff6ff" ;
577+ const statusColor = isReferralBonus ? ( hasRetirementTx ? "#5b21b6" : "#92400e" ) : hasRetirementTx ? "#2d6a4f" : "#1e40af" ;
577578 const proofLink = hasRetirementTx
578579 ? ` <a href="https://www.mintscan.io/regen/tx/${ escapeHtml ( t . retirement_tx_hash ! ) } " target="_blank" rel="noopener" style="font-size:11px;">proof</a>`
579580 : "" ;
@@ -590,16 +591,68 @@ function renderDashboardPage(opts: {
590591 </div>
591592 ` : "" }
592593
593- <!-- Share -->
594+ <!-- Referrals -->
594595 <div style="margin-bottom:32px;">
595- <div style="background:var(--regen-white);border:1px solid var(--regen-gray-200);border-radius:var(--regen-radius);padding:24px;text-align:center;">
596- <p style="font-size:14px;color:var(--regen-gray-500);margin:0 0 12px;">Invite others to make their AI usage regenerative.</p>
597- <div class="regen-share-btns">
598- <a class="regen-share-btn regen-share-btn--x" href="https://twitter.com/intent/tweet?text=${ shareText } &url=${ shareUrl } " target="_blank" rel="noopener">Post on X</a>
599- <a class="regen-share-btn regen-share-btn--linkedin" href="https://www.linkedin.com/sharing/share-offsite/?url=${ shareUrl } " target="_blank" rel="noopener">Share on LinkedIn</a>
596+ <h2 class="regen-section-title" style="font-size:20px;">Invite Friends, Protect Wildlife</h2>
597+ <div style="background:var(--regen-white);border:1px solid var(--regen-gray-200);border-radius:var(--regen-radius);overflow:hidden;">
598+ <div style="padding:24px 24px 0;">
599+ <p style="font-size:15px;color:var(--regen-gray-700);margin:0 0 8px;line-height:1.6;">
600+ Your referrals directly fund jaguar conservation, support indigenous-led stewardship, and sequester carbon. Every person you invite amplifies your ecological impact.
601+ </p>
602+ <p style="font-size:15px;color:var(--regen-gray-700);margin:0 0 16px;line-height:1.6;">
603+ Your friend gets their <strong>first month free</strong>. You earn a <strong>bonus credit retirement</strong>.
604+ </p>
605+ </div>
606+
607+ <!-- Stats + encouragement -->
608+ <div style="padding:0 24px 16px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;">
609+ <div style="display:flex;align-items:center;gap:8px;">
610+ <span style="display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;background:#f5f3ff;border-radius:50%;font-size:16px;font-weight:800;color:#7c3aed;">${ referralCount } </span>
611+ <span style="font-size:14px;color:var(--regen-gray-600);font-weight:600;">${ referralCount === 1 ? "referral" : "referrals" } </span>
612+ </div>
613+ ${ isTopReferrer ? `
614+ <span style="display:inline-block;font-size:12px;font-weight:700;background:#f0fdf4;color:#166534;padding:4px 12px;border-radius:10px;">
615+ Top referrer — keep it up!
616+ </span>
617+ ` : referralCount > 0 ? `
618+ <span style="display:inline-block;font-size:12px;font-weight:600;color:var(--regen-gray-500);">
619+ Share more to join the top referrers
620+ </span>
621+ ` : `
622+ <span style="display:inline-block;font-size:12px;font-weight:600;color:var(--regen-gray-500);">
623+ Share your link below to get started
624+ </span>
625+ ` }
626+ </div>
627+
628+ <!-- Referral link -->
629+ <div style="padding:16px 24px;background:#f0fdf4;border-top:1px solid #bbf7d0;">
630+ <p style="margin:0 0 8px;font-size:13px;font-weight:600;color:#166534;">Your referral link</p>
631+ <div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
632+ <code id="refLink" style="flex:1;min-width:200px;padding:8px 12px;background:#fff;border:1px solid #d1d5db;border-radius:6px;font-size:13px;color:var(--regen-navy);word-break:break-all;">${ baseUrl } /r/${ escapeHtml ( referralCode ) } </code>
633+ <button onclick="copyRefLink()" style="padding:8px 16px;background:#4FB573;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">Copy</button>
634+ </div>
635+ </div>
636+
637+ <!-- Share buttons -->
638+ <div style="padding:16px 24px;text-align:center;border-top:1px solid var(--regen-gray-200);">
639+ <div class="regen-share-btns">
640+ <a class="regen-share-btn regen-share-btn--x" href="https://twitter.com/intent/tweet?text=${ encodeURIComponent ( "I use @RegenCompute to make my AI sessions fund ecological regeneration. Use my link for a free first month:" ) } &url=${ encodeURIComponent ( `${ baseUrl } /r/${ referralCode } ` ) } " target="_blank" rel="noopener">Post on X</a>
641+ <a class="regen-share-btn regen-share-btn--linkedin" href="https://www.linkedin.com/sharing/share-offsite/?url=${ encodeURIComponent ( `${ baseUrl } /r/${ referralCode } ` ) } " target="_blank" rel="noopener">Share on LinkedIn</a>
642+ </div>
600643 </div>
601644 </div>
602645 </div>
646+ <script>
647+ function copyRefLink() {
648+ var el = document.getElementById('refLink');
649+ navigator.clipboard.writeText(el.textContent).then(function() {
650+ var orig = el.textContent;
651+ el.textContent = 'Copied!';
652+ setTimeout(function() { el.textContent = orig; }, 2000);
653+ });
654+ }
655+ </script>
603656
604657 <!-- Community goal / stats -->
605658 ${ goalHtml }
@@ -949,6 +1002,12 @@ export function createDashboardRoutes(
9491002 billingInterval : ( s . billing_interval === "yearly" ? "yearly" : "monthly" ) as "monthly" | "yearly" ,
9501003 } ) ) ;
9511004
1005+ // Referral stats
1006+ const referralCode = viewUser ?. referral_code ?? user . referral_code ;
1007+ const referralCount = getReferralCount ( db , viewUser ?. id ?? user . id ) ;
1008+ const medianReferrals = getMedianReferralCount ( db ) ;
1009+ const isTopReferrer = referralCount > 0 && referralCount >= medianReferrals ;
1010+
9521011 res . setHeader ( "Content-Type" , "text/html" ) ;
9531012 res . send ( renderDashboardPage ( {
9541013 email,
@@ -972,6 +1031,9 @@ export function createDashboardRoutes(
9721031 batchDenomMap,
9731032 totalRetiredCents,
9741033 subscriptions,
1034+ referralCode,
1035+ referralCount,
1036+ isTopReferrer,
9751037 } ) ) ;
9761038 } ) ;
9771039
0 commit comments