diff --git a/.env.development b/.env.development
index 1ac1fe42..d335842e 100644
--- a/.env.development
+++ b/.env.development
@@ -15,6 +15,7 @@ NEXT_PUBLIC_API_URL=http://localhost:3001
NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000
#NEXT_PUBLIC_API_URL=http://localhost:3001
#NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000
+NEXT_PUBLIC_GITHUB_CLIENT_ID=Iv23litRxcCGhlBCEykT
NEXT_PUBLIC_APP_API_KEY=AIzaSyAHz1s-UuNhlZ6aKvqwzmzzidzWxBKw9hw
NEXT_PUBLIC_APP_AUTH_DOMAIN=ctfguide-dev.firebaseapp.com
diff --git a/package-lock.json b/package-lock.json
index 5be40a9a..6c767fa6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -40,7 +40,7 @@
"easymde": "^2.18.0",
"enable": "^3.4.0",
"focus-visible": "^5.2.0",
- "framer-motion": "^11.15.0",
+ "framer-motion": "^11.18.2",
"heroicons": "^2.0.13",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.456.0",
@@ -7066,6 +7066,7 @@
"version": "11.18.2",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
"integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==",
+ "license": "MIT",
"dependencies": {
"motion-dom": "^11.18.1",
"motion-utils": "^11.18.1",
diff --git a/package.json b/package.json
index 80527611..76438903 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
"easymde": "^2.18.0",
"enable": "^3.4.0",
"focus-visible": "^5.2.0",
- "framer-motion": "^11.15.0",
+ "framer-motion": "^11.18.2",
"heroicons": "^2.0.13",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.456.0",
diff --git a/src/components/BountyModal.jsx b/src/components/BountyModal.jsx
new file mode 100644
index 00000000..1b221a07
--- /dev/null
+++ b/src/components/BountyModal.jsx
@@ -0,0 +1,199 @@
+import { XMarkIcon, LockClosedIcon, CodeBracketIcon, SparklesIcon } from '@heroicons/react/24/outline';
+import { Fragment } from 'react';
+import { toast, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+import Link from 'next/link';
+
+const rankColors = {
+ Gold: {
+ bg: 'bg-yellow-900/50',
+ border: 'border-yellow-700/50',
+ iconText: 'text-yellow-300',
+ titleText: 'text-yellow-200',
+ bodyText: 'text-yellow-300/80',
+ },
+ Diamond: {
+ bg: 'bg-cyan-900/50',
+ border: 'border-cyan-700/50',
+ iconText: 'text-cyan-300',
+ titleText: 'text-cyan-200',
+ bodyText: 'text-cyan-300/80',
+ },
+};
+
+export function BountyModal({ bounty, onClose }) {
+ if (!bounty) return null;
+
+ const rewardTiers = [
+ { name: 'Critical', value: bounty.rewards.critical, color: 'text-red-400' },
+ { name: 'High', value: bounty.rewards.high, color: 'text-orange-400' },
+ { name: 'Medium', value: bounty.rewards.medium, color: 'text-yellow-400' },
+ { name: 'Low', value: bounty.rewards.low, color: 'text-blue-400' },
+ ];
+
+ const colors = bounty.requiredRank ? rankColors[bounty.requiredRank] || rankColors.Gold : {};
+ const isLocked = !!bounty.requiredRank;
+
+ const handleStartHacking = () => {
+ // This could navigate to a specific URL or trigger an action to create a container
+ // window.open(`https://replit.com`, '_blank');
+ toast.error('No available environments yet.');
+ };
+
+ return (
+
+
e.stopPropagation()}
+ >
+ {/* Left Side: Main Details */}
+
+
+
+
+
+
{bounty.company}
+
{bounty.title}
+
+
+
+
+
+
+
+
+ {bounty.requiredRank && (
+
+
+
+
Rank Required
+
+ This bounty is exclusive to researchers with the {bounty.requiredRank} rank.
+
+
+
+ )}
+
+
{bounty.description}
+
+
+
Submission History
+ {bounty.history && bounty.history.length > 0 ? (
+
+
+
+
+
+
+ User
+ Vulnerability
+ Reward
+ Date
+
+
+
+ {bounty.history.map((finding, index) => (
+
+ {finding.user}
+ {finding.vulnerability}
+ ${finding.reward.toLocaleString()}
+ {new Date(finding.date).toLocaleDateString()}
+
+ ))}
+
+
+
+
+
+ ) : (
+
+
No public submissions yet.
+
Be the first to find a vulnerability!
+
+ )}
+
+
+
+
+ {/* Right Side: Rewards, Scope & Actions */}
+
+
+ {!isLocked ? (
+ <>
+
Attack Environment
+
+
+ Start Hacking
+
+
+
+
+
Make a Submission
+
+ >
+ ) : (
+
+
+
Bounty Locked
+
+ This bounty requires {bounty.requiredRank} rank to access.
+
+
+ Unlock with PRO
+
+
+ )}
+
+
+
+
Rewards
+
+ {rewardTiers.map(tier => (
+
+ {tier.name}
+ ${parseInt(tier.value).toLocaleString()}
+
+ ))}
+
+
+
+
+
Scope & Vulnerabilities
+
+
Targets
+
+ {bounty.targets.map(target => (
+ {target}
+ ))}
+
+
+
+
Vulnerability Types
+
+ {bounty.vuln_types.map(vuln => (
+
+ {vuln}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/SignatureModal.jsx b/src/components/SignatureModal.jsx
new file mode 100644
index 00000000..e5b45fb0
--- /dev/null
+++ b/src/components/SignatureModal.jsx
@@ -0,0 +1,180 @@
+import { useState, useRef, useEffect } from 'react';
+import { XMarkIcon } from '@heroicons/react/24/outline';
+
+const LegalText = () => (
+
+
Terms of Service for Bounty Programs
+
This Agreement ("Agreement") is a binding contract between you ("Host," "you," or "your") and CTFGuide ("Platform," "we," "us," or "our"). By creating a bug bounty program on our platform, you agree to these terms.
+
+
1. Program Scope and Rewards
+
You are responsible for clearly defining the scope of your bounty program, including which assets are in-scope and out-of-scope. You must set reward amounts in good faith and are obligated to pay for all valid, unique, and in-scope vulnerability reports based on the severity levels you have defined.
+
+
2. Responsible Disclosure and Communication
+
You agree to follow the principles of responsible disclosure. This includes responding to researcher submissions in a timely manner (e.g., acknowledging receipt within 2 business days, providing a first-pass analysis within 5 business days), and communicating openly about the validation and remediation process.
+
+
3. Payouts
+
All rewards must be paid out to researchers within 14 days of a report being triaged and validated as a legitimate vulnerability. CTFGuide will facilitate these payments through its integrated payment processors. Disputes over payments will be mediated by CTFGuide, whose decision will be binding.
+
+
4. Data Handling and Confidentiality
+
All submission details are confidential. You agree not to disclose any part of a submission or the researcher's identity to any third party without the express written consent of the researcher. You will handle all data in accordance with our Privacy Policy.
+
+
5. Acknowledgment and Agreement
+
By signing this document, you acknowledge that you have read, understood, and agree to be bound by these terms. You confirm that you have the authority to represent your organization in this legally binding agreement. Failure to comply with these terms may result in the suspension or termination of your bounty program and your account on the CTFGuide platform.
+
+);
+
+
+export function SignatureModal({ isOpen, onClose, onSave }) {
+ const canvasRef = useRef(null);
+ const isDrawing = useRef(false);
+ const [isSigned, setIsSigned] = useState(false);
+
+ useEffect(() => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ const ctx = canvas.getContext('2d');
+
+ // Set canvas dimensions based on container
+ const resizeCanvas = () => {
+ const rect = canvas.parentElement.getBoundingClientRect();
+ canvas.width = rect.width;
+ canvas.height = 192; // h-48
+ ctx.strokeStyle = '#000000';
+ ctx.lineWidth = 2;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ };
+
+ const getEventPosition = (event) => {
+ const rect = canvas.getBoundingClientRect();
+ if (event.touches && event.touches.length > 0) {
+ return {
+ x: event.touches[0].clientX - rect.left,
+ y: event.touches[0].clientY - rect.top
+ };
+ }
+ return {
+ x: event.clientX - rect.left,
+ y: event.clientY - rect.top
+ };
+ };
+
+ const startDrawing = (e) => {
+ e.preventDefault();
+ isDrawing.current = true;
+ const { x, y } = getEventPosition(e);
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ };
+
+ const draw = (e) => {
+ if (!isDrawing.current) return;
+ e.preventDefault();
+ const { x, y } = getEventPosition(e);
+ ctx.lineTo(x, y);
+ ctx.stroke();
+ };
+
+ const stopDrawing = () => {
+ if (isDrawing.current) {
+ isDrawing.current = false;
+ setIsSigned(true);
+ }
+ };
+
+ resizeCanvas();
+ window.addEventListener('resize', resizeCanvas);
+
+ // Mouse events
+ canvas.addEventListener('mousedown', startDrawing);
+ canvas.addEventListener('mousemove', draw);
+ canvas.addEventListener('mouseup', stopDrawing);
+ canvas.addEventListener('mouseleave', stopDrawing);
+
+ // Touch events
+ canvas.addEventListener('touchstart', startDrawing);
+ canvas.addEventListener('touchmove', draw);
+ canvas.addEventListener('touchend', stopDrawing);
+
+ return () => {
+ window.removeEventListener('resize', resizeCanvas);
+ canvas.removeEventListener('mousedown', startDrawing);
+ canvas.removeEventListener('mousemove', draw);
+ canvas.removeEventListener('mouseup', stopDrawing);
+ canvas.removeEventListener('mouseleave', stopDrawing);
+ canvas.removeEventListener('touchstart', startDrawing);
+ canvas.removeEventListener('touchmove', draw);
+ canvas.removeEventListener('touchend', stopDrawing);
+ };
+ }, [isOpen]);
+
+
+ const clearCanvas = () => {
+ const canvas = canvasRef.current;
+ const ctx = canvas.getContext('2d');
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ setIsSigned(false);
+ };
+
+ const saveSignature = () => {
+ if (!isSigned) {
+ alert("Please provide your signature.");
+ return;
+ }
+ const signatureData = canvasRef.current.toDataURL('image/png');
+ onSave(signatureData);
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
Bounty Program Agreement
+
+
+
+
+
+
+
+
+
+
+ Please sign in the box below to accept the terms:
+
+
+
+
+
+ Clear Signature
+
+
+
+
+
+
+ Cancel
+
+
+ Agree & Sign
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/StandardNav.jsx b/src/components/StandardNav.jsx
index 0df2b72c..8ac27c66 100644
--- a/src/components/StandardNav.jsx
+++ b/src/components/StandardNav.jsx
@@ -12,6 +12,7 @@ import {
ArrowRightIcon,
EllipsisVerticalIcon,
ShieldCheckIcon,
+ GiftIcon,
BellIcon,
UserGroupIcon,
} from '@heroicons/react/24/outline';
@@ -399,6 +400,18 @@ export function StandardNav({ guestAllowed, alignCenter = true }) {
+
+
+
+
+
+
Bounties
+
Earn rewards for solving challenges
+
+
({
+ x: direction > 0 ? 300 : -300,
+ opacity: 0,
+ }),
+ center: {
+ zIndex: 1,
+ x: 0,
+ opacity: 1,
+ },
+ exit: (direction) => ({
+ zIndex: 0,
+ x: direction < 0 ? 300 : -300,
+ opacity: 0,
+ }),
+};
+
+
+export function WelcomeModal({ isOpen, onClose }) {
+ const [currentStep, setCurrentStep] = useState(0);
+ const [direction, setDirection] = useState(0);
+
+ const paginate = (newDirection) => {
+ setDirection(newDirection);
+ setCurrentStep(prev => prev + newDirection);
+ };
+
+ if (!isOpen) return null;
+
+ const step = tutorialSteps[currentStep];
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
How Bounties Work
+
+
+
+
+
+
+
+
+
+ {step.title}
+ {step.description}
+
+
+
+
+
+
+
+ {currentStep > 0 && (
+ paginate(-1)}
+ className="text-neutral-300 font-semibold py-2 px-4 rounded-md"
+ >
+ Previous
+
+ )}
+
+
+
+ {tutorialSteps.map((_, i) => (
+
+ ))}
+
+
+
+ {currentStep < tutorialSteps.length - 1 ? (
+ paginate(1)}
+ className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-md transition-colors"
+ >
+ Next
+
+ ) : (
+
+ Start Hacking!
+
+ )}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/layouts/BountyLayout.jsx b/src/components/layouts/BountyLayout.jsx
new file mode 100644
index 00000000..175d3918
--- /dev/null
+++ b/src/components/layouts/BountyLayout.jsx
@@ -0,0 +1,53 @@
+import { StandardNav } from '../StandardNav';
+import { Footer } from '../Footer';
+import Link from 'next/link';
+import { ChevronRightIcon } from '@heroicons/react/20/solid';
+
+const Breadcrumb = ({ bounty }) => {
+ return (
+
+
+
+
+
+ Bounties
+
+
+
+ {bounty && (
+
+
+
+
+ {bounty.title}
+
+
+
+ )}
+
+
+
+
+ Submit Report
+
+
+
+
+
+ );
+};
+
+export default function BountyLayout({ children, bounty }) {
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/modals/RecordingModal.jsx b/src/components/modals/RecordingModal.jsx
new file mode 100644
index 00000000..12899c22
--- /dev/null
+++ b/src/components/modals/RecordingModal.jsx
@@ -0,0 +1,76 @@
+import { useState } from 'react';
+import { XMarkIcon, EyeIcon, ChevronUpIcon } from '@heroicons/react/24/solid';
+
+const mockRecordings = [
+ { id: 1, name: 'Initial Reconnaissance', duration: '15:32', date: '2023-10-26', videoUrl: './terminalproof.mp4' },
+ { id: 2, name: 'Exploiting XSS', duration: '08:45', date: '2023-10-26', videoUrl: 'https://www.w3schools.com/html/mov_bbb.mp4' },
+ { id: 3, name: 'Privilege Escalation Path', duration: '22:10', date: '2023-10-25', videoUrl: 'https://www.w3schools.com/html/mov_bbb.mp4' },
+ { id: 4, name: 'Data Exfiltration Test', duration: '05:00', date: '2023-10-24', videoUrl: 'https://www.w3schools.com/html/mov_bbb.mp4' },
+];
+
+export default function RecordingModal({ isOpen, onClose }) {
+ const [previewingId, setPreviewingId] = useState(null);
+
+ if (!isOpen) return null;
+
+ const togglePreview = (id) => {
+ setPreviewingId(previewingId === id ? null : id);
+ };
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
Attach Recording Session
+
+
+
+
+
+
+ {mockRecordings.map(rec => (
+
+
+
+
{rec.name}
+
+ Duration: {rec.duration} | Recorded: {new Date(rec.date).toLocaleDateString()}
+
+
+
togglePreview(rec.id)}
+ className="flex items-center gap-2 bg-neutral-700 hover:bg-neutral-600 text-white font-bold py-2 px-4 text-sm rounded-md transition-colors"
+ >
+ {previewingId === rec.id ? : }
+ {previewingId === rec.id ? 'Close' : 'Preview'}
+
+
+ {previewingId === rec.id && (
+
+
+
+ Your browser does not support the video tag.
+
+
+
+ Attach this recording
+
+
+
+ )}
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/data/bounties.js b/src/data/bounties.js
new file mode 100644
index 00000000..783fe904
--- /dev/null
+++ b/src/data/bounties.js
@@ -0,0 +1,93 @@
+export const rankColors = {
+ Gold: {
+ border: 'border-yellow-500',
+ text: 'text-yellow-500',
+ bg: 'bg-yellow-900/50',
+ ring: 'ring-yellow-500/20',
+ hoverBorder: 'hover:border-yellow-400',
+ focusRing: 'focus:ring-yellow-500',
+ },
+ Diamond: {
+ border: 'border-cyan-500',
+ text: 'text-cyan-500',
+ bg: 'bg-cyan-900/50',
+ ring: 'ring-cyan-500/20',
+ hoverBorder: 'hover:border-cyan-400',
+ focusRing: 'focus:ring-cyan-500',
+ },
+};
+
+export const mockBounties = [
+ {
+ id: 5,
+ company: 'Synack',
+ title: 'Synack - Crowdsourced Security Testing Platform',
+ category: 'Security',
+ logo: 'https://www.synack.com/wp-content/uploads/2023/04/synack-red-team-svg-2.svg',
+ description: 'Exclusive access to our private bug bounty programs. Only for top-ranked researchers.',
+ targets: ['platform.synack.com'],
+ rewards: { critical: '25000', high: '10000', medium: '5000', low: '1000' },
+ vuln_types: ['Web Application', 'Mobile', 'Network', 'Host'],
+ history: [],
+ requiredRank: 'Diamond'
+ },
+ {
+ id: 4,
+ company: 'Cluely',
+ title: 'Cluely – Cheat on everything',
+ category: 'Desktop AI Assistant',
+ logo: 'https://media.licdn.com/dms/image/v2/D560BAQFPDCGXilJm2g/company-logo_200_200/B56ZblmJB2H4AI-/0/1747608705117/cluely_logo?e=2147483647&v=beta&t=JNygewi41TBDr_LIyWVo_smWOhKD2q3GXkos83PZ6ck',
+ description: 'Cluely is an AI‑powered desktop assistant that invisibly monitors your screen and audio during meetings, sales calls, coding interviews, or solo workflows—then feeds you context‑aware prompts and answers in real time.',
+ targets: ['cluely.com', 'app.cluely.ai'],
+ rewards: { critical: '5000', high: '2500', medium: '1000', low: '250' },
+ vuln_types: ['Screen/audio privacy', 'Data leakage', 'Access control', 'API security', 'Overlay spoofing'],
+
+
+ history: [
+ { user: 'hacker_one', vulnerability: 'Critical RCE', reward: 10000, date: '2023-05-15' },
+ { user: 'another_user', vulnerability: 'Medium XSS', reward: 2000, date: '2023-04-20' }
+ ]
+ },
+ {
+ id: 1,
+ company: 'Replit',
+ title: 'Replit - The collaborative browser based IDE',
+ category: 'Web Application',
+ logo: 'https://play-lh.googleusercontent.com/baV9RL2D0iV8JkTtCzSxeLf6XxCJMWQYbyXMqyQfc0OQGtjkCyUenUbLb5tefYfMxfU',
+ description: 'Help secure our platform that provides a collaborative, browser-based IDE. We are looking for a wide range of web vulnerabilities.',
+ targets: ['replit.com', 'api.replit.com'],
+ rewards: { critical: '10000', high: '5000', medium: '2000', low: '500' },
+ vuln_types: ['RCE', 'SSRF', 'Container Escape', 'XSS'],
+ history: [
+ { user: 'hacker_one', vulnerability: 'Critical RCE', reward: 10000, date: '2023-05-15' },
+ { user: 'another_user', vulnerability: 'Medium XSS', reward: 2000, date: '2023-04-20' }
+ ]
+ },
+ {
+ id: 2,
+ company: 'OpenSea',
+ title: 'OpenSea - The largest NFT marketplace',
+ category: 'Web3',
+ logo: 'https://storage.googleapis.com/opensea-static/Logomark/Logomark-Blue.svg',
+ description: 'Secure the world\'s largest NFT marketplace. We are offering rewards for vulnerabilities related to smart contracts and our web platform.',
+ targets: ['opensea.io', 'api.opensea.io', 'Smart Contracts'],
+ rewards: { critical: '100000', high: '25000', medium: '10000', low: '2500' },
+ vuln_types: ['Smart Contract Exploit', 'Re-entrancy', 'Transaction Gas Optimization', 'Web3 Injection'],
+ history: [
+ { user: 'sec_researcher', vulnerability: 'Re-entrancy on listing', reward: 25000, date: '2023-06-01' }
+ ],
+ requiredRank: 'Gold'
+ },
+ {
+ id: 3,
+ company: 'Deel',
+ title: 'Deel - The all-in-one HR platform for global teams',
+ category: 'Financial',
+ logo: 'https://cdn.prod.website-files.com/5f15081919fdf673994ab5fd/66021319743010423fabbff9_Deel-Logo.svg',
+ description: 'Help us secure our HR platform for global teams. We are looking for vulnerabilities in our payment systems, and data handling.',
+ targets: ['deel.com', 'api.deel.com'],
+ rewards: { critical: '15000', high: '7500', medium: '3000', low: '1000' },
+ vuln_types: ['Business Logic Errors', 'Payment Flaws', 'Information Disclosure', 'Access Control'],
+ history: []
+ },
+];
\ No newline at end of file
diff --git a/src/pages/api/bounties.js b/src/pages/api/bounties.js
new file mode 100644
index 00000000..0519ecba
--- /dev/null
+++ b/src/pages/api/bounties.js
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/pages/bounties.jsx b/src/pages/bounties.jsx
new file mode 100644
index 00000000..64711eb3
--- /dev/null
+++ b/src/pages/bounties.jsx
@@ -0,0 +1,309 @@
+import Head from 'next/head';
+import Link from 'next/link';
+import { StandardNav } from '@/components/StandardNav';
+import { Footer } from '@/components/Footer';
+import { useState, useEffect } from 'react';
+import { GiftIcon, UsersIcon, CodeBracketSquareIcon, ShieldCheckIcon, LockClosedIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline';
+import { BountyModal } from '@/components/BountyModal';
+import { WelcomeModal } from '@/components/WelcomeModal';
+import { rankColors, mockBounties } from '@/data/bounties';
+
+function ForStartups() {
+ return (
+
+
Launch your Bug Bounty
+
+ Secure your startup with our global community of security researchers. We offer managed bounties, seamless deployment, and actionable reports.
+
+
+
+ List Your Bounty
+
+
+
+ );
+}
+
+function TopBounties({ bounties, onBountyClick }) {
+ const sortedBounties = [...bounties].sort((a, b) => {
+ const maxRewardA = Math.max(...Object.values(a.rewards).map(r => parseInt(r.replace(',', ''))));
+ const maxRewardB = Math.max(...Object.values(b.rewards).map(r => parseInt(r.replace(',', ''))));
+ return maxRewardB - maxRewardA;
+ }).slice(0, 3);
+
+ return (
+
+ );
+}
+
+function RecentPayouts({ bounties, onBountyClick }) {
+ const allPayouts = bounties.flatMap(bounty =>
+ bounty.history.map(payout => ({ ...payout, bounty }))
+ ).sort((a, b) => new Date(b.date) - new Date(a.date)).slice(0, 5);
+
+ return (
+
+
Recent Payouts
+
+ {allPayouts.map((payout, index) => (
+
+
+ {payout.user} was rewarded ${payout.reward.toLocaleString()} for a {payout.vulnerability} on onBountyClick(payout.bounty)} className="font-bold text-yellow-400 hover:underline">{payout.bounty.company} .
+
+ {new Date(payout.date).toLocaleDateString()}
+
+ ))}
+
+
+ );
+}
+
+export default function Bounties() {
+ const [bounties, setBounties] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [selectedBounty, setSelectedBounty] = useState(null);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [selectedCategory, setSelectedCategory] = useState('All');
+ const [showLockedOnly, setShowLockedOnly] = useState(false);
+ const [showWelcomeModal, setShowWelcomeModal] = useState(false);
+
+ useEffect(() => {
+ // Check if it's the user's first visit
+ const hasVisited = localStorage.getItem('hasViewedBountiesPage');
+ if (!hasVisited) {
+ setShowWelcomeModal(true);
+ }
+
+ const fetchBounties = async () => {
+ try {
+ const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/bounties`);
+ const data = await response.json();
+ setBounties(data);
+ } catch (error) {
+ console.error("Failed to fetch bounties:", error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchBounties();
+ }, []);
+
+ const handleCloseWelcomeModal = () => {
+ setShowWelcomeModal(false);
+ localStorage.setItem('hasViewedBountiesPage', 'true');
+ };
+
+ const getRewardRange = (rewards) => {
+ const rewardValues = Object.values(rewards).map(r => parseInt(r.replace(',', '')));
+ const min = Math.min(...rewardValues);
+ const max = Math.max(...rewardValues);
+ return `$${min.toLocaleString()} - $${max.toLocaleString()}`;
+ };
+
+ const categories = ['All', ...new Set(mockBounties.map(b => b.category))];
+
+ const filteredBounties = bounties
+ .filter(bounty => {
+ if (showLockedOnly && !bounty.requiredRank) {
+ return false;
+ }
+ return true;
+ })
+ .filter(bounty => {
+ const query = searchQuery.toLowerCase();
+ if (!query) return true;
+ return (
+ bounty.title.toLowerCase().includes(query) ||
+ bounty.company.toLowerCase().includes(query) ||
+ bounty.description.toLowerCase().includes(query)
+ );
+ })
+ .filter(bounty => {
+ return selectedCategory === 'All' || bounty.category === selectedCategory;
+ });
+
+ return (
+ <>
+
+ Bug Bounties | CTFGuide
+
+
+
+
+
+
+
+
+
+
+ Bounties
+
+
+ Help secure innovative startups and get rewarded for finding vulnerabilities.
+
+
+
+
+
+
+
+
+
+
+
setSearchQuery(e.target.value)}
+ className="w-full bg-neutral-800 border border-neutral-700 text-white placeholder-neutral-400 rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2 pl-10"
+ />
+
+
+
setSelectedCategory(e.target.value)}
+ className="bg-neutral-800 border border-neutral-700 text-white rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2"
+ >
+ {categories.map(cat => {cat} )}
+
+
+
+
+ setShowLockedOnly(e.target.checked)}
+ className="h-4 w-4 rounded border-neutral-600 bg-neutral-800 text-blue-600 focus:ring-blue-500"
+ />
+ Show only locked bounties
+
+
+
+
+
+
+ {loading ? (
+
+ ) : (
+
+ {filteredBounties.map((bounty) => {
+ const hasRank = !!bounty.requiredRank;
+ const colors = hasRank ? rankColors[bounty.requiredRank] || rankColors.Gold : {};
+ const isLocked = hasRank; // assume user is not high enough rank
+
+ return (
+
+
!isLocked && setSelectedBounty(bounty)}
+ disabled={isLocked}
+ className={`w-full text-left block overflow-hidden bg-neutral-800 shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-neutral-900 transition-all border-l-4 ${
+ hasRank
+ ? `${colors.border} ${colors.hoverBorder} ${colors.focusRing}`
+ : 'border-transparent hover:border-blue-500 focus:ring-blue-500'
+ } ${isLocked ? 'cursor-not-allowed' : 'hover:bg-neutral-800/80'}`}
+ >
+
+
+
+
+
+
{bounty.company}
+
{bounty.title}
+
+
+
+
+ {bounty.category}
+
+ {hasRank && (
+
+
+ Requires {bounty.requiredRank}
+
+ )}
+
+
+
{bounty.description}
+
+
+
+ {getRewardRange(bounty.rewards)}
+
+
+ {bounty.vuln_types.slice(0, 3).map(vuln => (
+
+ {vuln}
+
+ ))}
+ {bounty.vuln_types.length > 3 && (
+
+ +{bounty.vuln_types.length - 3} more
+
+ )}
+
+
+
+
+ {isLocked && (
+
+
+
+
+
Locked
+
Requires {bounty.requiredRank} Rank
+
+
+
+
+
+ { /* Handle PRO unlock */ }}
+ className="bg-yellow-600 hover:bg-yellow-700 text-white font-semibold py-2 px-3 rounded-lg transition-colors text-sm"
+ >
+ Unlock with PRO
+
+
+
+ )}
+
+ );
+ })}
+
+ )}
+
+
+
+
+ setSelectedBounty(null)} />
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/bounties/[id]/submit.js b/src/pages/bounties/[id]/submit.js
new file mode 100644
index 00000000..444b159f
--- /dev/null
+++ b/src/pages/bounties/[id]/submit.js
@@ -0,0 +1,259 @@
+import { useState, useEffect } from 'react';
+import { useRouter } from 'next/router';
+import { toast, ToastContainer } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+import { CodeBracketIcon, PaperClipIcon, ArrowUpOnSquareIcon, SparklesIcon, VideoCameraIcon } from '@heroicons/react/24/solid';
+import BountyLayout from '../../../components/layouts/BountyLayout';
+import RecordingModal from '../../../components/modals/RecordingModal';
+
+// This would be your editor component
+const MockEditor = ({ value, onChange }) => (
+