WIP: feat: redesign invite and earn page#286
Conversation
✅ Deploy Preview for fancy-gelato-7cdad5 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
| }, 200); | ||
|
|
||
| return () => clearInterval(interval); | ||
| }, [inviteCount]); |
There was a problem hiding this comment.
Bug: Unnecessary animation wastes system resources.
The animation interval in NetworkVisualization continues running indefinitely even after all nodes are displayed. Once animatedNodes.length reaches nodes, the interval keeps firing every 200ms but only returns the same state, wasting CPU cycles. The interval should be cleared when animation completes.
d924650 to
a25fe6b
Compare
| <stop offset="0%" stopColor="#ec4899" stopOpacity="0.5" /> | ||
| <stop offset="100%" stopColor="#3b82f6" stopOpacity="0.5" /> | ||
| </linearGradient> | ||
| </defs> |
There was a problem hiding this comment.
Bug: Shared SVG Gradients Corrupt Visuals
SVG gradient IDs (centerGradient, nodeGradient, lineGradient) are hardcoded without uniqueness. If multiple NetworkVisualization components render on the same page, the gradient IDs will conflict, causing visual rendering issues where gradients from one instance affect others.
| <Share2 className="w-4 h-4" /> | ||
| <span>Share</span> | ||
| </button> | ||
| )} |
There was a problem hiding this comment.
|
Tested on PR -> the design looks great overall, just a few small notes to mentioned: First of all, there is a bug opened for Revoke invites that are not displayed as revoked which would be nice to be resolved soon: #252 Additional gugs on Desktop & mobile: Mobile: WhatsApp.Video.2025-11-11.at.12.19.05.mp4
WhatsApp.Video.2025-11-11.at.12.53.44.mp4Also, one question: do we want the diagram to show all invitations as points, or just the ones claimed? -- because for example if I have 10 invitations, but I revoked 5, I think would be better to shown only 5 points as my network diagram, not all invites generated |
- Redesigned hero section with bold headline emphasizing 0.5% earning potential - Added interactive StepGuide component with expandable cards - Created EarningExplanation component with interactive calculator - Added EarningFlow visualization component - Created NetworkVisualization component for invite network display - Enhanced InviteAndEarnCard with social sharing buttons (Twitter, Discord) - Enhanced CollectRewardsCard with celebration animations and better visuals - Added StatsSection component (ready for API integration) - Improved mobile responsiveness across all components - Modern, engaging design targeting crypto-savvy users aged 18-25
- Replaced dark backgrounds with glass morphism using CSS variables - Updated all cards to use bg-[var(--glass-bg)] and border-[var(--glass-border)] - Applied backdrop-blur-[20px] and rounded-[20px] consistently - Added hover effects with shadow and translate-y transitions - Reduced opacity of gradient overlays for subtler glass effect - Matches the liquid glass aesthetic used in feed items
- Update hero section text to 'Earn up to 0.5% of Every Token Purchase' - Update commission rate stat to 'Up to 0.5%' - Update InviteAndEarnCard description to 'Earn up to 0.5%' - Update EarningFlow badge to 'Up to 0.5% Commission' - Update EarningExplanation percentage display to 'Up to 0.5%'
- Remove AuroraBackground import from Invite.tsx - Remove auroraConfig state and customization controls - Remove AuroraBackground component usage from hero section
a25fe6b to
025e54e
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix prepared fixes for all 3 issues found in the latest run.
- ✅ Fixed: Unhandled
navigator.share()promise rejection on cancel- Wrapped
navigator.share()in async try/catch and ignore AbortError while logging other errors.
- Wrapped
- ✅ Fixed: Discord share button copies silently without user feedback
- Discord button now calls copy with the row index so the checkmark feedback appears.
- ✅ Fixed: Redundant
useInvitationshook triggers duplicate API calls- Removed parent
useInvitationscall and derived invite count from the invitations atom to avoid extra fetches.
- Removed parent
Or push these changes by commenting:
@cursor push 684722b5bb
Preview (684722b5bb)
diff --git a/src/features/trending/components/Invitation/InviteAndEarnCard.tsx b/src/features/trending/components/Invitation/InviteAndEarnCard.tsx
--- a/src/features/trending/components/Invitation/InviteAndEarnCard.tsx
+++ b/src/features/trending/components/Invitation/InviteAndEarnCard.tsx
@@ -143,8 +143,9 @@
window.open(`https://twitter.com/intent/tweet?text=${text}`, '_blank');
};
- const shareToDiscord = (link: string) => {
- copyToClipboard(link, -1);
+ const shareToDiscord = (link: string, index: number) => {
+ // Provide visual feedback by using the row index (same as the copy button)
+ copyToClipboard(link, index);
};
const canUseNativeShare = typeof navigator !== 'undefined' && typeof navigator.share === 'function';
@@ -368,7 +369,7 @@
</button>
<button
type="button"
- onClick={() => shareToDiscord(link)}
+ onClick={() => shareToDiscord(link, index)}
className="flex-1 min-w-[7rem] flex items-center justify-center gap-2 px-3 py-2 bg-[#5865F2]/20 hover:bg-[#5865F2]/30 border border-[#5865F2]/30 rounded-lg transition-all duration-300 text-sm text-white"
>
<MessageCircle className="w-4 h-4" />
@@ -377,7 +378,17 @@
{canUseNativeShare && (
<button
type="button"
- onClick={() => navigator.share({ text: link, title: 'Superhero Invite Link' })}
+ onClick={async () => {
+ try {
+ await navigator.share({ text: link, title: 'Superhero Invite Link' });
+ } catch (err: any) {
+ // Swallow user-cancelled share; log other errors
+ if (err?.name !== 'AbortError') {
+ // eslint-disable-next-line no-console
+ console.error('Share failed:', err);
+ }
+ }
+ }}
className="flex-1 min-w-[7rem] flex items-center justify-center gap-2 px-3 py-2 bg-white/10 hover:bg-white/20 border border-white/20 rounded-lg transition-all duration-300 text-sm text-white"
>
<Share2 className="w-4 h-4" />
diff --git a/src/views/Trendminer/Invite.tsx b/src/views/Trendminer/Invite.tsx
--- a/src/views/Trendminer/Invite.tsx
+++ b/src/views/Trendminer/Invite.tsx
@@ -14,13 +14,14 @@
} from '../../features/trending/components/Invitation';
import Shell from '../../components/layout/Shell';
import { useAeSdk } from '../../hooks';
-import { useInvitations } from '../../features/trending/hooks/useInvitations';
import NetworkVisualization from '../../features/trending/components/Invitation/graphics/NetworkVisualization';
import EarningFlow from '../../features/trending/components/Invitation/graphics/EarningFlow';
+import { useAtomValue } from 'jotai';
+import { invitationListAtom } from '../../atoms/invitationAtoms';
export default function Invite() {
const { activeAccount } = useAeSdk();
- const { invitations } = useInvitations();
+ const invitationList = useAtomValue(invitationListAtom);
const [showStepGuide, setShowStepGuide] = useState<boolean>(() => {
try {
if (localStorage.getItem('invite_step_guide_dismissed') === '1') return false;
@@ -39,7 +40,9 @@
setShowStepGuide(false);
};
- const inviteCount = invitations?.length || 0;
+ const inviteCount = activeAccount
+ ? invitationList.filter(({ inviter }) => inviter === activeAccount).length
+ : 0;
return (
<Shell>This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
| {canUseNativeShare && ( | ||
| <button | ||
| type="button" | ||
| onClick={() => navigator.share({ text: link, title: 'Superhero Invite Link' })} |
There was a problem hiding this comment.
Unhandled navigator.share() promise rejection on cancel
Medium Severity
The navigator.share() call returns a Promise that isn't caught. When users dismiss the native share dialog, it rejects with an AbortError, producing an unhandled promise rejection. Compare with copyToClipboard which correctly wraps navigator.clipboard.writeText in a try/catch — the same error handling pattern is missing here.
|
|
||
| const shareToDiscord = (link: string) => { | ||
| copyToClipboard(link, -1); | ||
| }; |
There was a problem hiding this comment.
Discord share button copies silently without user feedback
Medium Severity
shareToDiscord calls copyToClipboard(link, -1) which copies to clipboard but passes index -1. Since the checkmark feedback UI only checks copiedLinkIndex === index for non-negative indices, users see no visual confirmation that anything happened. Unlike shareToTwitter which opens Twitter, this button labeled "Discord" silently copies with no feedback — a confusing inconsistency.
| export default function Invite() { | ||
| const { activeAccount } = useAeSdk(); | ||
| const [showInfo, setShowInfo] = useState<boolean>(() => { | ||
| const { invitations } = useInvitations(); |
There was a problem hiding this comment.
Redundant useInvitations hook triggers duplicate API calls
Low Severity
Invite.tsx now calls useInvitations() solely for invitations.length, but child components InviteAndEarnCard and InvitationList already call the same hook. Since transactionList is local state (not shared via atoms), each instance independently fetches the same transaction data from the middleware API, tripling the network requests on every account change or refresh.



Note
Low Risk
Mostly UI/UX changes and new presentational components; minimal functional impact limited to client-side clipboard/social sharing and localStorage dismissal state.
Overview
Redesigns the
Trendminer/Invitepage into a richer “Invite & Earn” funnel with a new hero section, dismissibleStepGuide,EarningExplanation(with a simple earnings calculator), and visualizations (EarningFlow,NetworkVisualizationdriven by current invite count).Enhances the invite-link generation dialog with per-link copy buttons and sharing actions (Twitter intent, Discord copy, and optional native share), and refreshes
CollectRewardsCardstyling/CTA states with eligibility-driven visuals (e.g., progress/reward emphasis and animated particles). Adds a stubbedStatsSection(currently hidden behindshowStats = false).Written by Cursor Bugbot for commit 025e54e. This will update automatically on new commits. Configure here.