Preorder content functionality (#1743)
* adding preorder button * adding preorder modal * frontend mostly done * check if its already purchased * refresh page after purchase * smooth out purchase process * check if user has card saved * handle case where its the users own upload * fix transaction listing order bug * cleaning up code for merge * fix lint errors * fix flow errors * allow eur purchases * support eur on customer transaction page * fix css
This commit is contained in:
parent
017df02816
commit
4f47779303
18 changed files with 557 additions and 15 deletions
|
@ -5,6 +5,7 @@ import {
|
|||
selectHasChannels,
|
||||
makeSelectTagInClaimOrChannelForUri,
|
||||
selectClaimIsNsfwForUri,
|
||||
selectPreorderTag,
|
||||
} from 'redux/selectors/claims';
|
||||
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
|
||||
import { doPrepareEdit } from 'redux/actions/publish';
|
||||
|
@ -32,6 +33,7 @@ const select = (state, props) => {
|
|||
streamingUrl: makeSelectStreamingUrlForUri(uri)(state),
|
||||
disableDownloadButton: makeSelectTagInClaimOrChannelForUri(uri, DISABLE_DOWNLOAD_BUTTON_TAG)(state),
|
||||
isMature: selectClaimIsNsfwForUri(state, uri),
|
||||
isAPreorder: selectPreorderTag(state, props.uri),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ type Props = {
|
|||
doToast: (data: { message: string }) => void,
|
||||
doDownloadUri: (uri: string) => void,
|
||||
isMature: boolean,
|
||||
isAPreorder: boolean,
|
||||
};
|
||||
|
||||
export default function FileActions(props: Props) {
|
||||
|
@ -54,6 +55,7 @@ export default function FileActions(props: Props) {
|
|||
doToast,
|
||||
doDownloadUri,
|
||||
isMature,
|
||||
isAPreorder,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
|
@ -115,7 +117,7 @@ export default function FileActions(props: Props) {
|
|||
<div className="media__actions">
|
||||
{ENABLE_FILE_REACTIONS && <FileReactions uri={uri} />}
|
||||
|
||||
<ClaimSupportButton uri={uri} fileAction />
|
||||
{ !isAPreorder && <ClaimSupportButton uri={uri} fileAction />}
|
||||
|
||||
<ClaimCollectionAddButton uri={uri} fileAction />
|
||||
|
||||
|
|
23
ui/component/preorderButton/index.js
Normal file
23
ui/component/preorderButton/index.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectPreorderTag, selectClaimForUri, selectClaimIsMine } from 'redux/selectors/claims';
|
||||
import ClaimTags from './view';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { selectClientSetting } from 'redux/selectors/settings';
|
||||
|
||||
const select = (state, props) => {
|
||||
const claim = selectClaimForUri(state, props.uri);
|
||||
|
||||
return {
|
||||
preorderTag: selectPreorderTag(state, props.uri),
|
||||
claimIsMine: selectClaimIsMine(state, claim),
|
||||
claim,
|
||||
preferredCurrency: selectClientSetting(state, SETTINGS.PREFERRED_CURRENCY),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = {
|
||||
doOpenModal,
|
||||
};
|
||||
|
||||
export default connect(select, perform)(ClaimTags);
|
111
ui/component/preorderButton/view.jsx
Normal file
111
ui/component/preorderButton/view.jsx
Normal file
|
@ -0,0 +1,111 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import Button from 'component/button';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import { getStripeEnvironment } from 'util/stripe';
|
||||
let stripeEnvironment = getStripeEnvironment();
|
||||
|
||||
type Props = {
|
||||
preorderTag: string,
|
||||
doOpenModal: (string, {}) => void,
|
||||
claim: StreamClaim,
|
||||
uri: string,
|
||||
claimIsMine: boolean,
|
||||
preferredCurrency: string,
|
||||
};
|
||||
|
||||
export default function PreorderButton(props: Props) {
|
||||
const { preorderTag, doOpenModal, uri, claim, claimIsMine, preferredCurrency } = props;
|
||||
|
||||
const claimId = claim.claim_id;
|
||||
const myUpload = claimIsMine;
|
||||
|
||||
const [hasAlreadyPreordered, setHasAlreadyPreordered] = React.useState(false);
|
||||
|
||||
function getPaymentHistory() {
|
||||
return Lbryio.call(
|
||||
'customer',
|
||||
'list',
|
||||
{
|
||||
environment: stripeEnvironment,
|
||||
},
|
||||
'post'
|
||||
);
|
||||
}
|
||||
|
||||
async function checkIfAlreadyPurchased() {
|
||||
try {
|
||||
// get card payments customer has made
|
||||
let customerTransactionResponse = await getPaymentHistory();
|
||||
|
||||
let matchingTransaction = false;
|
||||
for (const transaction of customerTransactionResponse) {
|
||||
if (claimId === transaction.source_claim_id) {
|
||||
matchingTransaction = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingTransaction) {
|
||||
setHasAlreadyPreordered(true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
let fiatIconToUse = ICONS.FINANCE;
|
||||
let fiatSymbolToUse = '$';
|
||||
if (preferredCurrency === 'EUR') {
|
||||
fiatIconToUse = ICONS.EURO;
|
||||
fiatSymbolToUse = '€';
|
||||
}
|
||||
|
||||
// populate customer payment data
|
||||
React.useEffect(() => {
|
||||
checkIfAlreadyPurchased();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{preorderTag && !hasAlreadyPreordered && !myUpload && (<div>
|
||||
<Button
|
||||
// ref={buttonRef}
|
||||
iconColor="red"
|
||||
className={'preorder-button'}
|
||||
// largestLabel={isMobile && shrinkOnMobile ? '' : subscriptionLabel}
|
||||
icon={fiatIconToUse}
|
||||
button="primary"
|
||||
label={`Preorder now for ${fiatSymbolToUse}${preorderTag}`}
|
||||
// title={titlePrefix}
|
||||
requiresAuth
|
||||
onClick={() => doOpenModal(MODALS.PREORDER_CONTENT, { uri, checkIfAlreadyPurchased })}
|
||||
/>
|
||||
</div>)}
|
||||
{preorderTag && hasAlreadyPreordered && !myUpload && (<div>
|
||||
<Button
|
||||
// ref={buttonRef}
|
||||
iconColor="red"
|
||||
className={'preorder-button'}
|
||||
// largestLabel={isMobile && shrinkOnMobile ? '' : subscriptionLabel}
|
||||
button="primary"
|
||||
label={'You have preordered this content'}
|
||||
// title={titlePrefix}
|
||||
requiresAuth
|
||||
/>
|
||||
</div>)}
|
||||
{preorderTag && myUpload && (<div>
|
||||
<Button
|
||||
// ref={buttonRef}
|
||||
iconColor="red"
|
||||
className={'preorder-button'}
|
||||
// largestLabel={isMobile && shrinkOnMobile ? '' : subscriptionLabel}
|
||||
button="primary"
|
||||
label={'You cannot preorder your own content'}
|
||||
// title={titlePrefix}
|
||||
/>
|
||||
</div>)}
|
||||
</>
|
||||
);
|
||||
}
|
45
ui/component/preorderContent/index.js
Normal file
45
ui/component/preorderContent/index.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectClaimForUri,
|
||||
selectPreorderTag,
|
||||
} from 'redux/selectors/claims';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import { preOrderPurchase } from 'redux/actions/wallet';
|
||||
import { selectClientSetting } from 'redux/selectors/settings';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import { withRouter } from 'react-router';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { getChannelIdFromClaim, getChannelNameFromClaim } from 'util/claim';
|
||||
import WalletSendTip from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { uri } = props;
|
||||
|
||||
const claim = selectClaimForUri(state, uri, false);
|
||||
const { claim_id: claimId, value_type: claimType } = claim || {};
|
||||
|
||||
// setup variables for backend tip API
|
||||
const channelClaimId = getChannelIdFromClaim(claim);
|
||||
const tipChannelName = getChannelNameFromClaim(claim);
|
||||
|
||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||
const { name: activeChannelName, claim_id: activeChannelId } = activeChannelClaim || {};
|
||||
|
||||
return {
|
||||
activeChannelName,
|
||||
activeChannelId,
|
||||
claimId,
|
||||
claimType,
|
||||
channelClaimId,
|
||||
tipChannelName,
|
||||
preferredCurrency: selectClientSetting(state, SETTINGS.PREFERRED_CURRENCY),
|
||||
preorderTag: selectPreorderTag(state, props.uri),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = {
|
||||
doHideModal,
|
||||
preOrderPurchase,
|
||||
};
|
||||
|
||||
export default withRouter(connect(select, perform)(WalletSendTip));
|
190
ui/component/preorderContent/view.jsx
Normal file
190
ui/component/preorderContent/view.jsx
Normal file
|
@ -0,0 +1,190 @@
|
|||
// @flow
|
||||
import { Form } from 'component/common/form';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
import React from 'react';
|
||||
|
||||
import { getStripeEnvironment } from 'util/stripe';
|
||||
const stripeEnvironment = getStripeEnvironment();
|
||||
|
||||
type TipParams = { tipAmount: number, tipChannelName: string, channelClaimId: string };
|
||||
type UserParams = { activeChannelName: ?string, activeChannelId: ?string };
|
||||
|
||||
type Props = {
|
||||
activeChannelId?: string,
|
||||
activeChannelName?: string,
|
||||
claimId: string,
|
||||
claimType?: string,
|
||||
channelClaimId?: string,
|
||||
tipChannelName?: string,
|
||||
claimIsMine: boolean,
|
||||
isSupport: boolean,
|
||||
isTipOnly?: boolean,
|
||||
customText?: string,
|
||||
doHideModal: () => void,
|
||||
setAmount?: (number) => void,
|
||||
preferredCurrency: string,
|
||||
preOrderPurchase: (
|
||||
TipParams,
|
||||
anonymous: boolean,
|
||||
UserParams,
|
||||
claimId: string,
|
||||
stripe: ?string,
|
||||
preferredCurrency: string,
|
||||
?(any) => Promise<void>,
|
||||
?(any) => void
|
||||
) => void,
|
||||
preorderTag: string,
|
||||
checkIfAlreadyPurchased: () => void,
|
||||
};
|
||||
|
||||
export default function PreorderContent(props: Props) {
|
||||
const {
|
||||
activeChannelId,
|
||||
activeChannelName,
|
||||
claimId,
|
||||
channelClaimId,
|
||||
tipChannelName,
|
||||
doHideModal,
|
||||
preOrderPurchase,
|
||||
preferredCurrency,
|
||||
preorderTag,
|
||||
checkIfAlreadyPurchased,
|
||||
} = props;
|
||||
|
||||
// set the purchase amount once the preorder tag is selected
|
||||
React.useEffect(() => {
|
||||
setTipAmount(Number(preorderTag));
|
||||
}, [preorderTag]);
|
||||
|
||||
/** STATE **/
|
||||
const [tipAmount, setTipAmount] = React.useState(0);
|
||||
|
||||
const [waitingForBackend, setWaitingForBackend] = React.useState(false);
|
||||
|
||||
const [hasCardSaved, setHasSavedCard] = React.useState(true);
|
||||
|
||||
// check if user has a payment method saved
|
||||
React.useEffect(() => {
|
||||
if (!stripeEnvironment) return;
|
||||
|
||||
Lbryio.call(
|
||||
'customer',
|
||||
'status',
|
||||
{
|
||||
environment: stripeEnvironment,
|
||||
},
|
||||
'post'
|
||||
).then((customerStatusResponse) => {
|
||||
const defaultPaymentMethodId =
|
||||
customerStatusResponse.Customer &&
|
||||
customerStatusResponse.Customer.invoice_settings &&
|
||||
customerStatusResponse.Customer.invoice_settings.default_payment_method &&
|
||||
customerStatusResponse.Customer.invoice_settings.default_payment_method.id;
|
||||
|
||||
setHasSavedCard(Boolean(defaultPaymentMethodId));
|
||||
});
|
||||
}, [setHasSavedCard]);
|
||||
|
||||
// text for modal header
|
||||
const titleText = 'Preorder Your Content';
|
||||
|
||||
// icon to use or explainer text to show per tab
|
||||
let explainerText =
|
||||
'This content is not available yet but you' + ' can pre-order it now so you can access it as soon as it goes live';
|
||||
|
||||
// when the form button is clicked
|
||||
function handleSubmit() {
|
||||
const tipParams: TipParams = {
|
||||
tipAmount,
|
||||
tipChannelName: tipChannelName || '',
|
||||
channelClaimId: channelClaimId || '',
|
||||
};
|
||||
const userParams: UserParams = { activeChannelName, activeChannelId };
|
||||
|
||||
async function checkIfFinished() {
|
||||
await checkIfAlreadyPurchased();
|
||||
doHideModal();
|
||||
}
|
||||
|
||||
setWaitingForBackend(true);
|
||||
|
||||
// hit backend to send tip
|
||||
preOrderPurchase(
|
||||
tipParams,
|
||||
!activeChannelId,
|
||||
userParams,
|
||||
claimId,
|
||||
stripeEnvironment,
|
||||
preferredCurrency,
|
||||
checkIfFinished,
|
||||
doHideModal
|
||||
);
|
||||
}
|
||||
|
||||
let fiatSymbolToUse = '$';
|
||||
if (preferredCurrency === 'EUR') {
|
||||
fiatSymbolToUse = '€';
|
||||
}
|
||||
|
||||
function buildButtonText() {
|
||||
return `Preorder your content for ${fiatSymbolToUse}${tipAmount.toString()}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
{!waitingForBackend && (
|
||||
<Card
|
||||
title={titleText}
|
||||
className={'preorder-content-modal'}
|
||||
subtitle={
|
||||
<>
|
||||
{/* short explainer under the button */}
|
||||
<div className="section__subtitle">{explainerText}</div>
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
// confirm purchase card
|
||||
<>
|
||||
{/* */}
|
||||
<div className="handle-submit-area">
|
||||
<Button
|
||||
autoFocus
|
||||
onClick={handleSubmit}
|
||||
button="primary"
|
||||
// label={__('Confirm')}
|
||||
label={buildButtonText()}
|
||||
disabled={!hasCardSaved}
|
||||
/>
|
||||
|
||||
{!hasCardSaved && (
|
||||
<>
|
||||
<div className="add-card-prompt">
|
||||
<Button navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`} label={__('Add a Card')} button="link" />
|
||||
{' ' + __('To Preorder Content')}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{/* processing payment card */}
|
||||
{waitingForBackend && (
|
||||
<Card
|
||||
title={titleText}
|
||||
className={'preorder-content-modal-loading'}
|
||||
subtitle={
|
||||
<>
|
||||
{/* short explainer under the button */}
|
||||
<div className="section__subtitle">{'Processing your purchase...'}</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
import FiatAccountHistory from './view';
|
||||
import WalletFiatAccountHistory from './view';
|
||||
|
||||
export default FiatAccountHistory;
|
||||
export default WalletFiatAccountHistory;
|
||||
|
|
|
@ -8,17 +8,13 @@ type Props = {
|
|||
transactions: any,
|
||||
};
|
||||
|
||||
const WalletBalance = (props: Props) => {
|
||||
const WalletFiatAccountHistory = (props: Props) => {
|
||||
// receive transactions from parent component
|
||||
const { transactions } = props;
|
||||
|
||||
let accountTransactions;
|
||||
|
||||
// reverse so most recent payments come first
|
||||
if (transactions && transactions.length) {
|
||||
accountTransactions = transactions.reverse();
|
||||
}
|
||||
let accountTransactions = transactions;
|
||||
|
||||
// TODO: should add pagination here
|
||||
// if there are more than 10 transactions, limit it to 10 for the frontend
|
||||
// if (accountTransactions && accountTransactions.length > 10) {
|
||||
// accountTransactions.length = 10;
|
||||
|
@ -104,4 +100,4 @@ const WalletBalance = (props: Props) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default WalletBalance;
|
||||
export default WalletFiatAccountHistory;
|
||||
|
|
|
@ -11,12 +11,28 @@ type Props = {
|
|||
transactions: any,
|
||||
};
|
||||
|
||||
const WalletBalance = (props: Props) => {
|
||||
const WalletFiatPaymentHistory = (props: Props) => {
|
||||
// receive transactions from parent component
|
||||
const { transactions: accountTransactions } = props;
|
||||
|
||||
const [lastFour, setLastFour] = React.useState();
|
||||
|
||||
function getSymbol(transaction) {
|
||||
if (transaction.currency === 'eur') {
|
||||
return '€';
|
||||
} else {
|
||||
return '$';
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrencyIso(transaction) {
|
||||
if (transaction.currency === 'eur') {
|
||||
return 'EUR';
|
||||
} else {
|
||||
return 'USD';
|
||||
}
|
||||
}
|
||||
|
||||
function getCustomerStatus() {
|
||||
return Lbryio.call(
|
||||
'customer',
|
||||
|
@ -53,7 +69,7 @@ const WalletBalance = (props: Props) => {
|
|||
<th className="date-header">{__('Date')}</th>
|
||||
<th className="channelName-header">{<>{__('Receiving Channel Name')}</>}</th>
|
||||
<th className="location-header">{__('Tip Location')}</th>
|
||||
<th className="amount-header">{__('Amount (USD)')} </th>
|
||||
<th className="amount-header">{__('Amount')} </th>
|
||||
<th className="card-header">{__('Card Last 4')}</th>
|
||||
<th className="anonymous-header">{__('Anonymous')}</th>
|
||||
</tr>
|
||||
|
@ -88,7 +104,10 @@ const WalletBalance = (props: Props) => {
|
|||
/>
|
||||
</td>
|
||||
{/* how much tipped */}
|
||||
<td>${transaction.tipped_amount / 100}</td>
|
||||
<td>
|
||||
{getSymbol(transaction)}
|
||||
{transaction.tipped_amount / 100} {getCurrencyIso(transaction)}
|
||||
</td>
|
||||
{/* TODO: this is incorrect need it per transactions not per user */}
|
||||
{/* last four of credit card */}
|
||||
<td>{lastFour}</td>
|
||||
|
@ -108,4 +127,4 @@ const WalletBalance = (props: Props) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default WalletBalance;
|
||||
export default WalletFiatPaymentHistory;
|
||||
|
|
|
@ -19,6 +19,7 @@ export const REWARD_GENERATED_CODE = 'reward_generated_code';
|
|||
export const AFFIRM_PURCHASE = 'affirm_purchase';
|
||||
export const CONFIRM_CLAIM_REVOKE = 'confirm_claim_revoke';
|
||||
export const SEND_TIP = 'send_tip';
|
||||
export const PREORDER_CONTENT = 'preorder_content';
|
||||
export const CONFIRM_SEND_TIP = 'confirm_send_tip';
|
||||
export const REPOST = 'repost';
|
||||
export const SOCIAL_SHARE = 'social_share';
|
||||
|
|
9
ui/modal/modalPreorderContent/index.js
Normal file
9
ui/modal/modalPreorderContent/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import ModalSendTip from './view';
|
||||
|
||||
const perform = {
|
||||
doHideModal,
|
||||
};
|
||||
|
||||
export default connect(null, perform)(ModalSendTip);
|
28
ui/modal/modalPreorderContent/view.jsx
Normal file
28
ui/modal/modalPreorderContent/view.jsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Modal } from 'modal/modal';
|
||||
import PreorderContent from 'component/preorderContent';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
doHideModal: () => void,
|
||||
checkIfAlreadyPurchased: () => void,
|
||||
};
|
||||
|
||||
class ModalSendTip extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { uri, doHideModal, checkIfAlreadyPurchased } = this.props;
|
||||
|
||||
return (
|
||||
<Modal onAborted={doHideModal} isOpen type="card">
|
||||
<PreorderContent
|
||||
uri={uri}
|
||||
onCancel={doHideModal}
|
||||
checkIfAlreadyPurchased={checkIfAlreadyPurchased}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ModalSendTip;
|
|
@ -38,6 +38,7 @@ const MAP = Object.freeze({
|
|||
[MODALS.MIN_CHANNEL_AGE]: lazyImport(() => import('modal/modalMinChannelAge' /* webpackChunkName: "modalMinChannelAge" */)),
|
||||
[MODALS.MOBILE_SEARCH]: lazyImport(() => import('modal/modalMobileSearch' /* webpackChunkName: "modalMobileSearch" */)),
|
||||
[MODALS.PHONE_COLLECTION]: lazyImport(() => import('modal/modalPhoneCollection' /* webpackChunkName: "modalPhoneCollection" */)),
|
||||
[MODALS.PREORDER_CONTENT]: lazyImport(() => import('modal/modalPreorderContent' /* webpackChunkName: "modalPreorderContent" */)),
|
||||
[MODALS.PUBLISH]: lazyImport(() => import('modal/modalPublish' /* webpackChunkName: "modalPublish" */)),
|
||||
[MODALS.PUBLISH_PREVIEW]: lazyImport(() => import('modal/modalPublishPreview' /* webpackChunkName: "modalPublishPreview" */)),
|
||||
[MODALS.REPOST]: lazyImport(() => import('modal/modalRepost' /* webpackChunkName: "modalRepost" */)),
|
||||
|
|
|
@ -16,6 +16,7 @@ import Button from 'component/button';
|
|||
import Empty from 'component/common/empty';
|
||||
import SwipeableDrawer from 'component/swipeableDrawer';
|
||||
import DrawerExpandButton from 'component/swipeableDrawerExpand';
|
||||
import PreorderButton from 'component/preorderButton';
|
||||
import { useIsMobile, useIsMobileLandscape } from 'effects/use-screensize';
|
||||
|
||||
const CommentsList = lazyImport(() => import('component/commentsList' /* webpackChunkName: "comments" */));
|
||||
|
@ -224,6 +225,7 @@ export default function FilePage(props: Props) {
|
|||
{!isMarkdown && (
|
||||
<div className="file-page__secondary-content">
|
||||
<section className="file-page__media-actions">
|
||||
<PreorderButton uri={uri} />
|
||||
{claimIsMine && isLivestream && (
|
||||
<div className="livestream__creator-message">
|
||||
<h4>{__('Only visible to you')}</h4>
|
||||
|
|
|
@ -797,3 +797,55 @@ export const doSendCashTip = (
|
|||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const preOrderPurchase = (
|
||||
tipParams,
|
||||
anonymous,
|
||||
userParams,
|
||||
claimId,
|
||||
stripeEnvironment,
|
||||
preferredCurrency,
|
||||
successCallback,
|
||||
failureCallback
|
||||
) => (dispatch) => {
|
||||
Lbryio.call(
|
||||
'customer',
|
||||
'tip',
|
||||
{
|
||||
// round to fix issues with floating point numbers
|
||||
amount: Math.round(100 * tipParams.tipAmount), // convert from dollars to cents
|
||||
creator_channel_name: tipParams.tipChannelName, // creator_channel_name
|
||||
creator_channel_claim_id: tipParams.channelClaimId,
|
||||
tipper_channel_name: userParams.activeChannelName,
|
||||
tipper_channel_claim_id: userParams.activeChannelId,
|
||||
currency: preferredCurrency || 'USD',
|
||||
anonymous: anonymous,
|
||||
source_claim_id: claimId,
|
||||
environment: stripeEnvironment,
|
||||
},
|
||||
'post'
|
||||
)
|
||||
.then((customerTipResponse) => {
|
||||
dispatch(
|
||||
doToast({
|
||||
message: __('Preorder completed successfully'),
|
||||
subMessage: __("You will be able to see the content as soon as it's available!"),
|
||||
// linkText: `${fiatSymbol}${tipParams.tipAmount} ⇒ ${tipParams.tipChannelName}`,
|
||||
// linkTarget: '/wallet',
|
||||
})
|
||||
);
|
||||
|
||||
if (successCallback) successCallback(customerTipResponse);
|
||||
})
|
||||
.catch((error) => {
|
||||
// show error message from Stripe if one exists (being passed from backend by Beamer's API currently)
|
||||
dispatch(
|
||||
doToast({
|
||||
message: error.message || __('Sorry, there was an error in processing your payment!'),
|
||||
isError: true,
|
||||
})
|
||||
);
|
||||
|
||||
if (failureCallback) failureCallback(error);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -680,6 +680,11 @@ export const selectTagsForUri = createCachedSelector(selectMetadataForUri, (meta
|
|||
return metadata && metadata.tags ? metadata.tags.filter((tag) => !INTERNAL_TAGS.includes(tag)) : [];
|
||||
})((state, uri) => String(uri));
|
||||
|
||||
export const selectPreorderTag = createCachedSelector(selectMetadataForUri, (metadata: ?GenericMetadata) => {
|
||||
const matchingTag = metadata && metadata.tags && metadata.tags.find((tag) => tag.includes('preorder:'));
|
||||
if (matchingTag) return matchingTag.slice(9);
|
||||
})((state, uri) => String(uri));
|
||||
|
||||
export const selectFetchingClaimSearchByQuery = (state: State) => selectState(state).fetchingClaimSearchByQuery || {};
|
||||
|
||||
export const selectFetchingClaimSearch = createSelector(
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
@import 'component/notification';
|
||||
@import 'component/nudge';
|
||||
@import 'component/pagination';
|
||||
@import 'component/preorder-button';
|
||||
@import 'component/post';
|
||||
@import 'component/purchase';
|
||||
@import 'component/placeholder';
|
||||
|
|
55
ui/scss/component/_preorder-button.scss
Normal file
55
ui/scss/component/_preorder-button.scss
Normal file
|
@ -0,0 +1,55 @@
|
|||
.preorder-button {
|
||||
background-color: var(--color-primary);
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
|
||||
svg {
|
||||
stroke: white;
|
||||
}
|
||||
}
|
||||
|
||||
.preorder-content-modal {
|
||||
max-width: 580px;
|
||||
}
|
||||
|
||||
section.preorder-content-modal {
|
||||
h2.card__title, .section__subtitle, .handle-submit-area {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card__title-section {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.section__subtitle {
|
||||
max-width: 421px;
|
||||
}
|
||||
|
||||
.card__main-actions {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.add-card-prompt {
|
||||
margin: 0 auto;
|
||||
margin-top: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
section.preorder-content-modal-loading {
|
||||
h2.card__title, .section__subtitle {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card__title-section {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.section__subtitle {
|
||||
margin-top: 27px;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue