Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Schedule from '@/pages/Schedule';
import StellarSplit from '@/pages/StellarSplit';
import Names from '@/pages/Names';
import Activity from '@/pages/Activity';
import Debug from '@/pages/Debug';

export function App() {
return (
Expand All @@ -29,6 +30,8 @@ export function App() {
<Route path="/pay" element={<Send />} />
<Route path="/names" element={<Names />} />
<Route path="/activity" element={<Activity />} />
<Route path="/history" element={<Activity />} />
<Route path="/debug" element={<Debug />} />
<Route path="*" element={<Navigate to="/send" replace />} />
</Routes>
</main>
Expand Down
48 changes: 32 additions & 16 deletions src/components/ActivityRow.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { ActivityEntry } from '@/stores/activityStore';
import { stellarTxUrl, horizenTxUrl, solanaTxUrl, ckbTxUrl } from '@/lib/explorer';
import { StellarLink } from '@/components/StellarLink';

interface ActivityRowProps {
entry: ActivityEntry;
}

export function ActivityRow({ entry }: ActivityRowProps) {
const isStellarAccount =
entry.chain === 'stellar' && !!entry.recipient && /^[GM][A-Z2-7]+$/.test(entry.recipient);
const getExplorerUrl = () => {
switch (entry.chain) {
case 'stellar':
Expand Down Expand Up @@ -115,14 +118,23 @@ export function ActivityRow({ entry }: ActivityRowProps) {
<span className="font-mono text-[10px] uppercase tracking-widest text-outline">
Recipient / Address
</span>
<span
className="max-w-[200px] truncate font-mono text-[10px] text-on-surface"
title={entry.recipient}
>
{entry.recipient.length > 30
? `${entry.recipient.slice(0, 12)}...${entry.recipient.slice(-12)}`
: entry.recipient}
</span>
{isStellarAccount ? (
<StellarLink
value={entry.recipient!}
type="account"
className="max-w-[240px]"
linkClassName="text-[10px]"
/>
) : (
<span
className="max-w-[200px] truncate font-mono text-[10px] text-on-surface"
title={entry.recipient}
>
{entry.recipient.length > 30
? `${entry.recipient.slice(0, 12)}...${entry.recipient.slice(-12)}`
: entry.recipient}
</span>
)}
</div>
)}

Expand All @@ -131,14 +143,18 @@ export function ActivityRow({ entry }: ActivityRowProps) {
<span className="font-mono text-[10px] uppercase tracking-widest text-outline">
Hash
</span>
<a
href={getExplorerUrl()}
target="_blank"
rel="noopener noreferrer"
className="font-mono text-[10px] text-tertiary hover:underline"
>
{entry.id.slice(0, 12)}...{entry.id.slice(-12)}
</a>
{entry.chain === 'stellar' ? (
<StellarLink value={entry.id} type="tx" linkClassName="text-[10px]" />
) : (
<a
href={getExplorerUrl()}
target="_blank"
rel="noopener noreferrer"
className="font-mono text-[10px] text-tertiary hover:underline"
>
{entry.id.slice(0, 12)}...{entry.id.slice(-12)}
</a>
)}
</div>
)}
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/components/CopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);
return (
<button
type="button"
aria-label={`Copy ${text}`}
onClick={() => {
navigator.clipboard.writeText(text);
setCopied(true);
Expand Down
18 changes: 15 additions & 3 deletions src/components/FreighterConnectButton.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { StellarLink } from '@/components/StellarLink';

export const walletBtnBase =
'bg-transparent border border-outline-variant px-3 py-1.5 font-heading text-[10px] uppercase tracking-widest text-primary transition-colors hover:bg-surface-bright disabled:opacity-50 sm:px-4 sm:py-2 sm:text-xs h-8 sm:h-9';
export const walletBtnConnected =
Expand Down Expand Up @@ -47,9 +49,19 @@ export function FreighterConnectButton({

if (status === 'connected' && address) {
return (
<button onClick={onDisconnect} className={walletBtnConnected}>
{address.slice(0, 4)}...{address.slice(-4)}
</button>
<div className={`${walletBtnConnected} flex items-center gap-2`}>
<StellarLink value={address} type="account" linkClassName="text-[10px] sm:text-xs">
{address.slice(0, 4)}...{address.slice(-4)}
</StellarLink>
<button
type="button"
onClick={onDisconnect}
className="text-outline transition-colors hover:text-error"
aria-label="Disconnect Stellar wallet"
>
×
</button>
</div>
);
}

Expand Down
37 changes: 20 additions & 17 deletions src/components/StellarHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useState, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { useStellarWallet } from '@/context/StellarWalletContext';
import { stellarTxUrl } from '@/lib/explorer';
import { useActivityStore, ActivityKind, ActivityStatus } from '@/stores/activityStore';
import { EmptyState } from '@/components/EmptyState';
import { StellarLink } from '@/components/StellarLink';

/** @deprecated Use the `/activity` page. Retained for existing imports. */
export function StellarHistory() {
const navigate = useNavigate();
const { address, isConnected } = useStellarWallet();
Expand Down Expand Up @@ -186,14 +187,23 @@ export function StellarHistory() {
<span className="font-mono text-[10px] uppercase tracking-widest text-outline">
Recipient / Address
</span>
<span
className="max-w-[200px] truncate font-mono text-[10px] text-on-surface"
title={tx.recipient}
>
{tx.recipient.length > 30
? `${tx.recipient.slice(0, 12)}...${tx.recipient.slice(-12)}`
: tx.recipient}
</span>
{/^[GM][A-Z2-7]+$/.test(tx.recipient) ? (
<StellarLink
value={tx.recipient}
type="account"
className="max-w-[240px]"
linkClassName="text-[10px]"
/>
) : (
<span
className="max-w-[200px] truncate font-mono text-[10px] text-on-surface"
title={tx.recipient}
>
{tx.recipient.length > 30
? `${tx.recipient.slice(0, 12)}...${tx.recipient.slice(-12)}`
: tx.recipient}
</span>
)}
</div>
)}

Expand All @@ -202,14 +212,7 @@ export function StellarHistory() {
<span className="font-mono text-[10px] uppercase tracking-widest text-outline">
Hash
</span>
<a
href={stellarTxUrl(tx.id)}
target="_blank"
rel="noopener noreferrer"
className="font-mono text-[10px] text-tertiary hover:underline"
>
{tx.id.slice(0, 12)}...{tx.id.slice(-12)}
</a>
<StellarLink value={tx.id} type="tx" linkClassName="text-[10px]" />
</div>
)}
</div>
Expand Down
48 changes: 48 additions & 0 deletions src/components/StellarLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { ReactNode } from 'react';
import { CopyButton } from '@/components/CopyButton';
import {
stellarExpertUrl,
type StellarExpertNetworkInput,
type StellarExpertResource,
} from '@/utils/stellarExpert';

interface StellarLinkProps {
value: string;
type: StellarExpertResource;
network?: StellarExpertNetworkInput;
children?: ReactNode;
className?: string;
linkClassName?: string;
}

function truncateIdentifier(value: string): string {
if (value.length <= 28) return value;
return `${value.slice(0, 12)}...${value.slice(-12)}`;
}

export function StellarLink({
value,
type,
network,
children,
className = '',
linkClassName = '',
}: StellarLinkProps) {
const resourceLabel = type === 'tx' ? 'transaction' : type;

return (
<span className={`inline-flex min-w-0 items-center gap-2 ${className}`}>
<a
href={stellarExpertUrl(type, value, network)}
target="_blank"
rel="noopener noreferrer"
title={value}
aria-label={`View Stellar ${resourceLabel} ${value} on Stellar Expert`}
className={`min-w-0 truncate font-mono text-tertiary underline decoration-outline underline-offset-2 transition-colors hover:text-primary ${linkClassName}`}
>
{children ?? truncateIdentifier(value)}
</a>
<CopyButton text={value} />
</span>
);
}
28 changes: 8 additions & 20 deletions src/components/StellarMatchCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from 'react';
import { stellarTxUrl, stellarAddrUrl } from '@/lib/explorer';
import { CopyButton } from '@/components/CopyButton';
import { StellarLink } from '@/components/StellarLink';
import { PrivacyTooltip } from '@/components/PrivacyTooltip';
import type { StellarAssetKey } from '@/lib/stellar/assets';
import { STELLAR_ASSETS, getAssetByKey, formatStellarAssetAmount } from '@/lib/stellar/assets';
Expand Down Expand Up @@ -340,17 +340,12 @@ export function StellarMatchCard({
<span className="font-mono text-[10px] uppercase tracking-widest text-outline">
Stealth Address
</span>
<div className="mt-0.5 flex items-center gap-2">
<a
href={stellarAddrUrl(stealthAddress)}
target="_blank"
rel="noopener noreferrer"
className="block truncate font-mono text-xs text-primary underline"
>
{stealthAddress}
</a>
<CopyButton text={stealthAddress} />
</div>
<StellarLink
value={stealthAddress}
type="account"
className="mt-0.5 max-w-full"
linkClassName="text-xs"
/>
</div>
<div className="flex shrink-0 flex-col items-end gap-0.5">
{balanceState === 'loading' ? (
Expand Down Expand Up @@ -454,14 +449,7 @@ export function StellarMatchCard({
<span className="inline-block h-1.5 w-1.5 bg-tertiary"></span>
<span className="font-mono text-[10px] text-on-surface-variant">
{feeBumpHash ? 'Sponsored withdrawal complete' : 'Withdrawn'} —{' '}
<a
href={stellarTxUrl(withdrawHash)}
target="_blank"
rel="noopener noreferrer"
className="text-primary underline"
>
{withdrawHash.slice(0, 14)}...
</a>
<StellarLink value={withdrawHash} type="tx" linkClassName="text-[10px]" />
</span>
</div>
{feeBumpHash && (
Expand Down
26 changes: 8 additions & 18 deletions src/components/StellarReceive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { useStellarWallet } from '@/context/StellarWalletContext';
import { useActivity } from '@/context/ActivityContext';
import { CopyButton } from '@/components/CopyButton';
import { trackEvent } from '@/lib/telemetry';
import { stellarTxUrl, stellarAddrUrl } from '@/lib/explorer';
import { StellarLink } from '@/components/StellarLink';
import { PrivacyBadge } from '@/components/PrivacyBadge';
import { computePrivacyScore } from '@/lib/privacy-score';
import { STELLAR_NETWORK } from '@/config';
Expand Down Expand Up @@ -531,15 +531,12 @@ function StellarMatchCardContainer({
{t('common.stealthAddress')}
</span>
<div className="mt-0.5 flex items-center gap-2">
<a
href={stellarAddrUrl(match.stealthAddress)}
target="_blank"
rel="noopener noreferrer"
className="block truncate font-mono text-xs text-primary underline"
>
{match.stealthAddress}
</a>
<CopyButton text={match.stealthAddress} />
<StellarLink
value={match.stealthAddress}
type="account"
className="max-w-full"
linkClassName="text-xs"
/>
</div>
</div>
<div className="flex shrink-0 items-center gap-2">
Expand Down Expand Up @@ -596,14 +593,7 @@ function StellarMatchCardContainer({
<span className="inline-block h-1.5 w-1.5 bg-tertiary"></span>
<span className="font-mono text-[10px] text-on-surface-variant">
{t('common.withdrawn')} —{' '}
<a
href={stellarTxUrl(withdrawHash)}
target="_blank"
rel="noopener noreferrer"
className="text-primary underline"
>
{withdrawHash.slice(0, 14)}...
</a>
<StellarLink value={withdrawHash} type="tx" linkClassName="text-[10px]" />
</span>
</div>
)}
Expand Down
11 changes: 2 additions & 9 deletions src/components/StellarReceiveView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReactNode } from 'react';
import { useNavigate } from 'react-router-dom';
import { stellarTxUrl } from '@/lib/explorer';
import { StellarLink } from '@/components/StellarLink';
import { CopyButton } from '@/components/CopyButton';
import { StellarPaymentLink } from '@/components/StellarPaymentLink';
import { ImportConflictModal } from '@/components/ImportConflictModal';
Expand Down Expand Up @@ -191,14 +191,7 @@ export function StellarReceiveView({
{regHash && (
<>
{' — '}
<a
href={stellarTxUrl(regHash)}
target="_blank"
rel="noopener noreferrer"
className="text-primary underline"
>
{regHash.slice(0, 14)}...
</a>
<StellarLink value={regHash} type="tx" linkClassName="text-xs" />
</>
)}
</span>
Expand Down
Loading
Loading