From 5452ccc830a74e6843354ac69ba831bb4d4b7ec3 Mon Sep 17 00:00:00 2001 From: water <672684719@qq.com> Date: Sat, 1 Aug 2026 23:08:25 +0800 Subject: [PATCH 1/2] test --- test-pr-permission.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 test-pr-permission.txt diff --git a/test-pr-permission.txt b/test-pr-permission.txt new file mode 100644 index 000000000..9daeafb98 --- /dev/null +++ b/test-pr-permission.txt @@ -0,0 +1 @@ +test From fbf8930b8e9e7aeaed78b3f7ecff5af796ac2826 Mon Sep 17 00:00:00 2001 From: waterWang Date: Sun, 2 Aug 2026 18:00:16 +0800 Subject: [PATCH 2/2] feat: add interactive bounty flow diagram component with SVG lifecycle visualization (Closes #851) - Interactive SVG diagram showing the 6-stage bounty lifecycle (post -> claim -> work -> submit -> review -> payment) - Hover tooltips explaining each stage with animated transitions - Responsive design: SVG diagram on desktop, accordion list on mobile - Framer Motion animations for smooth interactions - Integrated into the How It Works page --- .../components/bounty/BountyFlowDiagram.tsx | 348 ++++++++++++++++++ frontend/src/pages/HowItWorksPage.tsx | 13 + 2 files changed, 361 insertions(+) create mode 100644 frontend/src/components/bounty/BountyFlowDiagram.tsx diff --git a/frontend/src/components/bounty/BountyFlowDiagram.tsx b/frontend/src/components/bounty/BountyFlowDiagram.tsx new file mode 100644 index 000000000..bc295e33b --- /dev/null +++ b/frontend/src/components/bounty/BountyFlowDiagram.tsx @@ -0,0 +1,348 @@ +import React, { useState } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; + +/* ── Types ── */ +interface Stage { + id: string; + label: string; + description: string; + icon: string; + color: string; + lightColor: string; + x: number; + y: number; +} + +const STAGES: Stage[] = [ + { + id: 'post', + label: 'Post', + description: 'A bounty sponsor creates a new bounty with a reward, description, and acceptance criteria.', + icon: '📋', + color: '#3B82F6', + lightColor: '#DBEAFE', + x: 0, + y: 0, + }, + { + id: 'claim', + label: 'Claim', + description: 'A contributor claims the bounty, signaling intent to work on it. This may require staking collateral.', + icon: '✋', + color: '#8B5CF6', + lightColor: '#EDE9FE', + x: 120, + y: 0, + }, + { + id: 'work', + label: 'Work', + description: 'The contributor works on the bounty, implementing the required features or fixes in their fork.', + icon: '⚒️', + color: '#F59E0B', + lightColor: '#FEF3C7', + x: 240, + y: 0, + }, + { + id: 'submit', + label: 'Submit', + description: 'The contributor submits their work via a pull request, attaching evidence and a completion summary.', + icon: '📤', + color: '#10B981', + lightColor: '#D1FAE5', + x: 360, + y: 0, + }, + { + id: 'review', + label: 'Review', + description: 'The sponsor reviews the submission. They can approve, request changes, or reject with reasoning.', + icon: '🔍', + color: '#EC4899', + lightColor: '#FCE7F3', + x: 480, + y: 0, + }, + { + id: 'payment', + label: 'Payment', + description: 'Once approved, the bounty reward is released to the contributor. The bounty is marked as completed.', + icon: '💰', + color: '#22C55E', + lightColor: '#DCFCE7', + x: 600, + y: 0, + }, +]; + +/* ── Arrow path between two points ── */ +function arrowPath(x1: number, y1: number, x2: number, y2: number): string { + const midX = (x1 + x2) / 2; + const midY = (y1 + y2) / 2; + const controlOffset = 40; + // For horizontal flow, add a slight curve + return `M${x1},${y1} C${x1 + controlOffset},${y1} ${x2 - controlOffset},${y2} ${x2},${y2}`; +} + +/* ── Arrowhead marker ── */ +const ArrowMarker = () => ( + + + + + +); + +/* ── Stage Node ── */ +interface StageNodeProps { + stage: Stage; + index: number; + total: number; + activeId: string | null; + onHover: (id: string | null) => void; + onClick: (id: string) => void; +} + +const StageNode: React.FC = ({ stage, activeId, onHover, onClick }) => { + const isActive = activeId === stage.id; + + // Calculate connector positions + const nodeWidth = 80; + const nodeHeight = 80; + + return ( + onHover(stage.id)} + onMouseLeave={() => onHover(null)} + onClick={() => onClick(stage.id)} + > + {/* Glow when active */} + {isActive && ( + + + + + )} + + {/* Node background */} + + + {/* Icon */} + + {stage.icon} + + + {/* Label */} + + {stage.label} + + + ); +}; + +/* ── Tooltip ── */ +interface TooltipProps { + stage: Stage | null; +} + +const Tooltip: React.FC = ({ stage }) => { + return ( + + {stage && ( + +
+ {stage.icon} + {stage.label} Stage +
+

{stage.description}

+
+ )} +
+ ); +}; + +/* ── Mobile List View ── */ +const MobileListView: React.FC<{ + stages: Stage[]; + activeId: string | null; + onHover: (id: string | null) => void; + onClick: (id: string) => void; +}> = ({ stages, activeId, onHover, onClick }) => { + return ( +
+ {stages.map((stage, index) => { + const isActive = activeId === stage.id; + return ( + onHover(stage.id)} + onMouseLeave={() => onHover(null)} + onClick={() => onClick(stage.id)} + > + {stage.icon} +
+
{stage.label}
+ {isActive && ( + + {stage.description} + + )} +
+ {index < stages.length - 1 && ( + + + + )} +
+ ); + })} +
+ ); +}; + +/* ── Main Component ── */ +const BountyFlowDiagram: React.FC = () => { + const [activeId, setActiveId] = useState(null); + + const svgWidth = 680; + const svgHeight = 130; + + return ( +
+ {/* Desktop SVG Diagram */} +
+ + + + {/* Connection lines */} + {STAGES.slice(0, -1).map((stage, i) => { + const next = STAGES[i + 1]; + const x1 = stage.x + 80; + const y1 = stage.y + 40; + const x2 = next.x; + const y2 = next.y + 40; + return ( + + ); + })} + + {/* Stage nodes */} + {STAGES.map((stage, index) => ( + + ))} + + + {/* Tooltip */} + s.id === activeId) ?? null} /> +
+ + {/* Mobile List View */} + +
+ ); +}; + +export default BountyFlowDiagram; \ No newline at end of file diff --git a/frontend/src/pages/HowItWorksPage.tsx b/frontend/src/pages/HowItWorksPage.tsx index 9b245eb50..10333f9b9 100644 --- a/frontend/src/pages/HowItWorksPage.tsx +++ b/frontend/src/pages/HowItWorksPage.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { motion } from 'framer-motion'; import { PageLayout } from '../components/layout/PageLayout'; import { FlowTabs } from '../components/how-it-works/FlowTabs'; +import BountyFlowDiagram from '../components/bounty/BountyFlowDiagram'; import { fadeIn } from '../lib/animations'; export function HowItWorksPage() { @@ -12,6 +13,18 @@ export function HowItWorksPage() {

How It Works

Two paths to earning on SolFoundry

+ + {/* Bounty Lifecycle Flow Diagram */} +
+

+ Bounty Lifecycle +

+

+ From posting to payout — understand every stage of the bounty journey +

+ +
+