Skip to content

Commit c878fce

Browse files
authored
Merge pull request #410 from Dydex/copy-to-clipboard
fix: Added copy to clipboard
2 parents 7de1322 + 60f1d0a commit c878fce

9 files changed

Lines changed: 343 additions & 126 deletions

File tree

dex_with_fiat_frontend/src/components/BankDetailsModal.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import TransferTimeline, {
2626
StatusEvent,
2727
TransferStatus,
2828
} from '@/components/TransferTimeline';
29+
import CopyButton from '@/components/ui/CopyButton';
2930
import { useAccessibleModal } from '@/hooks/useAccessibleModal';
3031
import { useIdempotentAction } from '@/hooks/useIdempotentAction';
3132
import { getOrCreateClientSessionId } from '@/lib/clientSession';
@@ -941,7 +942,10 @@ export default function BankDetailsModal({
941942
Transfer Status
942943
</p>
943944
<TransferTimeline
944-
events={statusEvents}
945+
events={statusEvents.map((event) => ({
946+
...event,
947+
copyValue: transferReference || undefined,
948+
}))}
945949
isPolling={isPollingStatus}
946950
/>
947951
</div>
@@ -985,9 +989,12 @@ export default function BankDetailsModal({
985989
<p className="theme-text-muted text-xs mb-1">
986990
Transfer Reference
987991
</p>
988-
<p className="theme-text-primary font-mono text-sm break-all">
989-
{transferReference}
990-
</p>
992+
<div className="flex items-center gap-1.5">
993+
<p className="theme-text-primary font-mono text-sm break-all">
994+
{transferReference}
995+
</p>
996+
<CopyButton value={transferReference} />
997+
</div>
991998
</div>
992999
)}
9931000

@@ -997,7 +1004,13 @@ export default function BankDetailsModal({
9971004
<p className="theme-text-muted text-xs font-semibold uppercase tracking-wider mb-3">
9981005
Transfer History
9991006
</p>
1000-
<TransferTimeline events={statusEvents} isPolling={false} />
1007+
<TransferTimeline
1008+
events={statusEvents.map((event) => ({
1009+
...event,
1010+
copyValue: transferReference || undefined,
1011+
}))}
1012+
isPolling={false}
1013+
/>
10011014
</div>
10021015
)}
10031016

dex_with_fiat_frontend/src/components/LandingPage.tsx

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ import {
2222
FileText,
2323
HelpCircle,
2424
MessageSquare,
25-
Copy,
26-
Check,
2725
Sun,
2826
Moon,
2927
} from 'lucide-react';
3028
import { useRouter } from 'next/navigation';
3129
import { useTheme } from '../contexts/ThemeContext';
30+
import CopyButton from '@/components/ui/CopyButton';
3231
import OfflineStatusBanner from '@/components/OfflineStatusBanner';
3332

3433
interface FeatureCardProps {
@@ -106,7 +105,6 @@ export default function LandingPage() {
106105
const [isSubmitted, setIsSubmitted] = useState(false);
107106
const { isDarkMode, toggleDarkMode } = useTheme();
108107
const [heroVisible, setHeroVisible] = useState(false);
109-
const [copied, setCopied] = useState(false);
110108

111109
const contractAddress =
112110
'CB4L7Q6M3N7Z6K4L2A3B5C6D7E8F9G0H1I2J3K4L5M6N7O8P9Q0R1S2T3U4V5W6X7Y8Z9'; // Replace with actual deployed address
@@ -119,16 +117,6 @@ export default function LandingPage() {
119117
router.push('/chat');
120118
};
121119

122-
const copyToClipboard = async () => {
123-
try {
124-
await navigator.clipboard.writeText(contractAddress);
125-
setCopied(true);
126-
setTimeout(() => setCopied(false), 2000);
127-
} catch (err) {
128-
console.error('Failed to copy: ', err);
129-
}
130-
};
131-
132120
const handleEmailSubmit = (e: React.FormEvent) => {
133121
e.preventDefault();
134122
setIsSubmitted(true);
@@ -321,17 +309,7 @@ export default function LandingPage() {
321309
<code className="text-blue-400 font-mono text-sm break-all flex-1 mr-2">
322310
{contractAddress}
323311
</code>
324-
<button
325-
onClick={copyToClipboard}
326-
className="flex-shrink-0 p-1 hover:bg-gray-700 rounded transition-colors duration-200"
327-
title="Copy to clipboard"
328-
>
329-
{copied ? (
330-
<Check className="w-4 h-4 text-green-400" />
331-
) : (
332-
<Copy className="w-4 h-4 text-gray-400" />
333-
)}
334-
</button>
312+
<CopyButton value={contractAddress} />
335313
</div>
336314
</div>
337315
</div>

dex_with_fiat_frontend/src/components/Message.tsx

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import { useUserPreferences } from '@/contexts/UserPreferencesContext';
66
import { useMasking } from '@/hooks/useMasking';
77
import { useCurrencyConversion } from '@/hooks/useCurrencyConversion';
88
import { ChatMessage } from '@/types';
9-
import { AlertTriangle, Bot, Clock, Coins, Copy, Check, Link, RotateCcw, User, Loader2, RefreshCcw, XCircle } from 'lucide-react';
10-
import React, { useState, useCallback } from 'react';
9+
import { AlertTriangle, Bot, Clock, Coins, Link, RotateCcw, User, Loader2, RefreshCcw, XCircle } from 'lucide-react';
10+
import React from 'react';
1111
import ReactMarkdown from 'react-markdown';
1212
import { sanitizeUrl } from '@/lib/markdownSanitizer';
1313
import { useTranslation } from '@/contexts/TranslationContext';
1414
import { motion, useReducedMotion } from 'framer-motion';
15+
import CopyButton from '@/components/ui/CopyButton';
1516

1617
interface MessageProps {
1718
message: ChatMessage;
@@ -40,7 +41,7 @@ export default function Message({ message, onActionClick, onRetry, shouldAnimate
4041
const { t } = useTranslation();
4142
const isPending = message.metadata?.status === 'pending';
4243
const isFailed = message.metadata?.status === 'failed';
43-
const [receiptCopied, setReceiptCopied] = useState(false);
44+
4445

4546
// Currency conversion hook for transaction amounts
4647
const amountForConversion = message.metadata?.transactionData?.amountIn
@@ -52,14 +53,7 @@ export default function Message({ message, onActionClick, onRetry, shouldAnimate
5253
tokenForConversion,
5354
);
5455

55-
const handleCopyReceiptId = useCallback((receiptId: string) => {
56-
navigator.clipboard?.writeText(receiptId).then(() => {
57-
setReceiptCopied(true);
58-
setTimeout(() => setReceiptCopied(false), 2000);
59-
}).catch(() => {
60-
/* clipboard unavailable */
61-
});
62-
}, []);
56+
6357

6458
const variants = {
6559
initial: {
@@ -386,6 +380,40 @@ export default function Message({ message, onActionClick, onRetry, shouldAnimate
386380
</span>
387381
</div>
388382
)}
383+
{message.metadata.transactionData.transactionId && (
384+
<div className="flex justify-between items-center gap-2">
385+
<span>Request ID:</span>
386+
<div className="flex items-center gap-1">
387+
<span className="theme-text-primary font-mono text-xs">
388+
{message.metadata.transactionData.transactionId.slice(0, 6)}
389+
...
390+
{message.metadata.transactionData.transactionId.slice(-4)}
391+
</span>
392+
<CopyButton
393+
value={message.metadata.transactionData.transactionId}
394+
className="flex-shrink-0 p-0.5"
395+
iconClassName="w-3 h-3"
396+
/>
397+
</div>
398+
</div>
399+
)}
400+
{message.metadata.transactionData.txHash && (
401+
<div className="flex justify-between items-center gap-2">
402+
<span>Tx Hash:</span>
403+
<div className="flex items-center gap-1">
404+
<span className="theme-text-primary font-mono text-xs">
405+
{message.metadata.transactionData.txHash.slice(0, 6)}
406+
...
407+
{message.metadata.transactionData.txHash.slice(-4)}
408+
</span>
409+
<CopyButton
410+
value={message.metadata.transactionData.txHash}
411+
className="flex-shrink-0 p-0.5"
412+
iconClassName="w-3 h-3"
413+
/>
414+
</div>
415+
</div>
416+
)}
389417
{message.metadata.transactionData.receiptId && (
390418
<div className="flex justify-between items-center gap-2">
391419
<span>Receipt ID:</span>
@@ -395,22 +423,11 @@ export default function Message({ message, onActionClick, onRetry, shouldAnimate
395423
...
396424
{message.metadata.transactionData.receiptId.slice(-4)}
397425
</span>
398-
<button
399-
type="button"
400-
onClick={() =>
401-
handleCopyReceiptId(
402-
message.metadata!.transactionData!.receiptId!,
403-
)
404-
}
405-
className="flex-shrink-0 p-0.5 rounded text-gray-400 hover:text-blue-400 transition-colors"
406-
title="Copy receipt ID"
407-
>
408-
{receiptCopied ? (
409-
<Check className="w-3 h-3 text-green-400" />
410-
) : (
411-
<Copy className="w-3 h-3" />
412-
)}
413-
</button>
426+
<CopyButton
427+
value={message.metadata.transactionData.receiptId}
428+
className="flex-shrink-0 p-0.5"
429+
iconClassName="w-3 h-3"
430+
/>
414431
</div>
415432
</div>
416433
)}

dex_with_fiat_frontend/src/components/ReceiptDrawer.tsx

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { useTranslation } from '@/contexts/TranslationContext';
1414
import { useTheme } from '@/contexts/ThemeContext';
1515
import { useTransactionFilters } from '@/hooks/useTransactionFilters';
1616
import { FilterChipBar } from './filters/FilterChipBar';
17+
import CopyButton from '@/components/ui/CopyButton';
1718
import { TransactionAmountDisplay } from './TransactionAmountDisplay';
1819

1920
interface ReceiptDrawerProps {
@@ -244,22 +245,35 @@ export default function ReceiptDrawer({
244245
)}
245246
{tx.txHash && (
246247
<div className="flex justify-between items-center gap-2">
247-
<span className="text-gray-500">
248-
{t('receipt.hash')}
248+
<span className="text-gray-500">{t('receipt.hash')}</span>
249+
<div className="flex items-center gap-1">
250+
<a
251+
href={`https://stellar.expert/explorer/testnet/tx/${tx.txHash}`}
252+
target="_blank"
253+
rel="noopener noreferrer"
254+
className="flex items-center gap-1 text-blue-500 hover:underline font-mono text-[10px]"
255+
>
256+
{tx.txHash.substring(0, 8)}...
257+
<ExternalLink className="w-3 h-3" />
258+
</a>
259+
<CopyButton value={tx.txHash} iconClassName="w-3 h-3" />
260+
</div>
261+
</div>
262+
)}
263+
{tx.reference && (
264+
<div className="flex justify-between items-center gap-2">
265+
<span className="text-gray-500">Reference</span>
266+
<span className="flex items-center gap-1 font-mono text-[10px] dark:text-gray-300">
267+
{tx.reference}
268+
<CopyButton value={tx.reference} iconClassName="w-3 h-3" />
249269
</span>
250-
<a
251-
href={`https://stellar.expert/explorer/testnet/tx/${tx.txHash}`}
252-
target="_blank"
253-
rel="noopener noreferrer"
254-
className="flex items-center gap-1 text-blue-500 hover:underline font-mono text-[10px]"
255-
>
256-
{tx.txHash.substring(0, 8)}...
257-
<ExternalLink className="w-3 h-3" />
258-
</a>
259270
</div>
260271
)}
261272
<div className="flex justify-between text-[10px] text-gray-500 pt-2 border-t dark:border-gray-700">
262-
<span>{tx.id}</span>
273+
<span className="flex items-center gap-1">
274+
<span>{tx.id}</span>
275+
<CopyButton value={tx.id} iconClassName="w-3 h-3" />
276+
</span>
263277
<span>{new Date(tx.createdAt).toLocaleString()}</span>
264278
</div>
265279
</div>

dex_with_fiat_frontend/src/components/StellarChatInterface.tsx

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { useTranslation } from '@/contexts/TranslationContext';
5151
import ReceiptDrawer from './ReceiptDrawerWrapper';
5252
import { useTxHistory } from '@/hooks/useTxHistory';
5353
import { subscribeToQueue, processQueue } from '@/lib/networkQueue';
54+
import CopyButton from '@/components/ui/CopyButton';
5455
import SplitViewComparison from './SplitViewComparison';
5556
import ChatSearchPanel from './ChatSearchPanel';
5657
import { useChatHistory } from '@/hooks/useChatHistory';
@@ -515,7 +516,7 @@ export default function StellarChatInterface() {
515516

516517
{connection.isConnected ? (
517518
<div className="flex items-center gap-2">
518-
<div ref={accountDropdownRef} className="relative">
519+
<div ref={accountDropdownRef} className="relative flex items-center gap-1">
519520
<button
520521
onClick={() =>
521522
accounts.length > 1 &&
@@ -534,6 +535,11 @@ export default function StellarChatInterface() {
534535
/>
535536
)}
536537
</button>
538+
<CopyButton
539+
value={connection.address}
540+
iconClassName="w-3 h-3"
541+
className="p-0.5"
542+
/>
537543
{showAccountDropdown && accounts.length > 1 && (
538544
<div
539545
className={`absolute right-0 top-full mt-1 w-56 rounded-lg shadow-lg border z-50 ${isDarkMode ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}`}
@@ -544,27 +550,36 @@ export default function StellarChatInterface() {
544550
{t('header.switch_account')}
545551
</div>
546552
{accounts.map((account, idx) => (
547-
<button
553+
<div
548554
key={account.address}
549-
onClick={() => {
550-
selectAccount(idx);
551-
setShowAccountDropdown(false);
552-
}}
553-
className={`w-full flex items-center gap-2 px-3 py-2 text-xs transition-colors ${idx === selectedAccountIndex ? (isDarkMode ? 'bg-blue-900/50 text-blue-400' : 'bg-blue-50 text-blue-600') : isDarkMode ? 'text-gray-300 hover:bg-gray-700' : 'text-gray-700 hover:bg-gray-50'}`}
555+
className={`flex items-center gap-1 px-1.5 py-1 ${idx === selectedAccountIndex ? (isDarkMode ? 'bg-blue-900/50 text-blue-400' : 'bg-blue-50 text-blue-600') : ''}`}
554556
>
555-
<User className="w-3.5 h-3.5 flex-shrink-0" />
556-
<span className="font-mono truncate">
557-
{account.address.slice(0, 6)}
558-
{account.address.slice(-4)}
559-
</span>
560-
{idx === selectedAccountIndex && (
561-
<span
562-
className={`ml-auto text-[10px] px-1.5 py-0.5 rounded ${isDarkMode ? 'bg-blue-900/50' : 'bg-blue-100'}`}
563-
>
564-
{t('header.active_account')}
557+
<button
558+
onClick={() => {
559+
selectAccount(idx);
560+
setShowAccountDropdown(false);
561+
}}
562+
className={`flex-1 flex items-center gap-2 px-1.5 py-1 text-xs rounded transition-colors ${idx === selectedAccountIndex ? '' : isDarkMode ? 'text-gray-300 hover:bg-gray-700' : 'text-gray-700 hover:bg-gray-50'}`}
563+
>
564+
<User className="w-3.5 h-3.5 flex-shrink-0" />
565+
<span className="font-mono truncate">
566+
{account.address.slice(0, 6)}
567+
{account.address.slice(-4)}
565568
</span>
566-
)}
567-
</button>
569+
{idx === selectedAccountIndex && (
570+
<span
571+
className={`ml-auto text-[10px] px-1.5 py-0.5 rounded ${isDarkMode ? 'bg-blue-900/50' : 'bg-blue-100'}`}
572+
>
573+
{t('header.active_account')}
574+
</span>
575+
)}
576+
</button>
577+
<CopyButton
578+
value={account.address}
579+
iconClassName="w-3 h-3"
580+
className="p-0.5"
581+
/>
582+
</div>
568583
))}
569584
</div>
570585
)}

0 commit comments

Comments
 (0)