make flow shut up

This commit is contained in:
zeppi 2021-07-05 00:04:05 -04:00 committed by jessopb
parent 50556bba97
commit d34ef851d0
3 changed files with 365 additions and 276 deletions

1
flow-typed/user.js vendored
View file

@ -28,6 +28,7 @@ declare type User = {
device_types: Array<DeviceType>, device_types: Array<DeviceType>,
lbry_first_approved: boolean, lbry_first_approved: boolean,
experimental_ui: boolean, experimental_ui: boolean,
fiat_enabled: boolean,
odysee_live_enabled: boolean, odysee_live_enabled: boolean,
odysee_live_disabled: boolean, odysee_live_disabled: boolean,
global_mod: boolean, global_mod: boolean,

View file

@ -36,10 +36,11 @@ type State = {
loading: boolean, loading: boolean,
content: ?string, content: ?string,
stripeConnectionUrl: string, stripeConnectionUrl: string,
alreadyUpdated: boolean, // alreadyUpdated: boolean,
accountConfirmed: boolean, accountConfirmed: boolean,
accountPendingConfirmation: boolean,
accountNotConfirmedButReceivedTips: boolean, accountNotConfirmedButReceivedTips: boolean,
unpaidBalance: string unpaidBalance: number,
}; };
class StripeAccountConnection extends React.Component<Props, State> { class StripeAccountConnection extends React.Component<Props, State> {
@ -53,22 +54,30 @@ class StripeAccountConnection extends React.Component<Props, State> {
accountPendingConfirmation: false, accountPendingConfirmation: false,
accountNotConfirmedButReceivedTips: false, accountNotConfirmedButReceivedTips: false,
unpaidBalance: 0, unpaidBalance: 0,
stripeConnectionUrl: '',
// alreadyUpdated: false,
}; };
} }
componentDidMount() { componentDidMount() {
const { user } = this.props; const { user } = this.props;
// $FlowFixMe
this.experimentalUiEnabled = user && user.experimental_ui; this.experimentalUiEnabled = user && user.experimental_ui;
var that = this; var that = this;
function getAndSetAccountLink(stillNeedToConfirmAccount) { function getAndSetAccountLink(stillNeedToConfirmAccount) {
Lbryio.call('account', 'link', { Lbryio.call(
return_url: successStripeRedirectUrl, 'account',
refresh_url: failureStripeRedirectUrl, 'link',
environment: stripeEnvironment, {
}, 'post').then(accountLinkResponse => { return_url: successStripeRedirectUrl,
refresh_url: failureStripeRedirectUrl,
environment: stripeEnvironment,
},
'post'
).then((accountLinkResponse) => {
// stripe link for user to navigate to and confirm account // stripe link for user to navigate to and confirm account
const stripeConnectionUrl = accountLinkResponse.url; const stripeConnectionUrl = accountLinkResponse.url;
@ -87,54 +96,67 @@ class StripeAccountConnection extends React.Component<Props, State> {
} }
// call the account status endpoint // call the account status endpoint
Lbryio.call('account', 'status', { Lbryio.call(
environment: stripeEnvironment, 'account',
}, 'post').then(accountStatusResponse => { 'status',
const yetToBeCashedOutBalance = accountStatusResponse.total_received_unpaid; {
if (yetToBeCashedOutBalance) { environment: stripeEnvironment,
that.setState({ },
unpaidBalance: yetToBeCashedOutBalance, 'post'
}); )
} .then((accountStatusResponse) => {
const yetToBeCashedOutBalance = accountStatusResponse.total_received_unpaid;
if (yetToBeCashedOutBalance) {
that.setState({
unpaidBalance: yetToBeCashedOutBalance,
});
}
// if charges already enabled, no need to generate an account link // if charges already enabled, no need to generate an account link
if (accountStatusResponse.charges_enabled) { if (accountStatusResponse.charges_enabled) {
// account has already been confirmed // account has already been confirmed
that.setState({ that.setState({
accountConfirmed: true, accountConfirmed: true,
}); });
// user has not confirmed an account but have received payments // user has not confirmed an account but have received payments
} else if (accountStatusResponse.total_received_unpaid > 0) { } else if (accountStatusResponse.total_received_unpaid > 0) {
that.setState({ that.setState({
accountNotConfirmedButReceivedTips: true, accountNotConfirmedButReceivedTips: true,
}); });
getAndSetAccountLink(); getAndSetAccountLink();
// user has not received any amount or confirmed an account // user has not received any amount or confirmed an account
} else { } else {
// get stripe link and set it on the frontend // get stripe link and set it on the frontend
// pass true so it updates the frontend // pass true so it updates the frontend
getAndSetAccountLink(true); getAndSetAccountLink(true);
} }
}).catch(function(error) { })
// errorString passed from the API (with a 403 error) .catch(function (error) {
const errorString = 'account not linked to user, please link first'; // errorString passed from the API (with a 403 error)
const errorString = 'account not linked to user, please link first';
// if it's beamer's error indicating the account is not linked yet // if it's beamer's error indicating the account is not linked yet
if (error.message.indexOf(errorString) > -1) { if (error.message.indexOf(errorString) > -1) {
// get stripe link and set it on the frontend // get stripe link and set it on the frontend
getAndSetAccountLink(); getAndSetAccountLink();
} else { } else {
// not an error from Beamer, throw it // not an error from Beamer, throw it
throw new Error(error); throw new Error(error);
} }
}); });
} }
render() { render() {
const { stripeConnectionUrl, accountConfirmed, accountPendingConfirmation, unpaidBalance, accountNotConfirmedButReceivedTips } = this.state; const {
stripeConnectionUrl,
accountConfirmed,
accountPendingConfirmation,
unpaidBalance,
accountNotConfirmedButReceivedTips,
} = this.state;
const { user } = this.props; const { user } = this.props;
@ -146,79 +168,85 @@ class StripeAccountConnection extends React.Component<Props, State> {
body={ body={
<div> <div>
{/* show while waiting for account status */} {/* show while waiting for account status */}
{!accountConfirmed && !accountPendingConfirmation && !accountNotConfirmedButReceivedTips && {!accountConfirmed && !accountPendingConfirmation && !accountNotConfirmedButReceivedTips && (
<div className="card__body-actions"> <div className="card__body-actions">
<div>
<div> <div>
<h3>Getting your Bank Account Connection status...</h3>
</div>
</div>
</div>
}
{/* user has yet to complete their integration */}
{!accountConfirmed && accountPendingConfirmation &&
<div className="card__body-actions">
<div>
<div>
<h3>Connect your Bank Account to Odysee to receive donations directly from users</h3>
</div>
<div className="section__actions">
<a href={stripeConnectionUrl}>
<Button
button="secondary"
label={__('Connect Your Bank Account')}
icon={ICONS.FINANCE}
/>
</a>
</div>
</div>
</div>
}
{ /* user has completed their integration */ }
{accountConfirmed &&
<div className="card__body-actions">
<div>
<div>
<h3>Congratulations! Your account has been connected with Odysee.</h3>
{unpaidBalance > 0 ? <div><br />
<h3>Your account balance is ${unpaidBalance / 100} USD. Functionality to view your transactions and withdraw your balance will be landing shortly.</h3>
</div> : <div><br />
<h3>Your account balance is $0 USD. When you receive a tip you will see it here.</h3>
</div>}
</div>
</div>
</div>
}
{ accountNotConfirmedButReceivedTips &&
<div className="card__body-actions">
<div>
<div>
<h3>Congratulations, you have already begun receiving tips on Odysee!</h3>
<div><br />
<h3>Your pending account balance is ${unpaidBalance / 100} USD. Functionality to view and receive your transactions will land soon.</h3>
</div><br />
<div> <div>
<h3>Connect your Bank Account to be able to cash your pending balance out to your account.</h3> <h3>Getting your Bank Account Connection status...</h3>
</div>
</div>
</div>
)}
{/* user has yet to complete their integration */}
{!accountConfirmed && accountPendingConfirmation && (
<div className="card__body-actions">
<div>
<div>
<h3>Connect your Bank Account to Odysee to receive donations directly from users</h3>
</div> </div>
<div className="section__actions"> <div className="section__actions">
<a href={stripeConnectionUrl}> <a href={stripeConnectionUrl}>
<Button <Button button="secondary" label={__('Connect Your Bank Account')} icon={ICONS.FINANCE} />
button="secondary"
label={__('Connect Your Bank Account')}
icon={ICONS.FINANCE}
/>
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div> )}
} {/* user has completed their integration */}
{accountConfirmed && (
<div className="card__body-actions">
<div>
<div>
<h3>Congratulations! Your account has been connected with Odysee.</h3>
{unpaidBalance > 0 ? (
<div>
<br />
<h3>
Your account balance is ${unpaidBalance / 100} USD. Functionality to view your transactions
and withdraw your balance will be landing shortly.
</h3>
</div>
) : (
<div>
<br />
<h3>Your account balance is $0 USD. When you receive a tip you will see it here.</h3>
</div>
)}
</div>
</div>
</div>
)}
{accountNotConfirmedButReceivedTips && (
<div className="card__body-actions">
<div>
<div>
<h3>Congratulations, you have already begun receiving tips on Odysee!</h3>
<div>
<br />
<h3>
Your pending account balance is ${unpaidBalance / 100} USD. Functionality to view and receive
your transactions will land soon.
</h3>
</div>
<br />
<div>
<h3>Connect your Bank Account to be able to cash your pending balance out to your account.</h3>
</div>
<div className="section__actions">
<a href={stripeConnectionUrl}>
<Button button="secondary" label={__('Connect Your Bank Account')} icon={ICONS.FINANCE} />
</a>
</div>
</div>
</div>
</div>
)}
</div> </div>
} }
/> />
); );
} else { } else {
return (<></>); // probably null; return <></>; // probably null;
} }
} }
} }

View file

@ -1,4 +1,4 @@
// @flow // restore flow
/* eslint-disable no-undef */ /* eslint-disable no-undef */
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import React from 'react'; import React from 'react';
@ -19,14 +19,25 @@ if (STRIPE_PUBLIC_KEY.indexOf('pk_live') > -1) {
stripeEnvironment = 'live'; stripeEnvironment = 'live';
} }
type Props = { // type Props = {
disabled: boolean, // disabled: boolean,
label: ?string, // label: ?string,
email: ?string, // email: ?string,
} // scriptFailedToLoad: boolean,
// };
//
// type State = {
// open: boolean,
// currentFlowStage: string,
// customerTransactions: Array<any>,
// pageTitle: string,
// userCardDetails: any, // fill this out
// scriptFailedToLoad: boolean,
// };
class CardVerify extends React.Component<Props, State> { class CardVerify extends React.Component<Props, State> {
constructor(props: Props) { constructor(props) {
// :Props
super(props); super(props);
this.state = { this.state = {
open: false, open: false,
@ -56,96 +67,118 @@ class CardVerify extends React.Component<Props, State> {
// setting a timeout to let the client secret populate // setting a timeout to let the client secret populate
// TODO: fix this, should be a cleaner way // TODO: fix this, should be a cleaner way
setTimeout(function() { setTimeout(function () {
// check if customer has card setup already // check if customer has card setup already
Lbryio.call('customer', 'status', { Lbryio.call(
environment: stripeEnvironment, 'customer',
}, 'post').then(customerStatusResponse => { 'status',
// user has a card saved if their defaultPaymentMethod has an id {
const defaultPaymentMethod = customerStatusResponse.Customer.invoice_settings.default_payment_method; environment: stripeEnvironment,
var userHasAlreadySetupPayment = Boolean(defaultPaymentMethod && defaultPaymentMethod.id); },
'post'
)
.then((customerStatusResponse) => {
// user has a card saved if their defaultPaymentMethod has an id
const defaultPaymentMethod = customerStatusResponse.Customer.invoice_settings.default_payment_method;
var userHasAlreadySetupPayment = Boolean(defaultPaymentMethod && defaultPaymentMethod.id);
// show different frontend if user already has card // show different frontend if user already has card
if (userHasAlreadySetupPayment) { if (userHasAlreadySetupPayment) {
var card = customerStatusResponse.PaymentMethods[0].card; var card = customerStatusResponse.PaymentMethods[0].card;
var cardDetails = { var cardDetails = {
brand: card.brand, brand: card.brand,
expiryYear: card.exp_year, expiryYear: card.exp_year,
expiryMonth: card.exp_month, expiryMonth: card.exp_month,
lastFour: card.last4, lastFour: card.last4,
}; };
that.setState({
currentFlowStage: 'cardConfirmed',
pageTitle: 'Tip History',
userCardDetails: cardDetails,
});
// get customer transactions
Lbryio.call('customer', 'list', {
environment: stripeEnvironment,
}, 'post').then(customerTransactionsResponse => {
that.setState({ that.setState({
customerTransactions: customerTransactionsResponse, currentFlowStage: 'cardConfirmed',
pageTitle: 'Tip History',
userCardDetails: cardDetails,
}); });
console.log(customerTransactionsResponse); // get customer transactions
}); Lbryio.call(
'customer',
'list',
{
environment: stripeEnvironment,
},
'post'
).then((customerTransactionsResponse) => {
that.setState({
customerTransactions: customerTransactionsResponse,
});
// otherwise, prompt them to save a card console.log(customerTransactionsResponse);
} else { });
that.setState({
currentFlowStage: 'confirmingCard',
});
// get a payment method secret for frontend // otherwise, prompt them to save a card
Lbryio.call('customer', 'setup', { } else {
environment: stripeEnvironment, that.setState({
}, 'post').then(customerSetupResponse => { currentFlowStage: 'confirmingCard',
console.log(customerSetupResponse); });
clientSecret = customerSetupResponse.client_secret; // get a payment method secret for frontend
Lbryio.call(
'customer',
'setup',
{
environment: stripeEnvironment,
},
'post'
).then((customerSetupResponse) => {
console.log(customerSetupResponse);
// instantiate stripe elements clientSecret = customerSetupResponse.client_secret;
setupStripe();
});
}
// if the status call fails, either an actual error or need to run setup first
}).catch(function(error) {
console.log(error);
// errorString passed from the API (with a 403 error) // instantiate stripe elements
const errorString = 'user as customer is not setup yet'; setupStripe();
});
}
// if the status call fails, either an actual error or need to run setup first
})
.catch(function (error) {
console.log(error);
// if it's beamer's error indicating the account is not linked yet // errorString passed from the API (with a 403 error)
if (error.message.indexOf(errorString) > -1) { const errorString = 'user as customer is not setup yet';
// send them to save a card
that.setState({
currentFlowStage: 'confirmingCard',
});
// get a payment method secret for frontend // if it's beamer's error indicating the account is not linked yet
Lbryio.call('customer', 'setup', { if (error.message.indexOf(errorString) > -1) {
environment: stripeEnvironment, // send them to save a card
}, 'post').then(customerSetupResponse => { that.setState({
console.log(customerSetupResponse); currentFlowStage: 'confirmingCard',
});
clientSecret = customerSetupResponse.client_secret; // get a payment method secret for frontend
Lbryio.call(
'customer',
'setup',
{
environment: stripeEnvironment,
},
'post'
).then((customerSetupResponse) => {
console.log(customerSetupResponse);
// instantiate stripe elements clientSecret = customerSetupResponse.client_secret;
setupStripe();
}); // instantiate stripe elements
} else { setupStripe();
console.log('Unseen before error'); });
} } else {
}); console.log('Unseen before error');
}
});
}, 250); }, 250);
function setupStripe() { function setupStripe() {
// TODO: have to fix this, using so that the script is available // TODO: have to fix this, using so that the script is available
setTimeout(function() { setTimeout(function () {
var stripeElements = function(publicKey, setupIntent) { var stripeElements = function (publicKey, setupIntent) {
var stripe = Stripe(publicKey); var stripe = Stripe(publicKey);
var elements = stripe.elements(); var elements = stripe.elements();
@ -154,8 +187,7 @@ class CardVerify extends React.Component<Props, State> {
base: { base: {
fontSize: '16px', fontSize: '16px',
color: '#32325d', color: '#32325d',
fontFamily: fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
'-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
fontSmoothing: 'antialiased', fontSmoothing: 'antialiased',
'::placeholder': { '::placeholder': {
color: 'rgba(0,0,0,0.4)', color: 'rgba(0,0,0,0.4)',
@ -168,17 +200,17 @@ class CardVerify extends React.Component<Props, State> {
card.mount('#card-element'); card.mount('#card-element');
// Element focus ring // Element focus ring
card.on('focus', function() { card.on('focus', function () {
var el = document.getElementById('card-element'); var el = document.getElementById('card-element');
el.classList.add('focused'); el.classList.add('focused');
}); });
card.on('blur', function() { card.on('blur', function () {
var el = document.getElementById('card-element'); var el = document.getElementById('card-element');
el.classList.remove('focused'); el.classList.remove('focused');
}); });
card.on('ready', function() { card.on('ready', function () {
card.focus(); card.focus();
}); });
@ -198,29 +230,31 @@ class CardVerify extends React.Component<Props, State> {
changeLoadingState(true); changeLoadingState(true);
stripe.confirmCardSetup(clientSecret, { stripe
payment_method: { .confirmCardSetup(clientSecret, {
card: card, payment_method: {
billing_details: { email: email }, card: card,
}, billing_details: { email: email },
}).then(function(result) { },
if (result.error) { })
console.log(result); .then(function (result) {
if (result.error) {
console.log(result);
changeLoadingState(false); changeLoadingState(false);
var displayError = document.getElementById('card-errors'); var displayError = document.getElementById('card-errors');
displayError.textContent = result.error.message; displayError.textContent = result.error.message;
} else { } else {
// The PaymentMethod was successfully set up // The PaymentMethod was successfully set up
// hide and show the proper divs // hide and show the proper divs
orderComplete(stripe, clientSecret); orderComplete(stripe, clientSecret);
} }
}); });
} }
// Handle payment submission when user clicks the pay button. // Handle payment submission when user clicks the pay button.
var button = document.getElementById('submit'); var button = document.getElementById('submit');
button.addEventListener('click', function(event) { button.addEventListener('click', function (event) {
submitForm(event); submitForm(event);
}); });
@ -237,24 +271,35 @@ class CardVerify extends React.Component<Props, State> {
stripeElements(publicKey, clientSecret); stripeElements(publicKey, clientSecret);
// Show a spinner on payment submission // Show a spinner on payment submission
var changeLoadingState = function(isLoading) { var changeLoadingState = function (isLoading) {
if (isLoading) { if (isLoading) {
// $FlowFixMe
document.querySelector('button').disabled = true; document.querySelector('button').disabled = true;
// $FlowFixMe
document.querySelector('#spinner').classList.remove('hidden'); document.querySelector('#spinner').classList.remove('hidden');
// $FlowFixMe
document.querySelector('#button-text').classList.add('hidden'); document.querySelector('#button-text').classList.add('hidden');
} else { } else {
// $FlowFixMe
document.querySelector('button').disabled = false; document.querySelector('button').disabled = false;
// $FlowFixMe
document.querySelector('#spinner').classList.add('hidden'); document.querySelector('#spinner').classList.add('hidden');
// $FlowFixMe
document.querySelector('#button-text').classList.remove('hidden'); document.querySelector('#button-text').classList.remove('hidden');
} }
}; };
// shows a success / error message when the payment is complete // shows a success / error message when the payment is complete
var orderComplete = function(stripe, clientSecret) { var orderComplete = function (stripe, clientSecret) {
stripe.retrieveSetupIntent(clientSecret).then(function(result) { stripe.retrieveSetupIntent(clientSecret).then(function (result) {
Lbryio.call('customer', 'status', { Lbryio.call(
environment: stripeEnvironment, 'customer',
}, 'post').then(customerStatusResponse => { 'status',
{
environment: stripeEnvironment,
},
'post'
).then((customerStatusResponse) => {
var card = customerStatusResponse.PaymentMethods[0].card; var card = customerStatusResponse.PaymentMethods[0].card;
var cardDetails = { var cardDetails = {
@ -287,10 +332,16 @@ class CardVerify extends React.Component<Props, State> {
} }
componentWillUnmount() { componentWillUnmount() {
// pretty sure this doesn't exist
// $FlowFixMe
if (this.loadPromise) { if (this.loadPromise) {
// $FlowFixMe
this.loadPromise.reject(); this.loadPromise.reject();
} }
// pretty sure this doesn't exist
// $FlowFixMe
if (CardVerify.stripeHandler && this.state.open) { if (CardVerify.stripeHandler && this.state.open) {
// $FlowFixMe
CardVerify.stripeHandler.close(); CardVerify.stripeHandler.close();
} }
} }
@ -329,7 +380,6 @@ class CardVerify extends React.Component<Props, State> {
const { currentFlowStage, customerTransactions, pageTitle, userCardDetails } = this.state; const { currentFlowStage, customerTransactions, pageTitle, userCardDetails } = this.state;
return ( return (
<Page backout={{ title: pageTitle, backLabel: __('Done') }} noFooter noSideNavigation> <Page backout={{ title: pageTitle, backLabel: __('Done') }} noFooter noSideNavigation>
<div> <div>
{scriptFailedToLoad && ( {scriptFailedToLoad && (
@ -337,76 +387,86 @@ class CardVerify extends React.Component<Props, State> {
)} )}
</div> </div>
{currentFlowStage === 'loading' && <div className="headerCard toConfirmCard"> {currentFlowStage === 'loading' && (
<Card <div className="headerCard toConfirmCard">
title={__('Connect your card with Odysee')} <Card title={__('Connect your card with Odysee')} subtitle={__('Getting your card connection status...')} />
subtitle={__('Getting your card connection status...')} </div>
/> )}
</div>}
{currentFlowStage === 'confirmingCard' && <div className="sr-root"> {currentFlowStage === 'confirmingCard' && (
<div className="sr-main"> <div className="sr-root">
<div className="sr-payment-form card cardInput"> <div className="sr-main">
<div className="sr-form-row"> <div className="sr-payment-form card cardInput">
<label className="payment-details"> <div className="sr-form-row">
Card Details <label className="payment-details">Card Details</label>
</label> <div className="sr-input sr-element sr-card-element" id="card-element" />
<div className="sr-input sr-element sr-card-element" id="card-element" /> </div>
<div className="sr-field-error" id="card-errors" role="alert" />
<button className="linkButton" id="submit">
<div className="spinner hidden" id="spinner" />
<span id="button-text">Add Card</span>
</button>
</div> </div>
<div className="sr-field-error" id="card-errors" role="alert" />
<button className="linkButton" id="submit">
<div className="spinner hidden" id="spinner" />
<span id="button-text">Add Card</span>
</button>
</div> </div>
</div> </div>
</div>} )}
{currentFlowStage === 'cardConfirmed' && <div className="successCard"> {currentFlowStage === 'cardConfirmed' && (
<Card <div className="successCard">
title={__('Card Details')} <Card
body={<> title={__('Card Details')}
<h4 className="grey-text">Brand: {userCardDetails.brand.toUpperCase()} &nbsp; body={
Last 4: {userCardDetails.lastFour} &nbsp; <>
Expires: {userCardDetails.expiryMonth}/{userCardDetails.expiryYear} &nbsp; <h4 className="grey-text">
</h4> Brand: {userCardDetails.brand.toUpperCase()} &nbsp; Last 4: {userCardDetails.lastFour} &nbsp;
</>} Expires: {userCardDetails.expiryMonth}/{userCardDetails.expiryYear} &nbsp;
/> </h4>
<br /> </>
}
/>
<br />
{(!customerTransactions || customerTransactions.length === 0) && <Card {(!customerTransactions || customerTransactions.length === 0) && (
title={__('Tip History')} <Card
subtitle={__('You have not sent any tips yet. When you do they will appear here. ')} title={__('Tip History')}
/>} subtitle={__('You have not sent any tips yet. When you do they will appear here. ')}
/>
{customerTransactions && customerTransactions.length > 0 && <Card )}
title={__('Tip History')}
body={<><div className="table__wrapper">
<table className="table table--transactions">
<thead>
<tr>
<th className="date-header">{__('Date')}</th>
<th>{<>{__('Receiving Channel Name')}</>}</th>
<th>{__('Amount (USD)')} </th>
<th>{__('Anonymous')}</th>
</tr>
</thead>
<tbody>
{customerTransactions &&
customerTransactions.map((transaction) => (
<tr key={transaction.name + transaction.created_at}>
<td>{moment(transaction.created_at).format('LLL')}</td>
<td>{transaction.channel_name}</td>
<td>${transaction.tipped_amount / 100}</td>
<td>{transaction.private_tip ? 'Yes' : 'No'}</td>
</tr>
))}
</tbody>
</table>
</div></>}
/>}
</div>}
{customerTransactions && customerTransactions.length > 0 && (
<Card
title={__('Tip History')}
body={
<>
<div className="table__wrapper">
<table className="table table--transactions">
<thead>
<tr>
<th className="date-header">{__('Date')}</th>
<th>{<>{__('Receiving Channel Name')}</>}</th>
<th>{__('Amount (USD)')} </th>
<th>{__('Anonymous')}</th>
</tr>
</thead>
<tbody>
{customerTransactions &&
customerTransactions.map((transaction) => (
<tr key={transaction.name + transaction.created_at}>
<td>{moment(transaction.created_at).format('LLL')}</td>
<td>{transaction.channel_name}</td>
<td>${transaction.tipped_amount / 100}</td>
<td>{transaction.private_tip ? 'Yes' : 'No'}</td>
</tr>
))}
</tbody>
</table>
</div>
</>
}
/>
)}
</div>
)}
</Page> </Page>
); );
} }