fix frontend bug
show superchats in order properly scroll properly when switching tabs calculate fiat tips properly sum up lbc amounts refactor code a bit remove why isnt this working bit bugfix cant tip fiat if no lbc balance add toast when someone does a tip for a comment add error toast for card page show error on account connection page automatically truncate to two decimals close to working perfectly show decimals value better increase size of input value one bug left but almost working perfectly reverse so newest transactions come first fixing bug caused by floating point precision eslint fixes remove unused conditional get stuff ready for merge bugfix and cleanup requested changes fixing flow errors fix last flow error and touchups fiat and lbc tabs coming along support setting currency as the default tab via query param add wallet fiat balance fixing naming add fiat transactions using es6 to populate data should be fine but keeps crashing transaction listing working add no transactions thing about to add a third tab add third tab add card last 4 to transaction history some renaming show payments successfully show filler for subscriptions display if no transactions or subs working but in the wrong component approaching something thats working showing total tipped amount about to add last couple features cleanup More touchups adding last features calculate the total amount of unique creators tipped couple touchups remove transaction listings from settings add view transactions buttons small optimization add subscriptions section fix lot of linting errors and make command more userful
This commit is contained in:
parent
ff9ca662f2
commit
6532efdfad
12 changed files with 919 additions and 155 deletions
|
@ -38,7 +38,7 @@
|
|||
"build:dir": "yarn build -- --dir -c.compression=store -c.mac.identity=null",
|
||||
"crossenv": "./node_modules/cross-env/dist/bin/cross-env",
|
||||
"lint": "eslint 'ui/**/*.{js,jsx}' && eslint 'web/**/*.{js,jsx}' && eslint 'electron/**/*.js' && flow",
|
||||
"lint-fix": "eslint --fix 'ui/**/*.{js,jsx}' && eslint --fix 'web/**/*.{js,jsx}' && eslint --fix 'electron/**/*.js' && flow",
|
||||
"lint-fix": "eslint --fix --quiet 'ui/**/*.{js,jsx}' && eslint --fix --quiet 'web/**/*.{js,jsx}' && eslint --fix --quiet 'electron/**/*.js'",
|
||||
"format": "prettier 'src/**/*.{js,jsx,scss,json}' --write",
|
||||
"flow-defs": "flow-typed install",
|
||||
"precommit": "lint-staged",
|
||||
|
|
40
ui/component/walletFiatAccountHistory/index.js
Normal file
40
ui/component/walletFiatAccountHistory/index.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectBalance,
|
||||
selectClaimsBalance,
|
||||
selectSupportsBalance,
|
||||
selectTipsBalance,
|
||||
selectIsFetchingUtxoCounts,
|
||||
selectUtxoCounts,
|
||||
doFetchUtxoCounts,
|
||||
doUtxoConsolidate,
|
||||
selectIsConsolidatingUtxos,
|
||||
selectIsMassClaimingTips,
|
||||
selectPendingConsolidateTxid,
|
||||
selectPendingMassClaimTxid,
|
||||
} from 'lbry-redux';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { selectSyncHash } from 'redux/selectors/sync';
|
||||
import { selectClaimedRewards } from 'redux/selectors/rewards';
|
||||
import WalletBalance from './view';
|
||||
|
||||
const select = state => ({
|
||||
balance: selectBalance(state),
|
||||
claimsBalance: selectClaimsBalance(state) || 0,
|
||||
supportsBalance: selectSupportsBalance(state) || 0,
|
||||
tipsBalance: selectTipsBalance(state) || 0,
|
||||
rewards: selectClaimedRewards(state),
|
||||
hasSynced: Boolean(selectSyncHash(state)),
|
||||
fetchingUtxoCounts: selectIsFetchingUtxoCounts(state),
|
||||
consolidatingUtxos: selectIsConsolidatingUtxos(state),
|
||||
massClaimingTips: selectIsMassClaimingTips(state),
|
||||
utxoCounts: selectUtxoCounts(state),
|
||||
consolidateIsPending: selectPendingConsolidateTxid(state),
|
||||
massClaimIsPending: selectPendingMassClaimTxid(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doOpenModal,
|
||||
doFetchUtxoCounts,
|
||||
doUtxoConsolidate,
|
||||
})(WalletBalance);
|
166
ui/component/walletFiatAccountHistory/view.jsx
Normal file
166
ui/component/walletFiatAccountHistory/view.jsx
Normal file
|
@ -0,0 +1,166 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import moment from 'moment';
|
||||
|
||||
type Props = {
|
||||
accountDetails: any,
|
||||
transactions: any,
|
||||
};
|
||||
|
||||
const WalletBalance = (props: Props) => {
|
||||
// receive transactions from parent component
|
||||
let accountTransactions = props.transactions;
|
||||
|
||||
// reverse so most recent payments come first
|
||||
if (accountTransactions) {
|
||||
accountTransactions = accountTransactions.reverse();
|
||||
}
|
||||
|
||||
if (accountTransactions && accountTransactions.length > 10) {
|
||||
accountTransactions.length = 10;
|
||||
}
|
||||
|
||||
// const [detailsExpanded, setDetailsExpanded] = React.useState(false);
|
||||
const [accountStatusResponse, setAccountStatusResponse] = React.useState();
|
||||
const [subscriptions, setSubscriptions] = React.useState([]);
|
||||
|
||||
var environment = 'test';
|
||||
|
||||
function getAccountStatus() {
|
||||
return Lbryio.call(
|
||||
'account',
|
||||
'status',
|
||||
{
|
||||
environment,
|
||||
},
|
||||
'post'
|
||||
);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
(async function() {
|
||||
const response = await getAccountStatus();
|
||||
|
||||
setAccountStatusResponse(response);
|
||||
|
||||
console.log(response);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<><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>{__('Tip Location')}</th>
|
||||
<th>{__('Amount (USD)')} </th>
|
||||
<th>{__('Processing Fee')}</th>
|
||||
<th>{__('Odysee Fee')}</th>
|
||||
<th>{__('Received Amount')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{accountTransactions &&
|
||||
accountTransactions.map((transaction) => (
|
||||
<tr key={transaction.name + transaction.created_at}>
|
||||
<td>{moment(transaction.created_at).format('LLL')}</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.channel_claim_id}
|
||||
label={transaction.channel_name}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.source_claim_id}
|
||||
label={
|
||||
transaction.channel_claim_id === transaction.source_claim_id
|
||||
? 'Channel Page'
|
||||
: 'File Page'
|
||||
}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>${transaction.tipped_amount / 100}</td>
|
||||
<td>${transaction.transaction_fee / 100}</td>
|
||||
<td>${transaction.application_fee / 100}</td>
|
||||
<td>${transaction.received_amount / 100}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{!accountTransactions && <p style={{textAlign: 'center', marginTop: '20px', fontSize: '13px', color: 'rgb(171, 171, 171)'}}>No Transactions</p>}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Card
|
||||
title={__('Subscriptions')}
|
||||
body={
|
||||
<>
|
||||
<div className="table__wrapper">
|
||||
<table className="table table--transactions">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="date-header">{__('Date')}</th>
|
||||
<th>{<>{__('Receiving Channel Name')}</>}</th>
|
||||
<th>{__('Tip Location')}</th>
|
||||
<th>{__('Amount (USD)')} </th>
|
||||
<th>{__('Card Last 4')}</th>
|
||||
<th>{__('Anonymous')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{subscriptions &&
|
||||
subscriptions.reverse().map((transaction) => (
|
||||
<tr key={transaction.name + transaction.created_at}>
|
||||
<td>{moment(transaction.created_at).format('LLL')}</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.channel_claim_id}
|
||||
label={transaction.channel_name}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.source_claim_id}
|
||||
label={
|
||||
transaction.channel_claim_id === transaction.source_claim_id
|
||||
? 'Channel Page'
|
||||
: 'File Page'
|
||||
}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>${transaction.tipped_amount / 100}</td>
|
||||
<td>{lastFour}</td>
|
||||
<td>{transaction.private_tip ? 'Yes' : 'No'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{(!subscriptions || subscriptions.length === 0) && <p style={{textAlign: 'center', marginTop: '22px', fontSize: '13px', color: 'rgb(171, 171, 171)'}}>No Subscriptions</p>}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/></>
|
||||
);
|
||||
};
|
||||
|
||||
export default WalletBalance;
|
40
ui/component/walletFiatBalance/index.js
Normal file
40
ui/component/walletFiatBalance/index.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectBalance,
|
||||
selectClaimsBalance,
|
||||
selectSupportsBalance,
|
||||
selectTipsBalance,
|
||||
selectIsFetchingUtxoCounts,
|
||||
selectUtxoCounts,
|
||||
doFetchUtxoCounts,
|
||||
doUtxoConsolidate,
|
||||
selectIsConsolidatingUtxos,
|
||||
selectIsMassClaimingTips,
|
||||
selectPendingConsolidateTxid,
|
||||
selectPendingMassClaimTxid,
|
||||
} from 'lbry-redux';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { selectSyncHash } from 'redux/selectors/sync';
|
||||
import { selectClaimedRewards } from 'redux/selectors/rewards';
|
||||
import WalletBalance from './view';
|
||||
|
||||
const select = state => ({
|
||||
balance: selectBalance(state),
|
||||
claimsBalance: selectClaimsBalance(state) || 0,
|
||||
supportsBalance: selectSupportsBalance(state) || 0,
|
||||
tipsBalance: selectTipsBalance(state) || 0,
|
||||
rewards: selectClaimedRewards(state),
|
||||
hasSynced: Boolean(selectSyncHash(state)),
|
||||
fetchingUtxoCounts: selectIsFetchingUtxoCounts(state),
|
||||
consolidatingUtxos: selectIsConsolidatingUtxos(state),
|
||||
massClaimingTips: selectIsMassClaimingTips(state),
|
||||
utxoCounts: selectUtxoCounts(state),
|
||||
consolidateIsPending: selectPendingConsolidateTxid(state),
|
||||
massClaimIsPending: selectPendingMassClaimTxid(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doOpenModal,
|
||||
doFetchUtxoCounts,
|
||||
doUtxoConsolidate,
|
||||
})(WalletBalance);
|
99
ui/component/walletFiatBalance/view.jsx
Normal file
99
ui/component/walletFiatBalance/view.jsx
Normal file
|
@ -0,0 +1,99 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React from 'react';
|
||||
import CreditAmount from 'component/common/credit-amount';
|
||||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
import Icon from 'component/common/icon';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
|
||||
type Props = {
|
||||
accountDetails: any,
|
||||
};
|
||||
|
||||
const WalletBalance = (props: Props) => {
|
||||
const {
|
||||
accountDetails,
|
||||
} = props;
|
||||
|
||||
console.log('account details');
|
||||
console.log(accountDetails);
|
||||
|
||||
const [detailsExpanded, setDetailsExpanded] = React.useState(false);
|
||||
|
||||
return (
|
||||
<>{<Card
|
||||
title={<><Icon size="18" icon={ICONS.FINANCE} />{(accountDetails && (accountDetails.total_received_unpaid / 100)) || 0} USD</>}
|
||||
subtitle={
|
||||
<I18nMessage>
|
||||
This is your remaining balance that can still be withdrawn to your bank account
|
||||
</I18nMessage>
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
<h2 className="section__title--small">
|
||||
${(accountDetails && (accountDetails.total_tipped / 100)) || 0} Total Received Tips
|
||||
</h2>
|
||||
|
||||
<h2 className="section__title--small">
|
||||
${(accountDetails && (accountDetails.total_paid_out / 100)) || 0} Withdrawn
|
||||
<Button
|
||||
button="link"
|
||||
label={detailsExpanded ? __('View less') : __('View more')}
|
||||
iconRight={detailsExpanded ? ICONS.UP : ICONS.DOWN}
|
||||
onClick={() => setDetailsExpanded(!detailsExpanded)}
|
||||
/>
|
||||
</h2>
|
||||
|
||||
{/* view more section */}
|
||||
{detailsExpanded && (
|
||||
<div className="section__subtitle">
|
||||
<dl>
|
||||
<dt>
|
||||
<span className="dt__text">{__('Earned from uploads')}</span>
|
||||
{/* <span className="help--dt">({__('Earned from channel page')})</span> */}
|
||||
</dt>
|
||||
<dd>
|
||||
<span className="dd__text">
|
||||
{Boolean(1) && (
|
||||
<Button
|
||||
button="link"
|
||||
className="dd__button"
|
||||
icon={ICONS.UNLOCK}
|
||||
/>
|
||||
)}
|
||||
<CreditAmount amount={1} precision={4} />
|
||||
</span>
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
<span className="dt__text">{__('Earned from channel page')}</span>
|
||||
{/* <span className="help--dt">({__('Delete or edit past content to spend')})</span> */}
|
||||
</dt>
|
||||
<dd>
|
||||
<CreditAmount amount={1} precision={4} />
|
||||
</dd>
|
||||
|
||||
{/* <dt> */}
|
||||
{/* <span className="dt__text">{__('...supporting content')}</span> */}
|
||||
{/* <span className="help--dt">({__('Delete supports to spend')})</span> */}
|
||||
{/* </dt> */}
|
||||
{/* <dd> */}
|
||||
{/* <CreditAmount amount={1} precision={4} /> */}
|
||||
{/* </dd> */}
|
||||
</dl>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="section__actions">
|
||||
<Button button="primary" label={__('Receive Payout')} icon={ICONS.SEND} />
|
||||
<Button button="secondary" label={__('Account Configuration')} icon={ICONS.SETTINGS} navigate={`/$/${PAGES.SETTINGS_STRIPE_ACCOUNT}`} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>}</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WalletBalance;
|
40
ui/component/walletFiatPaymentBalance/index.js
Normal file
40
ui/component/walletFiatPaymentBalance/index.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectBalance,
|
||||
selectClaimsBalance,
|
||||
selectSupportsBalance,
|
||||
selectTipsBalance,
|
||||
selectIsFetchingUtxoCounts,
|
||||
selectUtxoCounts,
|
||||
doFetchUtxoCounts,
|
||||
doUtxoConsolidate,
|
||||
selectIsConsolidatingUtxos,
|
||||
selectIsMassClaimingTips,
|
||||
selectPendingConsolidateTxid,
|
||||
selectPendingMassClaimTxid,
|
||||
} from 'lbry-redux';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { selectSyncHash } from 'redux/selectors/sync';
|
||||
import { selectClaimedRewards } from 'redux/selectors/rewards';
|
||||
import WalletBalance from './view';
|
||||
|
||||
const select = state => ({
|
||||
balance: selectBalance(state),
|
||||
claimsBalance: selectClaimsBalance(state) || 0,
|
||||
supportsBalance: selectSupportsBalance(state) || 0,
|
||||
tipsBalance: selectTipsBalance(state) || 0,
|
||||
rewards: selectClaimedRewards(state),
|
||||
hasSynced: Boolean(selectSyncHash(state)),
|
||||
fetchingUtxoCounts: selectIsFetchingUtxoCounts(state),
|
||||
consolidatingUtxos: selectIsConsolidatingUtxos(state),
|
||||
massClaimingTips: selectIsMassClaimingTips(state),
|
||||
utxoCounts: selectUtxoCounts(state),
|
||||
consolidateIsPending: selectPendingConsolidateTxid(state),
|
||||
massClaimIsPending: selectPendingMassClaimTxid(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doOpenModal,
|
||||
doFetchUtxoCounts,
|
||||
doUtxoConsolidate,
|
||||
})(WalletBalance);
|
68
ui/component/walletFiatPaymentBalance/view.jsx
Normal file
68
ui/component/walletFiatPaymentBalance/view.jsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
import Icon from 'component/common/icon';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
|
||||
type Props = {
|
||||
totalTippedAmount: number,
|
||||
accountDetails: any,
|
||||
transactions: any,
|
||||
};
|
||||
|
||||
const WalletBalance = (props: Props) => {
|
||||
const {
|
||||
// accountDetails,
|
||||
totalTippedAmount,
|
||||
transactions,
|
||||
} = props;
|
||||
|
||||
// const [detailsExpanded, setDetailsExpanded] = React.useState(false);
|
||||
const [totalCreatorsSupported, setTotalCreatorsSupported] = React.useState(false);
|
||||
|
||||
// calculate how many unique users tipped
|
||||
React.useEffect(() => {
|
||||
if (transactions) {
|
||||
let channelNames = [];
|
||||
|
||||
for (const transaction of transactions) {
|
||||
channelNames.push(transaction.channel_name);
|
||||
console.log(transaction.channel_name);
|
||||
}
|
||||
|
||||
let unique = [...new Set(channelNames)];
|
||||
setTotalCreatorsSupported(unique.length);
|
||||
}
|
||||
}, [transactions]);
|
||||
|
||||
return (
|
||||
<>{<Card
|
||||
title={<><Icon size="18" icon={ICONS.FINANCE} />{totalTippedAmount} USD</>}
|
||||
subtitle={
|
||||
<I18nMessage>
|
||||
The total amount you have tipped to different creators
|
||||
</I18nMessage>
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
<h2 className="section__title--small">
|
||||
{transactions && transactions.length} Total Tips
|
||||
</h2>
|
||||
|
||||
<h2 className="section__title--small">
|
||||
{totalCreatorsSupported || 0} Creators Supported
|
||||
</h2>
|
||||
|
||||
<div className="section__actions">
|
||||
<Button button="secondary" label={__('Manage Cards')} icon={ICONS.SETTINGS} navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>}</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WalletBalance;
|
40
ui/component/walletFiatPaymentHistory/index.js
Normal file
40
ui/component/walletFiatPaymentHistory/index.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectBalance,
|
||||
selectClaimsBalance,
|
||||
selectSupportsBalance,
|
||||
selectTipsBalance,
|
||||
selectIsFetchingUtxoCounts,
|
||||
selectUtxoCounts,
|
||||
doFetchUtxoCounts,
|
||||
doUtxoConsolidate,
|
||||
selectIsConsolidatingUtxos,
|
||||
selectIsMassClaimingTips,
|
||||
selectPendingConsolidateTxid,
|
||||
selectPendingMassClaimTxid,
|
||||
} from 'lbry-redux';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { selectSyncHash } from 'redux/selectors/sync';
|
||||
import { selectClaimedRewards } from 'redux/selectors/rewards';
|
||||
import WalletBalance from './view';
|
||||
|
||||
const select = state => ({
|
||||
balance: selectBalance(state),
|
||||
claimsBalance: selectClaimsBalance(state) || 0,
|
||||
supportsBalance: selectSupportsBalance(state) || 0,
|
||||
tipsBalance: selectTipsBalance(state) || 0,
|
||||
rewards: selectClaimedRewards(state),
|
||||
hasSynced: Boolean(selectSyncHash(state)),
|
||||
fetchingUtxoCounts: selectIsFetchingUtxoCounts(state),
|
||||
consolidatingUtxos: selectIsConsolidatingUtxos(state),
|
||||
massClaimingTips: selectIsMassClaimingTips(state),
|
||||
utxoCounts: selectUtxoCounts(state),
|
||||
consolidateIsPending: selectPendingConsolidateTxid(state),
|
||||
massClaimIsPending: selectPendingMassClaimTxid(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doOpenModal,
|
||||
doFetchUtxoCounts,
|
||||
doUtxoConsolidate,
|
||||
})(WalletBalance);
|
203
ui/component/walletFiatPaymentHistory/view.jsx
Normal file
203
ui/component/walletFiatPaymentHistory/view.jsx
Normal file
|
@ -0,0 +1,203 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import moment from 'moment';
|
||||
|
||||
type Props = {
|
||||
accountDetails: any,
|
||||
transactions: any,
|
||||
totalTippedAmount: number,
|
||||
};
|
||||
|
||||
const WalletBalance = (props: Props) => {
|
||||
// receive transactions from parent component
|
||||
let accountTransactions = props.transactions;
|
||||
|
||||
console.log('heres transactions');
|
||||
console.log(accountTransactions);
|
||||
|
||||
// let totalTippedAmount = props.totalTippedAmount;
|
||||
|
||||
// totalTippedAmount = 0;
|
||||
|
||||
// reverse so most recent payments come first
|
||||
if (accountTransactions) {
|
||||
accountTransactions = accountTransactions.reverse();
|
||||
}
|
||||
|
||||
// const [detailsExpanded, setDetailsExpanded] = React.useState(false);
|
||||
// const [accountStatusResponse, setAccountStatusResponse] = React.useState();
|
||||
// const [totalTippedAmount, setTotalTippedAmount] = React.useState(0);
|
||||
|
||||
const [paymentHistoryTransactions, setPaymentHistoryTransactions] = React.useState();
|
||||
const [subscriptions, setSubscriptions] = React.useState();
|
||||
|
||||
const [lastFour, setLastFour] = React.useState();
|
||||
|
||||
var environment = 'test';
|
||||
|
||||
// function getPaymentHistory() {
|
||||
// return Lbryio.call(
|
||||
// 'customer',
|
||||
// 'list',
|
||||
// {
|
||||
// environment,
|
||||
// },
|
||||
// 'post'
|
||||
// ); };
|
||||
|
||||
function getCustomerStatus() {
|
||||
return Lbryio.call(
|
||||
'customer',
|
||||
'status',
|
||||
{
|
||||
environment,
|
||||
},
|
||||
'post'
|
||||
);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
(async function() {
|
||||
let response = accountTransactions;
|
||||
|
||||
console.log('payment transactions');
|
||||
console.log(response);
|
||||
|
||||
const customerStatusResponse = await getCustomerStatus();
|
||||
|
||||
setLastFour(customerStatusResponse.PaymentMethods[0].card.last4);
|
||||
|
||||
if (response && response.length > 10) response.length = 10;
|
||||
|
||||
setPaymentHistoryTransactions(response);
|
||||
|
||||
const subscriptions = [...response];
|
||||
|
||||
if (subscriptions && subscriptions.length > 2) {
|
||||
subscriptions.length = 2;
|
||||
setSubscriptions([]);
|
||||
} else {
|
||||
setSubscriptions([]);
|
||||
}
|
||||
|
||||
console.log(response);
|
||||
})();
|
||||
}, [accountTransactions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
title={__('Payment 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>{__('Tip Location')}</th>
|
||||
<th>{__('Amount (USD)')} </th>
|
||||
<th>{__('Card Last 4')}</th>
|
||||
<th>{__('Anonymous')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{accountTransactions &&
|
||||
accountTransactions.map((transaction) => (
|
||||
<tr key={transaction.name + transaction.created_at}>
|
||||
<td>{moment(transaction.created_at).format('LLL')}</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.channel_claim_id}
|
||||
label={transaction.channel_name}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.source_claim_id}
|
||||
label={
|
||||
transaction.channel_claim_id === transaction.source_claim_id
|
||||
? 'Channel Page'
|
||||
: 'File Page'
|
||||
}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>${transaction.tipped_amount / 100}</td>
|
||||
<td>{lastFour}</td>
|
||||
<td>{transaction.private_tip ? 'Yes' : 'No'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{(!accountTransactions || accountTransactions.length === 0) && <p style={{textAlign: 'center', marginTop: '20px', fontSize: '13px', color: 'rgb(171, 171, 171)'}}>No Transactions</p>}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card
|
||||
title={__('Subscriptions')}
|
||||
body={
|
||||
<>
|
||||
<div className="table__wrapper">
|
||||
<table className="table table--transactions">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="date-header">{__('Date')}</th>
|
||||
<th>{<>{__('Receiving Channel Name')}</>}</th>
|
||||
<th>{__('Tip Location')}</th>
|
||||
<th>{__('Amount (USD)')} </th>
|
||||
<th>{__('Card Last 4')}</th>
|
||||
<th>{__('Anonymous')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{subscriptions &&
|
||||
subscriptions.reverse().map((transaction) => (
|
||||
<tr key={transaction.name + transaction.created_at}>
|
||||
<td>{moment(transaction.created_at).format('LLL')}</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.channel_claim_id}
|
||||
label={transaction.channel_name}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.source_claim_id}
|
||||
label={
|
||||
transaction.channel_claim_id === transaction.source_claim_id
|
||||
? 'Channel Page'
|
||||
: 'File Page'
|
||||
}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>${transaction.tipped_amount / 100}</td>
|
||||
<td>{lastFour}</td>
|
||||
<td>{transaction.private_tip ? 'Yes' : 'No'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{(!subscriptions || subscriptions.length === 0) && <p style={{textAlign: 'center', marginTop: '22px', fontSize: '13px', color: 'rgb(171, 171, 171)'}}>No Subscriptions</p>}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WalletBalance;
|
|
@ -1,12 +1,12 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
import Page from 'component/page';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import { URL, WEBPACK_WEB_PORT, STRIPE_PUBLIC_KEY } from 'config';
|
||||
import moment from 'moment';
|
||||
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
|
||||
|
@ -184,7 +184,7 @@ class StripeAccountConnection extends React.Component<Props, State> {
|
|||
unpaidBalance,
|
||||
accountNotConfirmedButReceivedTips,
|
||||
pageTitle,
|
||||
accountTransactions,
|
||||
// accountTransactions,
|
||||
} = this.state;
|
||||
|
||||
const { user } = this.props;
|
||||
|
@ -228,25 +228,11 @@ class StripeAccountConnection extends React.Component<Props, State> {
|
|||
<div>
|
||||
<div>
|
||||
<h3>{__('Congratulations! Your account has been connected with Odysee.')}</h3>
|
||||
{unpaidBalance > 0 ? (
|
||||
<div>
|
||||
<br />
|
||||
<h3>
|
||||
{__('Your pending account balance is $%balance% USD.', { balance: unpaidBalance / 100 })}
|
||||
</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>
|
||||
)}
|
||||
{/* TODO: hopefully we won't be using this anymore and can remove it */}
|
||||
{accountNotConfirmedButReceivedTips && (
|
||||
<div className="card__body-actions">
|
||||
<div>
|
||||
|
@ -277,66 +263,16 @@ class StripeAccountConnection extends React.Component<Props, State> {
|
|||
)}
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('View Transactions')}
|
||||
icon={ICONS.SETTINGS}
|
||||
navigate={`/$/${PAGES.WALLET}?tab=account-history`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<br />
|
||||
|
||||
{/* customer already has transactions */}
|
||||
{accountTransactions && accountTransactions.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>{__('Tip Location')}</th>
|
||||
<th>{__('Amount (USD)')} </th>
|
||||
<th>{__('Processing Fee')}</th>
|
||||
<th>{__('Odysee Fee')}</th>
|
||||
<th>{__('Received Amount')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{accountTransactions &&
|
||||
accountTransactions.map((transaction) => (
|
||||
<tr key={transaction.name + transaction.created_at}>
|
||||
<td>{moment(transaction.created_at).format('LLL')}</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.channel_claim_id}
|
||||
label={transaction.channel_name}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.source_claim_id}
|
||||
label={
|
||||
transaction.channel_claim_id === transaction.source_claim_id
|
||||
? 'Channel Page'
|
||||
: 'File Page'
|
||||
}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>${transaction.tipped_amount / 100}</td>
|
||||
<td>${transaction.transaction_fee / 100}</td>
|
||||
<td>${transaction.application_fee / 100}</td>
|
||||
<td>${transaction.received_amount / 100}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -11,6 +11,7 @@ import Plastic from 'react-plastic';
|
|||
import Button from 'component/button';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import * as PAGES from 'constants/pages';
|
||||
|
||||
let stripeEnvironment = 'test';
|
||||
// if the key contains pk_live it's a live key
|
||||
|
@ -418,72 +419,18 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
/>
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('View Transactions')}
|
||||
icon={ICONS.SETTINGS}
|
||||
navigate={`/$/${PAGES.WALLET}?tab=payment-history`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<br />
|
||||
|
||||
{/* if a user has no transactions yet */}
|
||||
{(!customerTransactions || customerTransactions.length === 0) && (
|
||||
<Card
|
||||
title={__('Tip History')}
|
||||
subtitle={__('You have not sent any tips yet. When you do they will appear here. ')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* customer already has transactions */}
|
||||
{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>{__('Tip Location')}</th>
|
||||
<th>{__('Amount (USD)')} </th>
|
||||
<th>{__('Anonymous')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{customerTransactions &&
|
||||
customerTransactions.reverse().map((transaction) => (
|
||||
<tr key={transaction.name + transaction.created_at}>
|
||||
<td>{moment(transaction.created_at).format('LLL')}</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.channel_claim_id}
|
||||
label={transaction.channel_name}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
className="stripe__card-link-text"
|
||||
navigate={'/' + transaction.channel_name + ':' + transaction.source_claim_id}
|
||||
label={
|
||||
transaction.channel_claim_id === transaction.source_claim_id
|
||||
? 'Channel Page'
|
||||
: 'File Page'
|
||||
}
|
||||
button="link"
|
||||
/>
|
||||
</td>
|
||||
<td>${transaction.tipped_amount / 100}</td>
|
||||
<td>{transaction.private_tip ? 'Yes' : 'No'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,10 +2,15 @@
|
|||
import React from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import WalletBalance from 'component/walletBalance';
|
||||
import WalletFiatBalance from 'component/walletFiatBalance';
|
||||
import WalletFiatPaymentBalance from 'component/walletFiatPaymentBalance';
|
||||
import WalletFiatAccountHistory from 'component/walletFiatAccountHistory';
|
||||
import WalletFiatPaymentHistory from 'component/walletFiatPaymentHistory';
|
||||
import TxoList from 'component/txoList';
|
||||
import Page from 'component/page';
|
||||
import Spinner from 'component/spinner';
|
||||
import YrblWalletEmpty from 'component/yrblWalletEmpty';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
|
||||
type Props = {
|
||||
history: { action: string, push: (string) => void, replace: (string) => void },
|
||||
|
@ -14,6 +19,143 @@ type Props = {
|
|||
};
|
||||
|
||||
const WalletPage = (props: Props) => {
|
||||
console.log(props);
|
||||
|
||||
const stripeEnvironment = 'test';
|
||||
|
||||
const tab = new URLSearchParams(props.location.search).get('tab');
|
||||
|
||||
const [accountStatusResponse, setAccountStatusResponse] = React.useState();
|
||||
const [accountTransactionResponse, setAccountTransactionResponse] = React.useState();
|
||||
const [customerTransactions, setCustomerTransactions] = React.useState();
|
||||
const [totalTippedAmount, setTotalTippedAmount] = React.useState(0);
|
||||
|
||||
function getPaymentHistory() {
|
||||
return Lbryio.call(
|
||||
'customer',
|
||||
'list',
|
||||
{
|
||||
environment: stripeEnvironment,
|
||||
},
|
||||
'post'
|
||||
); };
|
||||
|
||||
// function getCustomerStatus() {
|
||||
// return Lbryio.call(
|
||||
// 'customer',
|
||||
// 'status',
|
||||
// {
|
||||
// environment: stripeEnvironment,
|
||||
// },
|
||||
// 'post'
|
||||
// );
|
||||
// }
|
||||
|
||||
function getAccountStatus() {
|
||||
return Lbryio.call(
|
||||
'account',
|
||||
'status',
|
||||
{
|
||||
environment: stripeEnvironment,
|
||||
},
|
||||
'post'
|
||||
);
|
||||
}
|
||||
|
||||
function getAccountTransactionsa() {
|
||||
return Lbryio.call(
|
||||
'account',
|
||||
'list',
|
||||
{
|
||||
environment: stripeEnvironment,
|
||||
},
|
||||
'post'
|
||||
);
|
||||
}
|
||||
|
||||
// calculate account transactions section
|
||||
React.useEffect(() => {
|
||||
(async function() {
|
||||
try {
|
||||
const response = await getAccountStatus();
|
||||
|
||||
setAccountStatusResponse(response);
|
||||
|
||||
// TODO: some weird naming clash hence getAccountTransactionsa
|
||||
const getAccountTransactions = await getAccountTransactionsa();
|
||||
|
||||
setAccountTransactionResponse(getAccountTransactions);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
// populate customer payment data
|
||||
React.useEffect(() => {
|
||||
(async function() {
|
||||
try {
|
||||
// get card payments customer has made
|
||||
const customerTransactionResponse = await getPaymentHistory();
|
||||
|
||||
let totalTippedAmount = 0;
|
||||
|
||||
for (const transaction of customerTransactionResponse) {
|
||||
totalTippedAmount = totalTippedAmount + transaction.tipped_amount;
|
||||
}
|
||||
|
||||
setTotalTippedAmount(totalTippedAmount / 100);
|
||||
|
||||
setCustomerTransactions(customerTransactionResponse);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
function focusLBCTab() {
|
||||
document.getElementsByClassName('lbc-transactions')[0].style.display = 'inline';
|
||||
document.getElementsByClassName('fiat-transactions')[0].style.display = 'none';
|
||||
document.getElementsByClassName('payment-history-tab')[0].style.display = 'none';
|
||||
|
||||
document.getElementsByClassName('lbc-tab-switcher')[0].style.textDecoration = 'underline';
|
||||
document.getElementsByClassName('fiat-tab-switcher')[0].style.textDecoration = 'none';
|
||||
document.getElementsByClassName('fiat-payment-history-switcher')[0].style.textDecoration = 'none';
|
||||
}
|
||||
|
||||
function focusAccountHistoryTab() {
|
||||
document.getElementsByClassName('lbc-transactions')[0].style.display = 'none';
|
||||
document.getElementsByClassName('payment-history-tab')[0].style.display = 'none';
|
||||
document.getElementsByClassName('fiat-transactions')[0].style.display = 'inline';
|
||||
|
||||
document.getElementsByClassName('lbc-tab-switcher')[0].style.textDecoration = 'none';
|
||||
document.getElementsByClassName('fiat-tab-switcher')[0].style.textDecoration = 'underline';
|
||||
document.getElementsByClassName('fiat-payment-history-switcher')[0].style.textDecoration = 'none';
|
||||
}
|
||||
|
||||
function focusPaymentHistoryTab() {
|
||||
document.getElementsByClassName('lbc-transactions')[0].style.display = 'none';
|
||||
document.getElementsByClassName('fiat-transactions')[0].style.display = 'none';
|
||||
document.getElementsByClassName('payment-history-tab')[0].style.display = 'inline';
|
||||
|
||||
document.getElementsByClassName('lbc-tab-switcher')[0].style.textDecoration = 'none';
|
||||
document.getElementsByClassName('fiat-tab-switcher')[0].style.textDecoration = 'none';
|
||||
document.getElementsByClassName('fiat-payment-history-switcher')[0].style.textDecoration = 'underline';
|
||||
}
|
||||
|
||||
// select the first tab
|
||||
React.useEffect(() => {
|
||||
if (tab === 'account-history') {
|
||||
// if (1 === 2) {
|
||||
focusAccountHistoryTab();
|
||||
} else if (tab === 'payment-history') {
|
||||
// } else if (1 === 2){
|
||||
focusPaymentHistoryTab();
|
||||
} else {
|
||||
focusLBCTab();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const { location, totalBalance } = props;
|
||||
const { search } = location;
|
||||
const showIntro = totalBalance === 0;
|
||||
|
@ -21,23 +163,66 @@ const WalletPage = (props: Props) => {
|
|||
|
||||
return (
|
||||
<Page>
|
||||
{loading && (
|
||||
<div className="main--empty">
|
||||
<Spinner delayed />
|
||||
</div>
|
||||
)}
|
||||
{!loading && (
|
||||
<>
|
||||
{showIntro ? (
|
||||
<YrblWalletEmpty includeWalletLink />
|
||||
) : (
|
||||
<div className="card-stack">
|
||||
<WalletBalance />
|
||||
<TxoList search={search} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* tabs to switch between fiat and lbc */}
|
||||
{/* lbc button */}
|
||||
<h2 className="lbc-tab-switcher"
|
||||
style={{display: 'inline-block', paddingBottom: '16px', marginRight: '14px', textUnderlineOffset: '4px', fontSize: '18px', marginLeft: '3px'}}
|
||||
onClick={() => {
|
||||
focusLBCTab();
|
||||
}}
|
||||
>LBC Wallet</h2>
|
||||
{/* account history button */}
|
||||
<h2 className="fiat-tab-switcher"
|
||||
style={{display: 'inline-block', textUnderlineOffset: '4px', fontSize: '18px', marginRight: '14px'}}
|
||||
onClick={() => {
|
||||
focusAccountHistoryTab();
|
||||
}}
|
||||
>Account History</h2>
|
||||
{/* payment history button */}
|
||||
<h2 className="fiat-payment-history-switcher"
|
||||
style={{display: 'inline-block', textUnderlineOffset: '4px', fontSize: '18px'}}
|
||||
onClick={() => {
|
||||
focusPaymentHistoryTab();
|
||||
}}
|
||||
>Payment History</h2>
|
||||
|
||||
{/* lbc wallet section */}
|
||||
<div className="lbc-transactions">
|
||||
{/* if the transactions are loading */}
|
||||
{ loading && (
|
||||
<div className="main--empty">
|
||||
<Spinner delayed />
|
||||
</div>
|
||||
)}
|
||||
{/* when the transactions are finished loading */}
|
||||
{ !loading && (
|
||||
<>
|
||||
{showIntro ? (
|
||||
<YrblWalletEmpty includeWalletLink />
|
||||
) : (
|
||||
<div className="card-stack">
|
||||
<WalletBalance />
|
||||
<TxoList search={search} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* account received transactions section */}
|
||||
<div className="fiat-transactions" style={{display: 'none'}}>
|
||||
<WalletFiatBalance accountDetails={accountStatusResponse} />
|
||||
<div style={{paddingTop: '25px'}} />
|
||||
<WalletFiatAccountHistory transactions={accountTransactionResponse} />
|
||||
</div>
|
||||
|
||||
{/* fiat payment history for tips made by user */}
|
||||
<div className="payment-history-tab" style={{display: 'none'}}>
|
||||
<WalletFiatPaymentBalance transactions={customerTransactions} totalTippedAmount={totalTippedAmount} accountDetails={accountStatusResponse} />
|
||||
<div style={{paddingTop: '25px'}} />
|
||||
<WalletFiatPaymentHistory transactions={customerTransactions} />
|
||||
</div>
|
||||
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue