Skip to content

Commit 0039510

Browse files
authored
Merge pull request OlaGreat#232 from Biokes/new_wave_fixes
New wave fixes
2 parents 8e30115 + 8ec70e9 commit 0039510

3 files changed

Lines changed: 122 additions & 45 deletions

File tree

frontend/src/components/profile-card.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
import { useState } from "react";
13
import { isValidStellarAddress } from "@/lib/stellar";
24

35
type Asset = {
@@ -28,9 +30,18 @@ export function ProfileCard({
2830
twitterHandle,
2931
githubHandle,
3032
}: ProfileCardProps) {
33+
const [copied, setCopied] = useState(false);
3134
const isValid = isValidStellarAddress(walletAddress);
3235
const hasSocialLinks = email || websiteUrl || twitterHandle || githubHandle;
3336

37+
const handleCopy = async () => {
38+
await navigator.clipboard.writeText(walletAddress);
39+
setCopied(true);
40+
setTimeout(() => setCopied(false), 2000);
41+
};
42+
43+
const expertUrl = `https://stellar.expert/explorer/testnet/account/${walletAddress}`;
44+
3445
return (
3546
<article className="rounded-[2rem] border border-white/10 bg-white/[0.04] p-5 sm:p-7 shadow-xl shadow-black/15">
3647
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
@@ -79,7 +90,22 @@ export function ProfileCard({
7990
</div>
8091
<div className="w-full sm:w-auto mt-4 sm:mt-0 rounded-3xl border border-mint/20 bg-ink/50 px-4 py-3 text-sm text-sky/80">
8192
<p className="font-semibold text-white">Stellar Wallet</p>
82-
<p className="mt-2 break-all">{walletAddress}</p>
93+
<div className="mt-2 flex items-center">
94+
<a
95+
href={expertUrl}
96+
target="_blank"
97+
rel="noopener noreferrer"
98+
className="text-xs text-indigo-500 hover:underline font-mono break-all flex-1"
99+
>
100+
{walletAddress}
101+
</a>
102+
<button
103+
onClick={handleCopy}
104+
className="ml-2 text-xs text-gray-400 hover:text-gray-600 transition-colors"
105+
>
106+
{copied ? 'Copied!' : 'Copy'}
107+
</button>
108+
</div>
83109
<p className={`mt-3 ${isValid ? "text-mint" : "text-gold"}`}>
84110
{isValid ? "Valid Stellar address" : "Replace with a valid Stellar address"}
85111
</p>

frontend/src/components/profile-tabs.test.tsx

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect, vi, beforeEach } from 'vitest';
2-
import { render, screen } from '@testing-library/react';
2+
import { render, screen, fireEvent } from '@testing-library/react';
33
import { ProfileTabs } from '@/components/profile-tabs';
44

55
// Mock framer-motion to avoid animation issues in tests
@@ -53,7 +53,7 @@ describe('ProfileTabs', () => {
5353
amount: '100',
5454
assetCode: 'XLM',
5555
createdAt: '2026-03-01T00:00:00Z',
56-
status: 'verified',
56+
status: 'SUCCESS',
5757
},
5858
],
5959
}),
@@ -63,5 +63,83 @@ describe('ProfileTabs', () => {
6363

6464
expect(await screen.findByText('100 XLM')).toBeInTheDocument();
6565
expect(screen.queryByText('No support yet')).not.toBeInTheDocument();
66+
67+
// Check that status is rendered as a badge
68+
expect(screen.getByText('SUCCESS')).toBeInTheDocument();
69+
});
70+
71+
it('renders status badges with correct colors', async () => {
72+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
73+
json: async () => ({
74+
transactions: [
75+
{
76+
id: '1',
77+
txHash: 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
78+
amount: '100',
79+
assetCode: 'XLM',
80+
createdAt: '2026-03-01T00:00:00Z',
81+
status: 'SUCCESS',
82+
},
83+
{
84+
id: '2',
85+
txHash: 'fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321',
86+
amount: '50',
87+
assetCode: 'USDC',
88+
createdAt: '2026-03-02T00:00:00Z',
89+
status: 'PENDING',
90+
},
91+
{
92+
id: '3',
93+
txHash: '1111111111111111111111111111111111111111111111111111111111111111',
94+
amount: '25',
95+
assetCode: 'XLM',
96+
createdAt: '2026-03-03T00:00:00Z',
97+
status: 'FAILED',
98+
},
99+
{
100+
id: '4',
101+
txHash: '2222222222222222222222222222222222222222222222222222222222222222',
102+
amount: '75',
103+
assetCode: 'XLM',
104+
createdAt: '2026-03-04T00:00:00Z',
105+
status: 'UNKNOWN',
106+
},
107+
],
108+
}),
109+
}));
110+
111+
render(<ProfileTabs username="stellar-dev" />);
112+
113+
// Check that all status badges are rendered
114+
expect(await screen.findByText('SUCCESS')).toBeInTheDocument();
115+
expect(screen.getByText('PENDING')).toBeInTheDocument();
116+
expect(screen.getByText('FAILED')).toBeInTheDocument();
117+
expect(screen.getByText('UNKNOWN')).toBeInTheDocument();
118+
119+
// Check that badges have correct CSS classes for colors
120+
const successBadge = screen.getByText('SUCCESS');
121+
const pendingBadge = screen.getByText('PENDING');
122+
const failedBadge = screen.getByText('FAILED');
123+
const unknownBadge = screen.getByText('UNKNOWN');
124+
125+
expect(successBadge).toHaveClass('bg-green-100', 'text-green-800');
126+
expect(pendingBadge).toHaveClass('bg-yellow-100', 'text-yellow-800');
127+
expect(failedBadge).toHaveClass('bg-red-100', 'text-red-800');
128+
expect(unknownBadge).toHaveClass('bg-gray-100', 'text-gray-800');
129+
});
130+
131+
it('renders badges coming soon empty state', () => {
132+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
133+
json: async () => ({ transactions: [] }),
134+
}));
135+
136+
render(<ProfileTabs username="stellar-dev" />);
137+
138+
// Click on the badges tab
139+
const badgesTab = screen.getByText('Badges');
140+
fireEvent.click(badgesTab);
141+
142+
expect(screen.getByText('Badges coming soon')).toBeInTheDocument();
143+
expect(screen.getByText('Achievement badges will appear here once earned.')).toBeInTheDocument();
66144
});
67145
});

frontend/src/components/profile-tabs.tsx

Lines changed: 15 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useState, useEffect } from "react";
44
import {
55
History, Award, LayoutDashboard,
6-
ExternalLink, Clock, ShieldCheck
6+
ExternalLink
77
} from "lucide-react";
88
import Link from "next/link";
99
import { motion, AnimatePresence } from "framer-motion";
@@ -25,6 +25,12 @@ export function ProfileTabs({ username }: { username: string }) {
2525
const [transactions, setTransactions] = useState<Transaction[]>([]);
2626
const [loading, setLoading] = useState(false);
2727

28+
const statusStyles: Record<string, string> = {
29+
SUCCESS: 'bg-green-100 text-green-800',
30+
PENDING: 'bg-yellow-100 text-yellow-800',
31+
FAILED: 'bg-red-100 text-red-800',
32+
};
33+
2834
useEffect(() => {
2935
if (activeTab === "history") {
3036
setLoading(true);
@@ -93,10 +99,9 @@ export function ProfileTabs({ username }: { username: string }) {
9399
{transactions.map((tx) => (
94100
<tr key={tx.id} className="group hover:bg-white/[0.02] transition-colors">
95101
<td className="px-6 py-4">
96-
<div className="flex items-center gap-2">
97-
<ShieldCheck size={14} className="text-mint" />
98-
<span className="text-[11px] font-bold text-mint uppercase">Verified</span>
99-
</div>
102+
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${statusStyles[tx.status] ?? 'bg-gray-100 text-gray-800'}`}>
103+
{tx.status}
104+
</span>
100105
</td>
101106
<td className="px-6 py-4">
102107
<span className="text-white font-medium">{tx.amount} {tx.assetCode}</span>
@@ -135,24 +140,12 @@ export function ProfileTabs({ username }: { username: string }) {
135140
initial={{ opacity: 0, y: 10 }}
136141
animate={{ opacity: 1, y: 0 }}
137142
exit={{ opacity: 0, y: -10 }}
138-
className="grid grid-cols-2 gap-4 sm:grid-cols-4"
143+
className="min-h-[300px]"
139144
>
140-
<BadgeCard
141-
name="Early Bird"
142-
desc="Supported in the first 24h"
143-
date="Mar 2024"
144-
color="text-gold"
145-
/>
146-
<BadgeCard
147-
name="Nova Whale"
148-
desc="5,000+ XLM Contributed"
149-
locked
150-
/>
151-
<BadgeCard
152-
name="Consistent"
153-
desc="Active Drip for 3 months"
154-
locked
155-
/>
145+
<div className="flex flex-col items-center justify-center py-12 text-center text-gray-400">
146+
<p className="text-lg font-medium">Badges coming soon</p>
147+
<p className="text-sm mt-1">Achievement badges will appear here once earned.</p>
148+
</div>
156149
</motion.div>
157150
)}
158151
</AnimatePresence>
@@ -175,23 +168,3 @@ function TabButton({ active, onClick, icon, label }: any) {
175168
</button>
176169
);
177170
}
178-
179-
function BadgeCard({ name, desc, date, color = "text-steel/50", locked = false }: any) {
180-
return (
181-
<div className={`relative flex flex-col items-center gap-3 rounded-2xl border border-white/10 bg-white/5 p-6 text-center ${locked ? "opacity-50 grayscale" : ""}`}>
182-
<div className={`rounded-xl bg-white/5 p-3 ${color}`}>
183-
<Award size={32} />
184-
</div>
185-
<div>
186-
<h4 className="text-xs font-bold text-white uppercase tracking-wider">{name}</h4>
187-
<p className="mt-1 text-[10px] text-steel leading-tight">{desc}</p>
188-
{date && <p className="mt-2 text-[9px] font-mono text-mint/60">{date}</p>}
189-
</div>
190-
{locked && (
191-
<div className="absolute right-2 top-2 rounded-full bg-black/40 p-1">
192-
<Clock size={10} className="text-white/40" />
193-
</div>
194-
)}
195-
</div>
196-
);
197-
}

0 commit comments

Comments
 (0)