Skip to content

Commit

Permalink
show status, estimated arrival time and help message
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewliu08 committed Sep 3, 2024
1 parent affcdba commit 3c95ded
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 33 deletions.
26 changes: 24 additions & 2 deletions apps/daimo-mobile/src/i18n/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,12 @@ export const en = {
cancelledLink: () => `Cancelled link`,
sent: () => `Sent`,
received: () => `Received`,
deposited: () => `Deposited`,
withdrew: () => `Withdrew`,
},
whyNoFees: {
help: {
title: () => `About this transfer`,
description: {
whyNoFees: {
firstPara: (chainName: string) =>
`This transaction settled on ${chainName}, an Ethereum rollup.`,
firstPara2Chain: (chainA: string, chainB: string) =>
Expand All @@ -69,12 +71,32 @@ export const en = {
thirdPara: () =>
`Transactions cost a few cents. Daimo sponsored this transfer, making it free.`,
},
landlineDeposit: {
firstPara: () =>
"This transaction transfers funds from your connected bank account to your Daimo account.",
secondPara: () =>
"Once the funds are received by our partner, we will make an on-chain transfer to deposit the funds to your Daimo account.",
thirdPara: () =>
"Bank transfers normally cost a few dollars. Daimo sponsored this transfer, making it free.",
},
landlineWithdrawal: {
firstPara: () =>
"This transaction transfers funds from your Daimo account to your connected bank account.",
secondPara: () =>
"The funds are transferred on-chain to our partner's address. Upon receiving the funds, we initiate a bank transfer to your bank account.",
thirdPara: () =>
"Bank transfers normally cost a few dollars. Daimo sponsored this transfer, making it free.",
},
},
feeText: {
free: () => `FREE`,
pending: () => `PENDING`,
fee: (amount: string) => `${amount} FEE`,
},
fundArrivalTime: {
deposit: () => `Your funds will arrive to your Daimo account`,
withdrawal: () => `Your funds will arrive to your bank account`,
},
},

// ------------ KEYROTATION ------------
Expand Down
26 changes: 24 additions & 2 deletions apps/daimo-mobile/src/i18n/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ export const es: LanguageDefinition = {
cancelledLink: () => `Link cancelado`,
sent: () => `Enviado`,
received: () => `Recibida`,
deposited: () => `Depositado`,
withdrew: () => `Retirado`,
},
whyNoFees: {
help: {
title: () => `Sobre esta transferencia`,
description: {
whyNoFees: {
firstPara: (chainName: string) =>
`Esta transacción fue resuelta en ${chainName}, un rollup de Ethereum.`,
firstPara2Chain: (chainA: string, chainB: string) =>
Expand All @@ -71,12 +73,32 @@ export const es: LanguageDefinition = {
thirdPara: () =>
`Las transacciones cuestan unos centimos. Daimo patrocinó esta transferencia, haciéndola gratuita.`,
},
landlineDeposit: {
firstPara: () =>
"Esta transacción transfiere fondos desde tu cuenta bancaria vinculada a tu cuenta Daimo.",
secondPara: () =>
"Una vez que nuestro socio reciba los fondos, realizaremos una transferencia en cadena para depositar los fondos en tu cuenta Daimo.",
thirdPara: () =>
"Las transferencias bancarias normalmente cuestan unos dólares. Daimo ha patrocinado esta transferencia, haciéndola gratuita.",
},
landlineWithdrawal: {
firstPara: () =>
"Esta transacción transfiere fondos desde tu cuenta Daimo a tu cuenta bancaria vinculada.",
secondPara: () =>
"Los fondos se transfieren en cadena a la dirección de nuestro socio. Una vez recibidos los fondos, iniciamos una transferencia bancaria a tu cuenta bancaria.",
thirdPara: () =>
"Las transferencias bancarias normalmente cuestan unos dólares. Daimo patrocinó esta transferencia, haciéndola gratuita.",
},
},
feeText: {
free: () => `GRATIS`,
pending: () => `PENDIENTE`,
fee: (amount: string) => `${amount} TASA`,
},
fundArrivalTime: {
deposit: () => `Sus fondos llegarán a su cuenta Daimo`,
withdrawal: () => `Sus fondos llegarán a su cuenta bancaria`,
},
},

// ------------ KEYROTATION ------------
Expand Down
2 changes: 1 addition & 1 deletion apps/daimo-mobile/src/view/screen/SettingsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ import {
import { FarcasterButton } from "../shared/FarcasterBubble";
import { Icon } from "../shared/Icon";
import { ClockIcon, PlusIcon } from "../shared/Icons";
import { PendingDot } from "../shared/PendingDot";
import { ScreenHeader } from "../shared/ScreenHeader";
import Spacer from "../shared/Spacer";
import { PendingDot } from "../shared/StatusDot";
import { openSupportTG } from "../shared/error";
import { color, ss, touchHighlightUnderlay } from "../shared/style";
import {
Expand Down
5 changes: 3 additions & 2 deletions apps/daimo-mobile/src/view/screen/history/HistoryList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import { navToAccountPage, useNav } from "../../../common/nav";
import { env } from "../../../env";
import { i18NLocale, i18n } from "../../../i18n";
import {
canSendToContact,
DaimoContact,
EAccountContact,
canSendToContact,
eAccToContact,
getContactName,
landlineAccountToContact,
Expand All @@ -39,8 +39,8 @@ import { getCachedLandlineAccount } from "../../../logic/landlineAccountCache";
import { Account } from "../../../storage/account";
import { getAmountText } from "../../shared/Amount";
import { ContactBubble } from "../../shared/Bubble";
import { PendingDot, ProcessingDot } from "../../shared/PendingDot";
import Spacer from "../../shared/Spacer";
import { FailedDot, PendingDot, ProcessingDot } from "../../shared/StatusDot";
import { color, ss, touchHighlightUnderlay } from "../../shared/style";
import {
DaimoText,
Expand Down Expand Up @@ -310,6 +310,7 @@ function TransferClogRow({
</View>
{transferClogStatus === "pending" && <PendingDot />}
{transferClogStatus === "processing" && <ProcessingDot />}
{transferClogStatus === "failed" && <FailedDot />}
</View>
<TransferAmountDate
amount={amountDelta}
Expand Down
166 changes: 142 additions & 24 deletions apps/daimo-mobile/src/view/screen/history/HistoryOpBottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
TransferSwapClog,
EAccount,
getTransferClogStatus,
assert,
daysUntil,
} from "@daimo/common";
import { ChainConfig, daimoChainFromId } from "@daimo/contract";
import Octicons from "@expo/vector-icons/Octicons";
Expand Down Expand Up @@ -50,11 +52,13 @@ import { ButtonBig } from "../../shared/Button";
import { CenterSpinner } from "../../shared/CenterSpinner";
import { ScreenHeader } from "../../shared/ScreenHeader";
import Spacer from "../../shared/Spacer";
import { FailedDot, PendingDot, ProcessingDot } from "../../shared/StatusDot";
import { color, ss } from "../../shared/style";
import {
TextBodyCaps,
TextCenter,
TextError,
TextH3,
TextPara,
} from "../../shared/text";
import { useWithAccount } from "../../shared/withAccount";
Expand Down Expand Up @@ -107,6 +111,18 @@ function HistoryOpInner({ account, route }: Props & { account: Account }) {
account.sentPaymentLinks.find((p) => p.id === op.noteStatus.id);
const shareLinkAgain = sentPaymentLink && (() => shareURL(sentPaymentLink));

const showOffchainOpArrivalTime =
op.type === "transfer" &&
op.offchainTransfer &&
op.offchainTransfer.status === "processing" &&
op.offchainTransfer.timeExpected;
const showOffchainOpStatus =
op.type === "transfer" &&
op.offchainTransfer &&
op.offchainTransfer.status === "failed" &&
op.offchainTransfer.statusMessage;
const showLinkToExplorer = op.txHash && !shareLinkAgain;

return (
<View style={ss.container.padH16}>
<ScreenHeader
Expand All @@ -117,9 +133,9 @@ function HistoryOpInner({ account, route }: Props & { account: Account }) {
<TransferBody account={account} transferClog={op} />
<Spacer h={36} />
<View style={ss.container.padH16}>
{op.txHash && !shareLinkAgain && (
<LinkToExplorer {...{ chainConfig }} op={op} />
)}
{showOffchainOpArrivalTime && <OffchainOpArrivalTime op={op} />}
{showOffchainOpStatus && <OffchainOpStatus op={op} />}
{showLinkToExplorer && <LinkToExplorer {...{ chainConfig }} op={op} />}
{shareLinkAgain && (
<ButtonBig
type="subtle"
Expand Down Expand Up @@ -193,6 +209,62 @@ function LinkToExplorer({
);
}

function OffchainOpArrivalTime({ op }: { op: TransferSwapClog }) {
assert(op.offchainTransfer != null);
assert(op.offchainTransfer.timeExpected != null);

const arrivalTime = op.offchainTransfer.timeExpected;
const arrivalTimeString = daysUntil(arrivalTime, i18NLocale, undefined, true);
const text =
op.offchainTransfer.transferType === "deposit"
? i18.fundArrivalTime.deposit()
: i18.fundArrivalTime.withdrawal();
return (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 12,
paddingHorizontal: 8,
paddingBottom: 16,
}}
>
<ProcessingDot />
<TextH3 style={{ marginLeft: 8 }}>
{text} {arrivalTimeString}
</TextH3>
</View>
);
}

function OffchainOpStatus({ op }: { op: TransferSwapClog }) {
assert(op.offchainTransfer != null);
if (!op.offchainTransfer.statusMessage) {
return null;
}

const transferClogStatus = getTransferClogStatus(op);

return (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 12,
paddingHorizontal: 8,
paddingBottom: 16,
}}
>
{transferClogStatus === "pending" && <PendingDot />}
{transferClogStatus === "processing" && <ProcessingDot />}
{transferClogStatus === "failed" && <FailedDot />}
<TextH3 style={{ marginLeft: 8 }}>
{op.offchainTransfer.statusMessage}
</TextH3>
</View>
);
}

function TransferBody({
account,
transferClog,
Expand Down Expand Up @@ -242,11 +314,19 @@ function TransferBody({

// Help button to explain fees, chain, etc
const dispatcher = useContext(DispatcherContext);
const onShowHelp = useCallback(
() =>
showHelpWhyNoFees(dispatcher, chainConfig.chainL2.name, foreignChainName),
[]
);
const onShowHelp = useCallback(() => {
let transferType: string = getTransferClogType(transferClog);
if (transferType === "landline") {
transferType = (transferClog as TransferSwapClog).offchainTransfer!
.transferType;
}
showHelpWhyNoFees(
dispatcher,
transferType,
chainConfig.chainL2.name,
foreignChainName
);
}, [transferClog]);

// Generate subtitle = fees, chain, other details
const col = color.grayMid;
Expand All @@ -255,7 +335,9 @@ function TransferBody({
<React.Fragment key="chain">{chainName}</React.Fragment>,
<React.Fragment key="fees">
<TextBodyCaps color={col}>
{getFeeText(transferClog.feeAmount)}
{transferClog.status === "pending"
? i18.feeText.pending()
: getFeeText(transferClog.feeAmount)}
</TextBodyCaps>
<Spacer w={8} />
<Octicons size={16} name="info" color={col} />
Expand Down Expand Up @@ -315,9 +397,11 @@ function TransferBody({
}

function getOpVerb(op: TransferClog, accountAddress: Address) {
const transferType = getTransferClogType(op);
const isPayLink = op.type === "createLink" || op.type === "claimLink";
const sentByUs = op.from === accountAddress;
const isRequestResponse = op.type === "transfer" && op.requestStatus != null;
const isLandline = transferType === "landline";

if (isPayLink) {
if (sentByUs) return i18.opVerb.createdLink();
Expand All @@ -327,39 +411,73 @@ function getOpVerb(op: TransferClog, accountAddress: Address) {
return sentByUs
? i18.opVerb.fulfilledRequest()
: i18.opVerb.receivedRequest();
} else if (isLandline) {
const landlineTransferType = (op as TransferSwapClog).offchainTransfer!
.transferType;
return landlineTransferType === "deposit"
? i18.opVerb.deposited()
: i18.opVerb.withdrew();
} else {
return sentByUs ? i18.opVerb.sent() : i18.opVerb.received();
}
}

function showHelpWhyNoFees(
dispatcher: Dispatcher,
transferType: string,
chainName: string,
foreignChainName?: string
) {
const i1 = i18.whyNoFees;
const i1 = i18.help;

const content = () => {
if (transferType === "deposit") {
return (
<View style={ss.container.padH8}>
<TextPara>{i1.landlineDeposit.firstPara()}</TextPara>
<Spacer h={24} />
<TextPara>{i1.landlineDeposit.secondPara()}</TextPara>
<Spacer h={24} />
<TextPara>{i1.landlineDeposit.thirdPara()}</TextPara>
</View>
);
} else if (transferType === "withdrawal") {
return (
<View style={ss.container.padH8}>
<TextPara>{i1.landlineWithdrawal.firstPara()}</TextPara>
<Spacer h={24} />
<TextPara>{i1.landlineWithdrawal.secondPara()}</TextPara>
<Spacer h={24} />
<TextPara>{i1.landlineWithdrawal.thirdPara()}</TextPara>
</View>
);
} else {
return (
<View style={ss.container.padH8}>
<TextPara>
{foreignChainName
? i1.whyNoFees.firstPara2Chain(chainName, foreignChainName)
: i1.whyNoFees.firstPara(chainName)}
</TextPara>
<Spacer h={24} />
<TextPara>{i1.whyNoFees.secondPara()}</TextPara>
<Spacer h={24} />
<TextPara>{i1.whyNoFees.thirdPara()}</TextPara>
</View>
);
}
};

dispatcher.dispatch({
name: "helpModal",
title: i1.title(),
content: (
<View style={ss.container.padH8}>
<TextPara>
{foreignChainName
? i1.description.firstPara2Chain(chainName, foreignChainName)
: i1.description.firstPara(chainName)}
</TextPara>
<Spacer h={24} />
<TextPara>{i1.description.secondPara()}</TextPara>
<Spacer h={24} />
<TextPara>{i1.description.thirdPara()}</TextPara>
</View>
),
content: content(),
});
}

function getFeeText(amount?: number) {
if (amount == null) {
return i18.feeText.pending();
return i18.feeText.free();
}

let feeStr = "$" + amountToDollars(amount);
Expand Down
Loading

0 comments on commit 3c95ded

Please sign in to comment.