Skip to content
Draft
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
6 changes: 1 addition & 5 deletions src/components/safe/portfolio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,5 @@ export const Portfolio = ({ portfolio, currency, isLoading }: PortfolioProps) =>
))}
</StyledDataTable>
</StyledVerticalStack>
) : (
<div className="w-full flex flex-col items-center justify-center gap-2 p-4">
<p className="text-dfxBlue-300 text-left">{translate('screens/safe', 'No assets found')}</p>
</div>
);
) : null;
};
4 changes: 2 additions & 2 deletions src/components/safe/safe-transaction-interface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export const SafeTransactionInterface = () => {
onClick={setTransactionType}
buttonLabel={(type) =>
type === TransactionType.FIAT
? translate('screens/payment', 'Fiat')
: translate('screens/payment', 'Crypto')
? translate('screens/safe', 'Fiat')
: translate('screens/safe', 'Crypto')
}
size={ButtonGroupSize.SM}
/>
Expand Down
85 changes: 85 additions & 0 deletions src/components/safe/transaction-history.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
AlignContent,
DfxIcon,
IconColor,
IconSize,
IconVariant,
SpinnerSize,
StyledDataTable,
StyledDataTableRow,
StyledLoadingSpinner,
StyledVerticalStack,
} from '@dfx.swiss/react-components';
import { useSettingsContext } from 'src/contexts/settings.context';
import { CustodyOrderHistory, CustodyOrderHistoryStatus, CustodyOrderType } from 'src/dto/order.dto';

interface TransactionHistoryProps {
transactions: CustodyOrderHistory[];
isLoading: boolean;
}

const ORDER_TYPE_LABELS: Record<CustodyOrderType, string> = {
[CustodyOrderType.DEPOSIT]: 'Fiat Deposit',
[CustodyOrderType.WITHDRAWAL]: 'Fiat Withdrawal',
[CustodyOrderType.RECEIVE]: 'Crypto Deposit',
[CustodyOrderType.SEND]: 'Crypto Withdrawal',
[CustodyOrderType.SWAP]: 'Swap',
[CustodyOrderType.SAVING_DEPOSIT]: 'Saving Deposit',
[CustodyOrderType.SAVING_WITHDRAWAL]: 'Saving Withdrawal',
};

const ORDER_TYPE_ICONS: Record<CustodyOrderType, IconVariant> = {
[CustodyOrderType.DEPOSIT]: IconVariant.BANK,
[CustodyOrderType.WITHDRAWAL]: IconVariant.BANK,
[CustodyOrderType.RECEIVE]: IconVariant.WALLET,
[CustodyOrderType.SEND]: IconVariant.WALLET,
[CustodyOrderType.SWAP]: IconVariant.SWAP,
[CustodyOrderType.SAVING_DEPOSIT]: IconVariant.SAFE,
[CustodyOrderType.SAVING_WITHDRAWAL]: IconVariant.SAFE,
};

const STATUS_LABELS: Record<CustodyOrderHistoryStatus, string> = {
[CustodyOrderHistoryStatus.WAITING_FOR_PAYMENT]: 'Waiting for payment',
[CustodyOrderHistoryStatus.CHECK_PENDING]: 'Check pending',
[CustodyOrderHistoryStatus.PROCESSING]: 'Processing',
[CustodyOrderHistoryStatus.COMPLETED]: 'Completed',
[CustodyOrderHistoryStatus.FAILED]: 'Failed',
};

export const TransactionHistory = ({ transactions, isLoading }: TransactionHistoryProps) => {
const { translate } = useSettingsContext();

const formatAmount = (amount?: number, asset?: string): string => {
if (amount === undefined || !asset) return '-';
return `${amount.toFixed(2)} ${asset}`;
};

return isLoading ? (
<div className="w-full flex flex-col items-center justify-center gap-2 p-4">
<StyledLoadingSpinner size={SpinnerSize.LG} />
</div>
) : transactions?.length ? (
<StyledVerticalStack full gap={2}>
<h2 className="text-dfxBlue-800">{translate('screens/safe', 'Recent Activity')}</h2>
<StyledDataTable alignContent={AlignContent.BETWEEN}>
{transactions.map((tx, index) => (
<StyledDataTableRow key={index}>
<div className="w-full flex flex-row justify-between items-center gap-2 text-dfxBlue-800 p-2">
<div className="flex flex-row items-center gap-3">
<DfxIcon icon={ORDER_TYPE_ICONS[tx.type]} color={IconColor.BLUE} size={IconSize.MD} />
<div className="text-base flex flex-col font-semibold text-left leading-none gap-1">
{translate('screens/safe', ORDER_TYPE_LABELS[tx.type])}
<div className="text-sm text-dfxGray-700">{translate('screens/safe', STATUS_LABELS[tx.status])}</div>
</div>
</div>
<div className="text-base text-right flex flex-col font-semibold leading-none gap-1 pr-1">
<div className="text-dfxGray-700 text-sm">{formatAmount(tx.inputAmount, tx.inputAsset)}</div>
<div className="text-dfxBlue-800">{formatAmount(tx.outputAmount, tx.outputAsset)}</div>
</div>
</div>
</StyledDataTableRow>
))}
</StyledDataTable>
</StyledVerticalStack>
) : null;
};
18 changes: 18 additions & 0 deletions src/dto/order.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ export enum CustodyOrderStatus {
APPROVED = 'Approved',
IN_PROGRESS = 'InProgress',
COMPLETED = 'Completed',
FAILED = 'Failed',
}

export enum CustodyOrderHistoryStatus {
WAITING_FOR_PAYMENT = 'WaitingForPayment',
CHECK_PENDING = 'CheckPending',
PROCESSING = 'Processing',
COMPLETED = 'Completed',
FAILED = 'Failed',
}

export interface CustodyOrderHistory {
type: CustodyOrderType;
status: CustodyOrderHistoryStatus;
inputAmount?: number;
inputAsset?: string;
outputAmount?: number;
outputAsset?: string;
}

export enum CustodyOrderType {
Expand Down
26 changes: 25 additions & 1 deletion src/hooks/safe.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
useUserContext,
} from '@dfx.swiss/react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { CustodyOrderType, OrderPaymentInfo } from 'src/dto/order.dto';
import { CustodyOrderHistory, CustodyOrderType, OrderPaymentInfo } from 'src/dto/order.dto';
import { CustodyAsset, CustodyBalance, CustodyHistory, CustodyHistoryEntry } from 'src/dto/safe.dto';
import { downloadPdfFromString } from 'src/util/utils';
import { OrderFormData } from './order.hook';
Expand Down Expand Up @@ -44,8 +44,10 @@ export interface UseSafeResult {
isInitialized: boolean;
isLoadingPortfolio: boolean;
isLoadingHistory: boolean;
isLoadingOrderHistory: boolean;
portfolio: CustodyBalance;
history: CustodyHistoryEntry[];
orderHistory: CustodyOrderHistory[];
error?: string;
custodyAddress?: string;
custodyBlockchains?: Blockchain[];
Expand Down Expand Up @@ -89,8 +91,10 @@ export function useSafe(): UseSafeResult {
const [custodyBlockchains, setCustodyBlockchains] = useState<Blockchain[]>([]);
const [portfolio, setPortfolio] = useState<CustodyBalance>({ totalValue: { chf: 0, eur: 0, usd: 0 }, balances: [] });
const [history, setHistory] = useState<CustodyHistoryEntry[]>([]);
const [orderHistory, setOrderHistory] = useState<CustodyOrderHistory[]>([]);
const [isLoadingPortfolio, setIsLoadingPortfolio] = useState(true);
const [isLoadingHistory, setIsLoadingHistory] = useState(true);
const [isLoadingOrderHistory, setIsLoadingOrderHistory] = useState(true);
const [selectedSourceAsset, setSelectedSourceAsset] = useState<string>();

// ---- Safe Screen Initialization ----
Expand Down Expand Up @@ -137,6 +141,15 @@ export function useSafe(): UseSafeResult {
.finally(() => setIsLoadingHistory(false));
}, [user, isLoggedIn]);

useEffect(() => {
if (!user || !isLoggedIn) return;
setIsLoadingOrderHistory(true);
getOrderHistory()
.then((orders) => setOrderHistory(orders))
.catch((error: ApiError) => setError(error.message ?? 'Unknown error'))
.finally(() => setIsLoadingOrderHistory(false));
}, [user, isLoggedIn]);

// ---- Available Deposit Pairs ----

const availableCurrencies = useMemo(() => {
Expand Down Expand Up @@ -222,6 +235,13 @@ export function useSafe(): UseSafeResult {
});
}

async function getOrderHistory(): Promise<CustodyOrderHistory[]> {
return call<CustodyOrderHistory[]>({
url: `custody/order`,
method: 'GET',
});
}

async function fetchPaymentInfo(data: OrderFormData): Promise<OrderPaymentInfo> {
const order = await call<OrderPaymentInfo>({
url: 'custody/order',
Expand Down Expand Up @@ -360,8 +380,10 @@ export function useSafe(): UseSafeResult {
isInitialized,
isLoadingPortfolio,
isLoadingHistory,
isLoadingOrderHistory,
portfolio,
history,
orderHistory,
error,
custodyAddress,
custodyBlockchains,
Expand Down Expand Up @@ -391,8 +413,10 @@ export function useSafe(): UseSafeResult {
isInitialized,
isLoadingPortfolio,
isLoadingHistory,
isLoadingOrderHistory,
portfolio,
history,
orderHistory,
error,
custodyAddress,
custodyBlockchains,
Expand Down
1 change: 1 addition & 0 deletions src/screens/compliance-custody-orders.screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const statusClasses: Record<CustodyOrderStatus, string> = {
[CustodyOrderStatus.APPROVED]: 'bg-dfxBlue-300/20 text-dfxBlue-400',
[CustodyOrderStatus.IN_PROGRESS]: 'bg-dfxRedBlue-300/20 text-dfxRedBlue-200',
[CustodyOrderStatus.COMPLETED]: 'bg-dfxGreen-100/20 text-dfxGreen-300',
[CustodyOrderStatus.FAILED]: 'bg-dfxRed-100/20 text-dfxRed-100',
};

function statusBadge(status: CustodyOrderStatus): JSX.Element {
Expand Down
16 changes: 14 additions & 2 deletions src/screens/safe.screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ButtonGroup, ButtonGroupSize } from 'src/components/safe/button-group';
import { PriceChart } from 'src/components/safe/chart';
import { Portfolio } from 'src/components/safe/portfolio';
import { SafeTransactionInterface } from 'src/components/safe/safe-transaction-interface';
import { TransactionHistory } from 'src/components/safe/transaction-history';
import { useOrderUIContext } from 'src/contexts/order-ui.context';
import { useSettingsContext } from 'src/contexts/settings.context';
import { FiatCurrency, SafeOperationType } from 'src/dto/safe.dto';
Expand All @@ -38,7 +39,17 @@ interface PdfFormData {
export default function SafeScreen(): JSX.Element {
useUserGuard('/login');

const { isInitialized, portfolio, history, isLoadingPortfolio, isLoadingHistory, error, downloadPdf } = useSafe();
const {
isInitialized,
portfolio,
history,
orderHistory,
isLoadingPortfolio,
isLoadingHistory,
isLoadingOrderHistory,
error,
downloadPdf,
} = useSafe();
const { currency: userCurrency, translate } = useSettingsContext();
const {
completionType,
Expand Down Expand Up @@ -143,7 +154,7 @@ export default function SafeScreen(): JSX.Element {
<div className="shadow-card rounded-xl">
<div id="chart-timeline" className="relative">
<div className="p-2 gap-2 flex flex-col items-start">
<div className="relative w-full" style={{ height: showChart ? '350px' : '85px' }}>
<div className="relative w-full" style={{ height: showChart ? '350px' : 'auto' }}>
<div className="w-full flex flex-col gap-3 text-left leading-none z-10">
<h2 className="text-dfxBlue-800">{translate('screens/safe', 'Portfolio')}</h2>
<div className="flex flex-row justify-between items-center">
Expand Down Expand Up @@ -190,6 +201,7 @@ export default function SafeScreen(): JSX.Element {
currency={currency}
isLoading={isLoadingPortfolio}
/>
<TransactionHistory transactions={orderHistory} isLoading={isLoadingOrderHistory} />
<SafeTransactionInterface />
</StyledVerticalStack>
)}
Expand Down
14 changes: 13 additions & 1 deletion src/translations/languages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,19 @@
"Click here to confirm the withdrawal": "Klicke hier, um die Auszahlung zu bestätigen",
"Your swap will be processed and reflected in your Safe portfolio. We will inform you by email about the progress of your transactions.": "Dein Swap wird verarbeitet und in Deinem Safe-Portfolio angezeigt. Wir werden Dich per E-Mail über den Fortschritt Deiner Transaktionen informieren.",
"Download PDF": "PDF herunterladen",
"Select a date for your portfolio report": "Wähle ein Datum für Deinen Portfolio-Bericht"
"Select a date for your portfolio report": "Wähle ein Datum für Deinen Portfolio-Bericht",
"Recent Activity": "Letzte Aktivitäten",
"Fiat Deposit": "Fiat-Einzahlung",
"Fiat Withdrawal": "Fiat-Auszahlung",
"Crypto Deposit": "Krypto-Einzahlung",
"Crypto Withdrawal": "Krypto-Auszahlung",
"Saving Deposit": "Saving-Einzahlung",
"Saving Withdrawal": "Saving-Auszahlung",
"Waiting for payment": "Warten auf Zahlung",
"Check pending": "Prüfung ausstehend",
"Processing": "In Bearbeitung",
"Completed": "Abgeschlossen",
"Failed": "Fehlgeschlagen"
},
"screens/realunit": {
"Account Summary": "Kontozusammenfassung",
Expand Down
14 changes: 13 additions & 1 deletion src/translations/languages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,19 @@
"Click here to confirm the withdrawal": "Cliquez ici pour confirmer le retrait",
"Your swap will be processed and reflected in your Safe portfolio. We will inform you by email about the progress of your transactions.": "Votre swap sera traité et reflété dans votre portefeuille Safe. Nous vous informerons par e-mail de l'état d'avancement de vos transactions.",
"Download PDF": "Télécharger PDF",
"Select a date for your portfolio report": "Sélectionnez une date pour votre rapport de portefeuille"
"Select a date for your portfolio report": "Sélectionnez une date pour votre rapport de portefeuille",
"Recent Activity": "Activité récente",
"Fiat Deposit": "Dépôt fiat",
"Fiat Withdrawal": "Retrait fiat",
"Crypto Deposit": "Dépôt crypto",
"Crypto Withdrawal": "Retrait crypto",
"Saving Deposit": "Saving dépôt",
"Saving Withdrawal": "Saving retrait",
"Waiting for payment": "En attente de paiement",
"Check pending": "Vérification en attente",
"Processing": "En cours de traitement",
"Completed": "Terminé",
"Failed": "Échoué"
},
"screens/realunit": {
"Account Summary": "Résumé du compte",
Expand Down
14 changes: 13 additions & 1 deletion src/translations/languages/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,19 @@
"Click here to confirm the withdrawal": "Clicca qui per confermare il prelievo",
"Your swap will be processed and reflected in your Safe portfolio. We will inform you by email about the progress of your transactions.": "Il tuo swap verrà elaborato e riflesso nel tuo portafoglio Safe. Ti informeremo via e-mail sui progressi delle tue transazioni.",
"Download PDF": "Scarica PDF",
"Select a date for your portfolio report": "Seleziona una data per il tuo report del portafoglio"
"Select a date for your portfolio report": "Seleziona una data per il tuo report del portafoglio",
"Recent Activity": "Attività recente",
"Fiat Deposit": "Deposito fiat",
"Fiat Withdrawal": "Prelievo fiat",
"Crypto Deposit": "Deposito crypto",
"Crypto Withdrawal": "Prelievo crypto",
"Saving Deposit": "Saving deposito",
"Saving Withdrawal": "Saving prelievo",
"Waiting for payment": "In attesa di pagamento",
"Check pending": "Verifica in sospeso",
"Processing": "In elaborazione",
"Completed": "Completato",
"Failed": "Fallito"
},
"screens/realunit": {
"Account Summary": "Riepilogo del conto",
Expand Down
2 changes: 1 addition & 1 deletion src/util/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,5 +338,5 @@ export function equalsIgnoreCase(left?: string, right?: string): boolean {

export function findCustodyBalanceString(asset: CustodyAsset, balances: CustodyAssetBalance[]): string {
const balance = balances.find((b) => b.asset.name === asset.name)?.balance;
return balance !== undefined ? Utils.formatAmountCrypto(balance) : '';
return balance?.toString() ?? '';
}
Loading