diff --git a/src/app/dashboard/researcher/rankings/components/ResearcherCard.tsx b/src/app/dashboard/researcher/rankings/components/ResearcherCard.tsx new file mode 100644 index 0000000..bbee456 --- /dev/null +++ b/src/app/dashboard/researcher/rankings/components/ResearcherCard.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Researcher } from '@/types/report'; + +interface ResearcherCardProps { + researcher: Researcher; + userRank?: number; // For highlighting user's rank +} + +const ResearcherCard: React.FC = ({ researcher, userRank }) => { + const formatAddress = (address: string) => { + return `${address.slice(0, 6)}...${address.slice(-4)}`; + }; + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 0, + }).format(amount); + }; + + const isUserRank = userRank && researcher.rank === userRank; + + return ( + + + {researcher.rank} + + + {researcher.username} + + {formatAddress(researcher.address)} + + {researcher.reports} + {researcher.reputation} + + {formatCurrency(researcher.earned)} + + + ); +}; + +export default ResearcherCard; \ No newline at end of file diff --git a/src/app/dashboard/researcher/rankings/components/ResearchersTable.tsx b/src/app/dashboard/researcher/rankings/components/ResearchersTable.tsx new file mode 100644 index 0000000..0d48ac0 --- /dev/null +++ b/src/app/dashboard/researcher/rankings/components/ResearchersTable.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { Researcher } from '@/types/report'; +import ResearcherCard from './ResearcherCard'; + +interface ResearchersTableProps { + researchers: Researcher[]; + userRank?: number; +} + +const ResearchersTable: React.FC = ({ researchers, userRank }) => { + return ( +
+ + + + + + + + + + + + + {researchers.map((researcher) => ( + + ))} + +
+ Rank + + User name + + Address + + Reports + + Reputation + + Earned +
+
+ ); +}; + +export default ResearchersTable; diff --git a/src/app/dashboard/researcher/rankings/components/TabNavigation.tsx b/src/app/dashboard/researcher/rankings/components/TabNavigation.tsx new file mode 100644 index 0000000..172f5b4 --- /dev/null +++ b/src/app/dashboard/researcher/rankings/components/TabNavigation.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +interface Tab { + id: string; + label: string; +} + +interface TabNavigationProps { + tabs: Tab[]; + activeTab: string; + onTabChange: (tabId: string) => void; +} + +const TabNavigation: React.FC = ({ + tabs, + activeTab, + onTabChange, +}) => { + return ( +
+ {tabs.map((tab) => ( + + ))} +
+ ); +}; + +export default TabNavigation; \ No newline at end of file diff --git a/src/app/dashboard/researcher/rankings/components/ValidatorCard.tsx b/src/app/dashboard/researcher/rankings/components/ValidatorCard.tsx new file mode 100644 index 0000000..2e9d0c8 --- /dev/null +++ b/src/app/dashboard/researcher/rankings/components/ValidatorCard.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Validator } from '@/types/report'; + +interface ValidatorCardProps { + validator: Validator; +} + +const ValidatorCard: React.FC = ({ validator }) => { + const formatAddress = (address: string) => { + return `${address.slice(0, 6)}...${address.slice(-4)}`; + }; + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 0, + }).format(amount); + }; + + return ( + + {validator.rank} + {validator.username} + + {formatAddress(validator.address)} + + {validator.audits} + {validator.reputation} + + {formatCurrency(validator.earned)} + + + ); +}; + +export default ValidatorCard; \ No newline at end of file diff --git a/src/app/dashboard/researcher/rankings/components/ValidatorsRanking.tsx b/src/app/dashboard/researcher/rankings/components/ValidatorsRanking.tsx new file mode 100644 index 0000000..8adf95b --- /dev/null +++ b/src/app/dashboard/researcher/rankings/components/ValidatorsRanking.tsx @@ -0,0 +1,197 @@ + "use client" +import React, { useState } from 'react'; +import TabNavigation from './TabNavigation'; +import ValidatorsTable from './ValidatorsTable'; +import ResearchersTable from './ResearchersTable'; +import { Validator, Researcher } from '@/types/report'; + +// Sample data for validators +const mockValidators: Validator[] = [ + { + rank: 1, + username: 'Ebube', + address: '0x4A7d5cB676...', + audits: 8, + reputation: 90, + earned: 15000, + }, + { + rank: 2, + username: 'Chika', + address: '0x5B8f6cF45d...', + audits: 12, + reputation: 85, + earned: 12500, + }, + { + rank: 3, + username: 'Uche', + address: '0x7C8e7fCeB3...', + audits: 15, + reputation: 92, + earned: 18000, + }, + { + rank: 4, + username: 'Adaobi', + address: '0x8D9b5cC8A...', + audits: 10, + reputation: 88, + earned: 14200, + }, + { + rank: 5, + username: 'Ifij', + address: '0x9E2gbcC98...', + audits: 20, + reputation: 91, + earned: 19500, + }, + { + rank: 6, + username: 'Nnamdi', + address: '0xAf4dD5B3c...', + audits: 6, + reputation: 80, + earned: 11000, + }, + { + rank: 7, + username: 'Obinna', + address: '0xB3eC9AHD7f...', + audits: 18, + reputation: 87, + earned: 16750, + }, + { + rank: 8, + username: 'Kelechi', + address: '0xC5eB6F3E2...', + audits: 14, + reputation: 89, + earned: 17000, + }, +]; + +// Sample data for researchers +const mockResearchers: Researcher[] = [ + { + rank: 1, + username: 'Ebube', + address: '0x4A7d5cB676...', + reports: 8, + reputation: 90, + earned: 15000, + }, + { + rank: 2, + username: 'Chika', + address: '0x5B8f6cF45d...', + reports: 12, + reputation: 85, + earned: 12500, + }, + { + rank: 3, + username: 'Uche', + address: '0x7C8e7fCeB3...', + reports: 15, + reputation: 92, + earned: 18000, + }, + { + rank: 4, + username: 'Adaobi', + address: '0x8D9b5cC8A...', + reports: 10, + reputation: 88, + earned: 14200, + }, + { + rank: 5, + username: 'Ifij', + address: '0x9E2gbcC98...', + reports: 20, + reputation: 91, + earned: 19500, + }, + { + rank: 6, + username: 'Nnamdi', + address: '0xAf4dD5B3c...', + reports: 6, + reputation: 80, + earned: 11000, + }, + { + rank: 7, + username: 'Obinna', + address: '0xB3eC9AHD7f...', + reports: 18, + reputation: 87, + earned: 16750, + }, + { + rank: 8, + username: 'Kelechi', + address: '0xC5eB6F3E2...', + reports: 14, + reputation: 89, + earned: 17000, + }, +]; + +const tabs = [ + { id: 'researchers', label: 'Researchers Ranking' }, + { id: 'validators', label: 'Validators Ranking' }, +]; + +interface ValidatorsRankingProps { + userRank?: number; // Optional prop to highlight user's position +} + +const ValidatorsRanking: React.FC = ({ userRank = 1 }) => { + const [activeTab, setActiveTab] = useState('researchers'); // Default to researchers tab + + return ( +
+
+ + + {activeTab === 'researchers' && ( +
+
+

+ Researchers Ranking +

+
+ Your Rank | + {userRank} +
+
+
+ +
+
+ )} + + {activeTab === 'validators' && ( +
+

+ Validators Ranking +

+
+ +
+
+ )} +
+
+ ); +}; + +export default ValidatorsRanking; \ No newline at end of file diff --git a/src/app/dashboard/researcher/rankings/components/ValidatorsTable.tsx b/src/app/dashboard/researcher/rankings/components/ValidatorsTable.tsx new file mode 100644 index 0000000..bb95245 --- /dev/null +++ b/src/app/dashboard/researcher/rankings/components/ValidatorsTable.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { Validator } from '@/types/report'; +import ValidatorCard from './ValidatorCard'; + +interface ValidatorsTableProps { + validators: Validator[]; +} + +const ValidatorsTable: React.FC = ({ validators }) => { + return ( +
+ + + + + + + + + + + + + {validators.map((validator) => ( + + ))} + +
+ Rank + + User name + + Address + + Audits + + Reputation + + Earned +
+
+ ); +}; + +export default ValidatorsTable; \ No newline at end of file diff --git a/src/app/dashboard/researcher/rankings/page.tsx b/src/app/dashboard/researcher/rankings/page.tsx index 4583406..a4a748c 100644 --- a/src/app/dashboard/researcher/rankings/page.tsx +++ b/src/app/dashboard/researcher/rankings/page.tsx @@ -1,7 +1,9 @@ import React from "react"; +import ValidatorsRanking from "../../researcher/rankings/components/ValidatorsRanking"; function page() { - return
page
; + return ; } export default page; + diff --git a/src/hooks/useValidators.ts b/src/hooks/useValidators.ts new file mode 100644 index 0000000..f0b8faa --- /dev/null +++ b/src/hooks/useValidators.ts @@ -0,0 +1,37 @@ +import { useState, useEffect } from 'react'; +import { Validator } from '@/types/report'; + +export const useValidators = () => { + const [validators, setValidators] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const fetchValidators = async () => { + setLoading(true); + setError(null); + + try { + const response = await fetch('/api/validators'); + if (!response.ok) { + throw new Error('Failed to fetch validators'); + } + const data = await response.json(); + setValidators(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchValidators(); + }, []); + + return { + validators, + loading, + error, + refetch: fetchValidators, + }; +}; \ No newline at end of file diff --git a/src/types/report.ts b/src/types/report.ts index 04fdfa6..4453aea 100644 --- a/src/types/report.ts +++ b/src/types/report.ts @@ -51,3 +51,20 @@ export interface GetReportProps { showSkeleton?: boolean; compact?: boolean; } +export interface Validator { + rank: number; + username: string; + address: string; + audits: number; + reputation: number; + earned: number; +} + +export interface Researcher { + rank: number; + username: string; + address: string; + reports: number; + reputation: number; + earned: number; +}