diff --git a/e2e/wallet-connect.spec.ts b/e2e/wallet-connect.spec.ts index 99a2ccf..14e4bcd 100644 --- a/e2e/wallet-connect.spec.ts +++ b/e2e/wallet-connect.spec.ts @@ -4,7 +4,7 @@ const MOCK_ADDRESS = 'GBHExampleAddressForTestingPurposesOnly1234567890ABCDE'; /** Inject a fake Freighter wallet object before any app code runs. */ function mockFreighter(page: import('@playwright/test').Page) { - return page.addInitScript(() => { + return page.addInitScript((addr) => { (window as unknown as Record).freighter = { isConnected: () => Promise.resolve({ isConnected: true }), requestAccess: () => @@ -15,7 +15,7 @@ function mockFreighter(page: import('@playwright/test').Page) { signMessage: (message: string) => Promise.resolve({ signedMessage: `mocked_signature_${message}`, error: null }), }; - }); + }, MOCK_ADDRESS); } test.describe('Wallet Connect – Freighter Mocked', () => { diff --git a/package-lock.json b/package-lock.json index da3e4cf..b48d50b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1748,7 +1748,7 @@ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ - "arm" + "arm64" ], "dev": true, "license": "MIT", @@ -1765,7 +1765,7 @@ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ - "arm64" + "ia32" ], "dev": true, "license": "MIT", @@ -1799,7 +1799,7 @@ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ - "loong64" + "mips64el" ], "dev": true, "license": "MIT", @@ -1833,7 +1833,7 @@ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ - "ppc64" + "riscv64" ], "dev": true, "license": "MIT", @@ -1916,7 +1916,7 @@ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", @@ -1950,7 +1950,7 @@ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", @@ -1967,7 +1967,7 @@ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", @@ -4880,6 +4880,19 @@ "node_modules/data-view-byte-length": { "version": "1.0.2", "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/engine.io-client": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", + "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -5704,7 +5717,7 @@ "is-callable": "^1.2.7" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5885,6 +5898,12 @@ "node_modules/get-symbol-description": { "version": "1.1.0", "dev": true, + "license": "ISC" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -9350,6 +9369,56 @@ "version": "1.0.4", "dev": true, "license": "MIT", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", diff --git a/src/components/RankProgressBar.tsx b/src/components/RankProgressBar.tsx index 5a03ddc..a765730 100644 --- a/src/components/RankProgressBar.tsx +++ b/src/components/RankProgressBar.tsx @@ -4,42 +4,64 @@ interface RankProgressBarProps { xp: number; } +const TIER_COLORS: Record = { + Rookie: '#9CA3AF', + Trader: '#22C55E', + Analyst: '#2C4BFD', + Strategist: '#A855F7', + Master: '#F59E0B', + Legend: '#EC4899', +}; + export default function RankProgressBar({ xp }: RankProgressBarProps) { const { current, next, progress } = getRankTiers(xp); + const color = TIER_COLORS[current.name] ?? '#2C4BFD'; + const nextColor = next ? (TIER_COLORS[next.name] ?? '#2C4BFD') : color; return (
-
- Rank - - {current.name} - -
-
- Experience - {xp} XP -
+ Rank + + {current.name} +
-
+
+
+ Experience + {xp} XP +
+
- {next && ( -

- {next.minXp - xp} XP to {next.name} + + {next ? ( +

+ {xp} /{' '} + {next.minXp} XP to{' '} + {next.name}

+ ) : ( +

Maximum rank reached!

)}
diff --git a/src/components/RecentActivity.test.tsx b/src/components/RecentActivity.test.tsx index 31bcebd..0d19c60 100644 --- a/src/components/RecentActivity.test.tsx +++ b/src/components/RecentActivity.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import { describe, it, expect } from 'vitest'; import RecentActivity from './RecentActivity'; import type { RecentActivityItem } from '../types'; @@ -88,6 +88,57 @@ describe('RecentActivity', () => { }); }); + describe('filter chips', () => { + it('renders all three filter options', () => { + render(); + const tabs = screen.getAllByRole('tab'); + expect(tabs).toHaveLength(3); + expect(tabs[0]).toHaveTextContent('all'); + expect(tabs[1]).toHaveTextContent('correct'); + expect(tabs[2]).toHaveTextContent('incorrect'); + }); + + it('defaults to "all" filter showing all items', () => { + render(); + expect(screen.getAllByRole('listitem')).toHaveLength(3); + }); + + it('filters to show only correct (Won) items when "correct" is selected', () => { + render(); + fireEvent.click(screen.getByRole('tab', { name: 'correct' })); + expect(screen.getAllByRole('listitem')).toHaveLength(2); + expect(screen.getByText('BTC')).toBeInTheDocument(); + expect(screen.getByText('XLM')).toBeInTheDocument(); + expect(screen.queryByText('ETH')).not.toBeInTheDocument(); + }); + + it('filters to show only incorrect (Lost) items when "incorrect" is selected', () => { + render(); + fireEvent.click(screen.getByRole('tab', { name: 'incorrect' })); + expect(screen.getAllByRole('listitem')).toHaveLength(1); + expect(screen.getByText('ETH')).toBeInTheDocument(); + expect(screen.queryByText('BTC')).not.toBeInTheDocument(); + expect(screen.queryByText('XLM')).not.toBeInTheDocument(); + }); + + it('shows filter-specific empty message when no items match', () => { + const allWon: RecentActivityItem[] = [ + { id: '1', asset: 'BTC', result: 'Won', amount: 10, mode: 'updown' }, + ]; + render(); + fireEvent.click(screen.getByRole('tab', { name: 'incorrect' })); + expect(screen.getByText(/no incorrect predictions yet/i)).toBeInTheDocument(); + expect(screen.queryByRole('list')).not.toBeInTheDocument(); + }); + + it('shows default empty message when items array is empty regardless of filter', () => { + render(); + fireEvent.click(screen.getByRole('tab', { name: 'correct' })); + expect(screen.getByText(/no predictions yet/i)).toBeInTheDocument(); + expect(screen.queryByRole('list')).not.toBeInTheDocument(); + }); + }); + describe('loading and error states', () => { it('renders a loading state with skeletons', () => { render(); diff --git a/src/components/RecentActivity.tsx b/src/components/RecentActivity.tsx index 9269580..29df974 100644 --- a/src/components/RecentActivity.tsx +++ b/src/components/RecentActivity.tsx @@ -1,6 +1,12 @@ +import { useState } from 'react'; import { Activity } from 'lucide-react'; +import { cn } from '../lib/utils'; import type { RecentActivityItem } from '../types'; +type FilterOption = 'all' | 'correct' | 'incorrect'; + +const FILTER_OPTIONS: FilterOption[] = ['all', 'correct', 'incorrect']; + interface RecentActivityProps { items: RecentActivityItem[]; isLoading?: boolean; @@ -9,6 +15,14 @@ interface RecentActivityProps { } export default function RecentActivity({ items, isLoading, error, onRetry }: RecentActivityProps) { + const [activeFilter, setActiveFilter] = useState('all'); + + const filteredItems = + activeFilter === 'all' + ? items + : items.filter((item) => + activeFilter === 'correct' ? item.result === 'Won' : item.result === 'Lost', + ); // Loading state if (isLoading) { return ( @@ -66,21 +80,53 @@ export default function RecentActivity({ items, isLoading, error, onRetry }: Rec Recent Predictions - {items.length === 0 ? ( +
+ {FILTER_OPTIONS.map((opt) => ( + + ))} +
+ + {filteredItems.length === 0 ? (
) : (
    - {items.map((item) => ( + {filteredItems.map((item) => (
  • | undefined; + return { baseFee: stroopsToXlm(baseFeeStroops), resourceFee: stroopsToXlm(resourceFeeStroops), diff --git a/src/main.tsx b/src/main.tsx index 4841350..ce6b895 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,3 +1,4 @@ +/// import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css'