From a9d1e30b5a5c3fee878dfa757678fa717eb38759 Mon Sep 17 00:00:00 2001 From: Devadakene Date: Fri, 21 Aug 2026 15:41:01 +0100 Subject: [PATCH 1/2] feat: implement CSS animation library and apply utilities --- frontend/src/components/dashboard/KpiCard.tsx | 2 +- frontend/src/components/landing/Hero.tsx | 69 +++++++----------- frontend/src/styles/animations.css | 70 +++++++++++++++++++ frontend/src/styles/global.css | 3 + frontend/src/styles/micro-interactions.css | 62 ++++++++++++++++ 5 files changed, 162 insertions(+), 44 deletions(-) create mode 100644 frontend/src/styles/animations.css create mode 100644 frontend/src/styles/micro-interactions.css diff --git a/frontend/src/components/dashboard/KpiCard.tsx b/frontend/src/components/dashboard/KpiCard.tsx index 53ec57e..be0ffdd 100644 --- a/frontend/src/components/dashboard/KpiCard.tsx +++ b/frontend/src/components/dashboard/KpiCard.tsx @@ -13,7 +13,7 @@ interface KpiCardProps { export const KpiCard: React.FC = ({ title, value, sparklineData, loading }) => { const data = sparklineData.map((v, i) => ({ x: i, y: v })); return ( -
+
{loading ? (
) : ( diff --git a/frontend/src/components/landing/Hero.tsx b/frontend/src/components/landing/Hero.tsx index ba0de1f..5bc2287 100644 --- a/frontend/src/components/landing/Hero.tsx +++ b/frontend/src/components/landing/Hero.tsx @@ -1,78 +1,61 @@ import React from 'react' import { useNavigate } from 'react-router-dom' -import { motion } from 'framer-motion' import { Sparkles, ArrowRight } from 'lucide-react' -const containerVariants = { - hidden: { opacity: 0 }, - visible: { - opacity: 1, - transition: { staggerChildren: 0.12, delayChildren: 0.2 }, - }, -} as const - -const itemVariants = { - hidden: { opacity: 0, y: 20 }, - visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: 'easeOut' as const } }, -} as const - const Hero: React.FC = () => { const navigate = useNavigate() return ( - {/* Centered Logo Mark */} -
a
- +
{/* Status Pill */} - -
+
Live on Stellar Testnet - +
{/* Headline */} - AI agents that
hire & pay each other -
+ {/* Subtext */} - Submit one task. Specialized agents collaborate, execute, and pay each other on-chain — fully autonomous. - +

{/* CTAs */} - - - +
+ ) } diff --git a/frontend/src/styles/animations.css b/frontend/src/styles/animations.css new file mode 100644 index 0000000..ac315e7 --- /dev/null +++ b/frontend/src/styles/animations.css @@ -0,0 +1,70 @@ +.fade-in { + animation: fadeIn 0.3s ease forwards; +} + +.slide-up { + animation: slideUp 0.3s ease forwards; +} + +.scale-in { + animation: scaleIn 0.3s ease forwards; +} + +.pulse { + animation: pulse 2s infinite ease-in-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes pulse { + 0% { + opacity: 1; + } + 50% { + opacity: 0.6; + } + 100% { + opacity: 1; + } +} + +@media (prefers-reduced-motion: reduce) { + .fade-in, + .slide-up, + .scale-in, + .pulse { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + animation-delay: 0.01ms !important; + } +} diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index de1ad35..e016a1b 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -2,6 +2,9 @@ @tailwind components; @tailwind utilities; +@import './animations.css'; +@import './micro-interactions.css'; + :root { --font-sans: 'Outfit', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; --bg-primary: #0A0E14; diff --git a/frontend/src/styles/micro-interactions.css b/frontend/src/styles/micro-interactions.css new file mode 100644 index 0000000..14a7aa7 --- /dev/null +++ b/frontend/src/styles/micro-interactions.css @@ -0,0 +1,62 @@ +.hover-lift { + transition-property: transform, box-shadow; + transition-duration: 300ms; + transition-timing-function: ease; +} + +.hover-lift:hover { + transform: translateY(-4px); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15); +} + +.hover-glow { + transition-property: box-shadow; + transition-duration: 300ms; + transition-timing-function: ease; +} + +.hover-glow:hover { + box-shadow: 0 0 15px var(--accent-cyan); +} + +.hover-scale { + transition-property: transform; + transition-duration: 300ms; + transition-timing-function: ease; +} + +.hover-scale:hover { + transform: scale(1.05); +} + +.focus-ring { + outline: none; +} + +.focus-ring:focus-visible { + outline: 2px solid var(--accent-cyan); + outline-offset: 2px; +} + +.transition-fast { + transition-duration: 150ms; +} + +.transition-normal { + transition-duration: 300ms; +} + +.transition-slow { + transition-duration: 500ms; +} + +@media (prefers-reduced-motion: reduce) { + .hover-lift, + .hover-glow, + .hover-scale, + .transition-fast, + .transition-normal, + .transition-slow { + transition-duration: 0.01ms !important; + } +} From 63d8c5ba6d472eef960ed996b8b8fe4019850914 Mon Sep 17 00:00:00 2001 From: Devadakene Date: Fri, 21 Aug 2026 15:48:48 +0100 Subject: [PATCH 2/2] feat: add agent reputation radar and trend charts --- .../agents/AgentDetailModal.module.css | 7 ++++ .../components/agents/AgentDetailModal.tsx | 21 ++++++++++ .../agents/AgentReputationRadar.module.css | 6 +++ .../agents/AgentReputationRadar.test.tsx | 34 +++++++++++++++ .../agents/AgentReputationRadar.tsx | 31 ++++++++++++++ .../agents/AgentReputationTrend.module.css | 4 ++ .../agents/AgentReputationTrend.tsx | 34 +++++++++++++++ frontend/src/hooks/useAgentReputation.ts | 41 +++++++++++++++++++ frontend/src/services/api.ts | 4 ++ frontend/src/types/agent.ts | 18 ++++++++ 10 files changed, 200 insertions(+) create mode 100644 frontend/src/components/agents/AgentReputationRadar.module.css create mode 100644 frontend/src/components/agents/AgentReputationRadar.test.tsx create mode 100644 frontend/src/components/agents/AgentReputationRadar.tsx create mode 100644 frontend/src/components/agents/AgentReputationTrend.module.css create mode 100644 frontend/src/components/agents/AgentReputationTrend.tsx create mode 100644 frontend/src/hooks/useAgentReputation.ts diff --git a/frontend/src/components/agents/AgentDetailModal.module.css b/frontend/src/components/agents/AgentDetailModal.module.css index 4e0cf9a..b54e872 100644 --- a/frontend/src/components/agents/AgentDetailModal.module.css +++ b/frontend/src/components/agents/AgentDetailModal.module.css @@ -161,3 +161,10 @@ grid-template-columns: 1fr; } } + +.chartsContainer { + display: flex; + flex-direction: column; + gap: 1.5rem; + margin-top: 0.5rem; +} diff --git a/frontend/src/components/agents/AgentDetailModal.tsx b/frontend/src/components/agents/AgentDetailModal.tsx index f659dce..fabc935 100644 --- a/frontend/src/components/agents/AgentDetailModal.tsx +++ b/frontend/src/components/agents/AgentDetailModal.tsx @@ -2,6 +2,9 @@ import { useEffect } from 'react' import { ExternalLink, X } from 'lucide-react' import type { AgentRecord } from '../../types/api' import { ReputationStars } from './ReputationStars' +import { useAgentReputation } from '../../hooks/useAgentReputation' +import { AgentReputationRadar } from './AgentReputationRadar' +import { AgentReputationTrend } from './AgentReputationTrend' import styles from './AgentDetailModal.module.css' const STELLAR_EXPLORER = 'https://stellar.expert/explorer/testnet' @@ -12,6 +15,8 @@ interface AgentDetailModalProps { } export function AgentDetailModal({ agent, onClose }: AgentDetailModalProps) { + const { data: reputationData, loading: reputationLoading } = useAgentReputation(agent?.id || '') + useEffect(() => { if (!agent) return const onKey = (e: KeyboardEvent) => { @@ -120,6 +125,22 @@ export function AgentDetailModal({ agent, onClose }: AgentDetailModalProps) { )}
+ +
+
Reputation Details
+
+ {reputationLoading ? ( +
Loading charts...
+ ) : reputationData ? ( +
+ + +
+ ) : ( +
No detailed reputation data available
+ )} +
+
diff --git a/frontend/src/components/agents/AgentReputationRadar.module.css b/frontend/src/components/agents/AgentReputationRadar.module.css new file mode 100644 index 0000000..cdb7320 --- /dev/null +++ b/frontend/src/components/agents/AgentReputationRadar.module.css @@ -0,0 +1,6 @@ +.container { + width: 100%; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/frontend/src/components/agents/AgentReputationRadar.test.tsx b/frontend/src/components/agents/AgentReputationRadar.test.tsx new file mode 100644 index 0000000..f269682 --- /dev/null +++ b/frontend/src/components/agents/AgentReputationRadar.test.tsx @@ -0,0 +1,34 @@ +import { render } from '@testing-library/react'; +import { AgentReputationRadar } from './AgentReputationRadar'; +import { vi, describe, it, expect } from 'vitest'; + +// Mock recharts because ResponsiveContainer doesn't work well in JSDOM +vi.mock('recharts', async () => { + const OriginalRecharts = await vi.importActual('recharts'); + return { + ...OriginalRecharts, + ResponsiveContainer: ({ children }: any) => ( +
{children}
+ ), + }; +}); + +describe('AgentReputationRadar', () => { + it('renders the radar chart with given dimensions', () => { + const dimensions = { + quality: 90, + speed: 85, + reliability: 95, + cost: 80, + }; + + const { container } = render(); + + // Check if the container is rendered + expect(container.firstChild).toBeInTheDocument(); + + // In a real JSDOM environment with SVG, we could check for specific SVG elements. + // For this test, we ensure it renders without crashing and contains the ResponsiveContainer div. + expect(container.querySelector('.recharts-wrapper')).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/components/agents/AgentReputationRadar.tsx b/frontend/src/components/agents/AgentReputationRadar.tsx new file mode 100644 index 0000000..00cd788 --- /dev/null +++ b/frontend/src/components/agents/AgentReputationRadar.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, ResponsiveContainer, Tooltip } from 'recharts'; +import type { ReputationDimensions } from '../../types/agent'; +import styles from './AgentReputationRadar.module.css'; + +interface AgentReputationRadarProps { + dimensions: ReputationDimensions; +} + +export const AgentReputationRadar: React.FC = ({ dimensions }) => { + const data = [ + { subject: 'Quality', A: dimensions.quality, fullMark: 100 }, + { subject: 'Speed', A: dimensions.speed, fullMark: 100 }, + { subject: 'Reliability', A: dimensions.reliability, fullMark: 100 }, + { subject: 'Cost', A: dimensions.cost, fullMark: 100 }, + ]; + + return ( +
+ + + + + + + + + +
+ ); +}; diff --git a/frontend/src/components/agents/AgentReputationTrend.module.css b/frontend/src/components/agents/AgentReputationTrend.module.css new file mode 100644 index 0000000..97e43ea --- /dev/null +++ b/frontend/src/components/agents/AgentReputationTrend.module.css @@ -0,0 +1,4 @@ +.container { + width: 100%; + margin-top: 1rem; +} diff --git a/frontend/src/components/agents/AgentReputationTrend.tsx b/frontend/src/components/agents/AgentReputationTrend.tsx new file mode 100644 index 0000000..a44af59 --- /dev/null +++ b/frontend/src/components/agents/AgentReputationTrend.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Brush } from 'recharts'; +import type { ReputationHistory } from '../../types/agent'; +import styles from './AgentReputationTrend.module.css'; + +interface AgentReputationTrendProps { + history: ReputationHistory[]; +} + +export const AgentReputationTrend: React.FC = ({ history }) => { + // Format dates for display + const data = history.map(item => { + const d = new Date(item.date); + return { + ...item, + displayDate: `${d.getMonth() + 1}/${d.getDate()}` + }; + }); + + return ( +
+ + + + + + + + + + +
+ ); +}; diff --git a/frontend/src/hooks/useAgentReputation.ts b/frontend/src/hooks/useAgentReputation.ts new file mode 100644 index 0000000..2bc804d --- /dev/null +++ b/frontend/src/hooks/useAgentReputation.ts @@ -0,0 +1,41 @@ +import { useState, useEffect } from 'react'; +import { getAgentReputation } from '../services/api'; +import type { AgentReputation } from '../types/agent'; + +export function useAgentReputation(agentId: string) { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (!agentId) return; + + let mounted = true; + const fetchReputation = async () => { + setLoading(true); + setError(null); + try { + const result = await getAgentReputation(agentId); + if (mounted) { + setData(result); + } + } catch (err) { + if (mounted) { + setError(err instanceof Error ? err.message : 'Failed to fetch reputation data'); + } + } finally { + if (mounted) { + setLoading(false); + } + } + }; + + fetchReputation(); + + return () => { + mounted = false; + }; + }, [agentId]); + + return { data, loading, error }; +} diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index b2479ea..37b95d4 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -114,3 +114,7 @@ export const getRecentTasks = async (walletAddress: string): Promise => { return apiClient.get('/api/agents'); }; + +export const getAgentReputation = async (id: string): Promise => { + return apiClient.get(`/api/agents/${id}/reputation`); +}; diff --git a/frontend/src/types/agent.ts b/frontend/src/types/agent.ts index 8622000..ca31d08 100644 --- a/frontend/src/types/agent.ts +++ b/frontend/src/types/agent.ts @@ -66,3 +66,21 @@ export type AgentResult = | DesignResult | null | undefined; + +export interface ReputationDimensions { + quality: number; + speed: number; + reliability: number; + cost: number; +} + +export interface ReputationHistory { + date: string; + score: number; +} + +export interface AgentReputation { + id: string; + dimensions: ReputationDimensions; + history: ReputationHistory[]; +}