Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f79659e
remove unused imports and fix setState in effect
Mosas2000 Apr 30, 2026
7137b44
fix impure function call in render
Mosas2000 Apr 30, 2026
a0aa874
fix conditional React Hook calls
Mosas2000 Apr 30, 2026
ff5bcd6
fix setState in effect for sort dropdown
Mosas2000 Apr 30, 2026
8222579
fix duplicate parameter in VoteBar function
Mosas2000 Apr 30, 2026
0a47715
fix parsing error in VotingHistory
Mosas2000 Apr 30, 2026
3dba3c5
fix prefer-const error in test
Mosas2000 Apr 30, 2026
d18f849
remove unused useState import
Mosas2000 Apr 30, 2026
266b1dc
remove unused icon imports
Mosas2000 Apr 30, 2026
8b2e634
disable next link rule for SPA page
Mosas2000 Apr 30, 2026
582fcc4
replace any with unknown in useGlobalLoading
Mosas2000 Apr 30, 2026
dc4b32e
replace any with proper event type
Mosas2000 Apr 30, 2026
d4077a3
add MockTransaction type to replace any
Mosas2000 Apr 30, 2026
06dc19a
replace any with union type in parseClarity
Mosas2000 Apr 30, 2026
5df3dc4
replace any with never in test assertion
Mosas2000 Apr 30, 2026
d240624
change error type to unknown
Mosas2000 Apr 30, 2026
b34f4f3
use function initializer for useState with Date.now
Mosas2000 Apr 30, 2026
eb49b4f
initialize activeIndex with correct value
Mosas2000 Apr 30, 2026
1c64d57
reorder declarations and add setActiveIndex to deps
Mosas2000 Apr 30, 2026
a0760e3
add eslint disable for false positive in test
Mosas2000 Apr 30, 2026
56659f6
use block comment to disable eslint rule
Mosas2000 Apr 30, 2026
428d9ec
add eslint disable for valid setState in effect
Mosas2000 Apr 30, 2026
5d393bc
remove unused shouldNotifyGovernance import
Mosas2000 Apr 30, 2026
d57245f
remove unused chart imports
Mosas2000 Apr 30, 2026
7a41d5f
remove unused motion and icon imports
Mosas2000 Apr 30, 2026
f8706f5
restore YAxis import that is still used
Mosas2000 Apr 30, 2026
fd73a10
restore YAxis import in ProposerActivityChart
Mosas2000 Apr 30, 2026
d57b505
remove unused timeline from TreasuryBalanceChart
Mosas2000 Apr 30, 2026
197250b
remove unused timeline from PerformanceMetricsPanel
Mosas2000 Apr 30, 2026
6142e2c
remove unused error variables
Mosas2000 Apr 30, 2026
0d504bc
remove unused totalCreated variable
Mosas2000 Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/components/EventAnalyticsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import React, { useState } from 'react';
import React from 'react';
import { useContractEvents } from '../src/hooks/useContractEvents';
import { getEventStats, groupEventsByCategory } from '../src/lib/event-utilities';
import { GOVERNANCE_CONFIG } from '../src/lib/governance-config';
Expand Down
46 changes: 3 additions & 43 deletions frontend/components/GovernanceNotificationManager.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
'use client';

import React, { useState, useCallback, useEffect } from 'react';
import React, { useState, useCallback } from 'react';
import { useNotifications } from '../src/hooks/useNotifications';
import { getGovernanceNotificationPreferences } from '../src/lib/governance-notification-preferences';
import {
createGovernanceNotification,
shouldNotifyGovernance,
deduplicateGovernanceNotifications,
} from '../src/lib/governance-notifications';
import { NotificationContainer } from './NotificationDisplay';
import { NotificationPreferencesModal } from './NotificationPreferencesModal';
import { Bell, Settings } from 'lucide-react';
Expand All @@ -19,51 +13,18 @@ interface GovernanceNotificationManagerProps {

export const GovernanceNotificationManager: React.FC<
GovernanceNotificationManagerProps
> = ({ contractPrincipal, pollInterval = 30000 }) => {
> = () => {
const {
notifications,
addNotification,
dismissNotification,
} = useNotifications();
const [preferencesOpen, setPreferencesOpen] = useState(false);
const [preferences, setPreferences] = useState(
getGovernanceNotificationPreferences()
);
const [unreadCount, setUnreadCount] = useState(0);

useEffect(() => {
const count = notifications.filter(n => !n.read).length;
setUnreadCount(count);
}, [notifications]);
const unreadCount = notifications.filter(n => !n.read).length;

const handleNavigation = useCallback((url: string) => {
window.location.href = url;
}, []);

const handleSimulateProposalCreated = useCallback(() => {
if (shouldNotifyGovernance('proposalCreated', preferences)) {
const notif = createGovernanceNotification('proposalCreated', {
proposalId: '123',
proposalTitle: 'Sample Governance Proposal',
});
if (notif) {
addNotification(notif);
}
}
}, [preferences, addNotification]);

const handleSimulateProposalExecuted = useCallback(() => {
if (shouldNotifyGovernance('proposalExecuted', preferences)) {
const notif = createGovernanceNotification('proposalExecuted', {
proposalId: '123',
proposalTitle: 'Sample Governance Proposal',
});
if (notif) {
addNotification(notif);
}
}
}, [preferences, addNotification]);

return (
<>
<div className="fixed bottom-4 right-4 z-40 flex items-center gap-2">
Expand Down Expand Up @@ -101,7 +62,6 @@ export const GovernanceNotificationManager: React.FC<
isOpen={preferencesOpen}
onClose={() => {
setPreferencesOpen(false);
setPreferences(getGovernanceNotificationPreferences());
}}
/>
</>
Expand Down
1 change: 0 additions & 1 deletion frontend/components/NotificationCenterDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import React, { useState } from 'react';
import { useNotifications } from '../src/hooks/useNotifications';
import { useGovernanceEventNotifications } from '../src/hooks/useGovernanceEventNotifications';
import NotificationList from './NotificationList';
import { Bell, X } from 'lucide-react';

interface NotificationCenterProps {
Expand Down
12 changes: 10 additions & 2 deletions frontend/components/NotificationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ export const NotificationList: React.FC<NotificationListProps> = ({
onDismiss,
onDismissAll,
}) => {
const [currentTime, setCurrentTime] = React.useState(() => Date.now());

React.useEffect(() => {
const interval = setInterval(() => {
setCurrentTime(Date.now());
}, 60000);
return () => clearInterval(interval);
}, []);

const formatTime = (timestamp: number) => {
const now = Date.now();
const diff = now - timestamp;
const diff = currentTime - timestamp;

if (diff < 60000) {
return 'just now';
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/NotificationPreferencesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
saveGovernanceNotificationPreferences,
} from '../src/lib/governance-notification-preferences';
import { NotificationPreference } from '../src/types/notifications';
import { Bell, Check, X } from 'lucide-react';
import { Bell, Check } from 'lucide-react';

const preferenceLabels: Record<keyof NotificationPreference, string> = {
proposalCreated: 'New Proposals',
Expand Down
34 changes: 18 additions & 16 deletions frontend/components/ProposalList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,24 @@ export default function ProposalList({ userAddress }: ProposalListProps) {

useRefreshOnConfirmation(fetchProposals);

// Pagination effects
const { page, pageSize } = filterParams;
const totalItems = proposals.length;
const totalPages = Math.ceil(totalItems / pageSize);

// Auto-correct out-of-bounds page
useEffect(() => {
if (totalItems > 0 && page > totalPages && totalPages > 0) {
setPage(totalPages);
}
}, [page, totalPages, totalItems, setPage]);

// Scroll to top on page change
useEffect(() => {
const validPage = Math.max(1, Math.min(page, Math.max(1, totalPages)));
window.scrollTo({ top: 0, behavior: 'smooth' });
}, [page, totalPages]);

// Uses centralized formatSTX from utils/formatSTX

const shortenAddress = (address: string) => {
Expand Down Expand Up @@ -478,23 +496,7 @@ export default function ProposalList({ userAddress }: ProposalListProps) {
});

// Pagination logic
const { page, pageSize } = filterParams;
const totalItems = sortedProposals.length;
const totalPages = Math.ceil(totalItems / pageSize);
const validPage = Math.max(1, Math.min(page, Math.max(1, totalPages)));

// Auto-correct out-of-bounds page
useEffect(() => {
if (totalItems > 0 && page > totalPages) {
setPage(totalPages);
}
}, [page, totalPages, totalItems, setPage]);

// Scroll to top on page change
useEffect(() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
}, [validPage]);

const paginatedProposals = paginateProposals(sortedProposals, validPage, pageSize);

return (
Expand Down
14 changes: 4 additions & 10 deletions frontend/components/SortDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,18 @@ const SORT_DESCRIPTIONS: Record<SortOption, string> = {
export default function SortDropdown({ onSortChange, sort }: SortDropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const [internalSort, setInternalSort] = useState<SortOption>('newest');
const [activeIndex, setActiveIndex] = useState(-1);

const dropdownRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLButtonElement>(null);

const selectedSort = sort ?? internalSort;
const initialActiveIndex = SORT_OPTIONS.findIndex(o => o.value === selectedSort);
const [activeIndex, setActiveIndex] = useState(initialActiveIndex >= 0 ? initialActiveIndex : 0);

const close = useCallback(() => {
setIsOpen(false);
setActiveIndex(-1);
}, []);

useEffect(() => {
if (isOpen) {
const index = SORT_OPTIONS.findIndex(o => o.value === selectedSort);
setActiveIndex(index >= 0 ? index : 0);
}
}, [isOpen, selectedSort]);
setActiveIndex(initialActiveIndex >= 0 ? initialActiveIndex : 0);
}, [initialActiveIndex, setActiveIndex]);

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
Expand Down
3 changes: 1 addition & 2 deletions frontend/components/charts/EcosystemBenchmarks.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use client';

import React from 'react';
import { motion } from 'framer-motion';
import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer, Tooltip as RechartsTooltip, Cell } from 'recharts';
import { BarChart3, TrendingUp, Info } from 'lucide-react';
import { BarChart3, Info } from 'lucide-react';

const benchmarkData = [
{ name: 'SprintFund', efficiency: 94, color: '#EA580C' },
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/charts/ProposerActivityChart.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { useGovernanceAnalytics } from '@/hooks/useGovernanceAnalytics';

export default function ProposerActivityChart() {
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/charts/TreasuryBalanceChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContai
import { useGovernanceAnalytics } from '@/hooks/useGovernanceAnalytics';

export default function TreasuryBalanceChart() {
const { proposals, timeline, loading } = useGovernanceAnalytics();
const { proposals, loading } = useGovernanceAnalytics();

if (loading) {
return (
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/common/AsyncErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class AsyncErrorBoundary extends React.Component<
};
}

componentDidCatch(error: Error) {
componentDidCatch() {
if (this.props.onError && this.state.error) {
this.props.onError(this.state.error);
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/common/CopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function CopyButton({ text, label = 'Copy' }: CopyButtonProps) {
setCopied(true);
toast.success('Copied to clipboard!');
setTimeout(() => setCopied(false), 2000);
} catch (err) {
} catch {
toast.error('Failed to copy');
}
};
Expand Down
2 changes: 0 additions & 2 deletions frontend/components/dashboard/AnalyticsKPIPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,9 @@ function calculateAverageFundingTime(timeline: TimelineItem[]): number {

// Since this hook provides counts per day, not proposal IDs,
// we estimate average funding time based on the timeline pattern
let totalCreated = 0;
let totalApproved = 0;

timeline.forEach((item) => {
totalCreated += item.created || 0;
totalApproved += item.approved || 0;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useGovernanceAnalytics } from '@/hooks/useGovernanceAnalytics';
import { Clock, Zap, TrendingUp } from 'lucide-react';

export function PerformanceMetricsPanel() {
const { proposals, proposalStats, timeline } = useGovernanceAnalytics();
const { proposals, proposalStats } = useGovernanceAnalytics();

const calculateApprovalVelocity = () => {
if (!proposals || proposals.length === 0) return 0;
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/__tests__/error-handling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ describe('Error Handling', () => {
expect(onRetry).toHaveBeenCalled();
});

/* eslint-disable react-hooks/set-state-in-effect */
it('applies exponential backoff', async () => {
const timings: number[] = [];
let lastTime = Date.now();
Expand All @@ -133,6 +134,7 @@ describe('Error Handling', () => {
// Verify delays are non-zero (indicating backoff is happening)
expect(timings.every(t => t > 0)).toBe(true);
});
/* eslint-enable react-hooks/set-state-in-effect */
});

describe('API Error', () => {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/NotificationCenter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function makeNotification(overrides: Partial<Notification> = {}): Notification {

describe('NotificationCenter state management', () => {
it('dropdown starts closed', () => {
let isOpen = false;
const isOpen = false;
expect(isOpen).toBe(false);
});

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/UserProposals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function statusBadge(proposal: Proposal): { label: string; className: string } {

/* ── Vote bar ─────────────────────────────────── */

function VoteBar({ votesFor, votesAgainst }: { votesFor: number; votesAgainst: number }, votesAgainst }: { votesFor: number; votesAgainst: number }) {
function VoteBar({ votesFor, votesAgainst }: { votesFor: number; votesAgainst: number }) {
const total = votesFor + votesAgainst;
const forPct = total > 0 ? Math.round((votesFor / total) * 100) : 0;
const againstPct = total > 0 ? 100 - forPct : 0;
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/VotingHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function formatDate(timestamp: number): string {

/* ── Vote direction badge ─────────────────────── */

function VoteBadge({ support }: { support: boolean }: { support: boolean }) {
function VoteBadge({ support }: { support: boolean }) {
return (
<span
className={`inline-flex items-center text-[10px] font-medium uppercase tracking-wider px-2 py-0.5 rounded-full ${
Expand Down Expand Up @@ -142,6 +142,7 @@ function VotingHistoryBase({ votes }: VotingHistoryProps) {

// Reset pagination on filter or search change
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
setVisibleCount(10);
}, [filter, search]);

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/hooks/useGlobalLoading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface GlobalLoadingContextType {
setGlobalLoading: (state: LoadingState) => void;
startLoading: (message?: string) => void;
completeLoading: () => void;
failLoading: (error?: any) => void;
failLoading: (error?: unknown) => void;
}

const GlobalLoadingContext = createContext<GlobalLoadingContextType | undefined>(undefined);
Expand All @@ -30,7 +30,7 @@ export function GlobalLoadingProvider({ children }: { children: ReactNode }) {
setGlobalLoading(prev => updateLoadingState(prev, 'success'));
}, []);

const failLoading = useCallback((error?: any) => {
const failLoading = useCallback((error?: unknown) => {
setGlobalLoading(prev => ({
...updateLoadingState(prev, 'error'),
error: error ? normalizeError(error) : undefined,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/useGovernanceEventNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const useGovernanceEventNotifications = ({
const preferences = useRef(getGovernanceNotificationPreferences());

const handleNewEvent = useCallback(
(event: any) => {
(event: { id: string; category: string; proposalId?: string; description?: string }) => {
if (!enabled || seenEventIds.current.has(event.id)) {
return;
}
Expand Down
Loading
Loading