Allow buy squeak for squeak card (#2138)

* Got basic BuySqueak component working

* Fetch received offers from backend on click open buy dialog

* Allow unlock squeak from squeak card component

* Improve display of unlock button in squeak page
This commit is contained in:
Jonathan Zernik 2022-04-13 02:41:11 -04:00 committed by GitHub
parent 96546f0e5c
commit 552cfcbecb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 206 additions and 104 deletions

View file

@ -11,17 +11,20 @@ import axios from 'axios'
import {API_URL} from '../../config'
import MakeSqueak from '../../features/squeaks/MakeSqueak'
import DeleteSqueak from '../../features/squeaks/DeleteSqueak'
import BuySqueak from '../../features/squeaks/BuySqueak'
import ContentEditable from 'react-contenteditable'
import {
setLikeSqueak,
setUnlikeSqueak,
fetchSqueakOffers,
} from '../../features/squeaks/squeaksSlice'
const SqueakCard = React.memo(function SqueakCard(props) {
const [replyModalOpen, setModalOpen] = useState(false)
const [deleteModalOpen, setDeleteModalOpen] = useState(false)
const [buyModalOpen, setBuyModalOpen] = useState(false)
const [parent, setParent] = useState(false)
const [styleBody, setStyleBody] = useState(false)
const dispatch = useDispatch()
@ -68,6 +71,16 @@ const SqueakCard = React.memo(function SqueakCard(props) {
setTimeout(()=>{ setDeleteModalOpen(!deleteModalOpen) },20)
}
const toggleBuyModal = (e, type) => {
if(e){
e.preventDefault();
e.stopPropagation();
}
setStyleBody(!styleBody)
if(type === 'parent'){setParent(true)}else{setParent(false)}
setTimeout(()=>{ setBuyModalOpen(!buyModalOpen) },20)
}
const handleModalClick = (e) => {
e.stopPropagation()
@ -139,7 +152,13 @@ const SqueakCard = React.memo(function SqueakCard(props) {
<div className="card-content-info">
{props.squeak.getContentStr()}
</div> :
<div className="card-content-info card-content-locked-content">
<div onClick={(e)=> {
e.preventDefault();
dispatch(fetchSqueakOffers(props.id));
toggleBuyModal();
}
}
className="card-content-info card-content-locked-content">
<ICON_LOCKFILL styles={{width:'36px', height:"36px", padding: "5px"}} />
<div>
Locked content
@ -237,6 +256,25 @@ const SqueakCard = React.memo(function SqueakCard(props) {
</div> : null}
</div> : null}
{/* buy modal */}
{props.squeak ?
<div onClick={()=>toggleBuyModal()} style={{display: buyModalOpen ? 'block' : 'none'}} className="modal-edit">
{buyModalOpen ?
<div style={{minHeight: '350px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
<div className="modal-header">
<div className="modal-closeIcon">
<div onClick={()=>toggleBuyModal()} className="modal-closeIcon-wrap">
<ICON_CLOSE />
</div>
</div>
<p className="modal-title">Buy Squeak</p>
</div>
<div style={{marginTop:'5px'}} className="modal-body">
<BuySqueak squeakHash={props.id} submittedCallback={toggleBuyModal} />
</div>
</div> : null}
</div> : null}
</div>
)

View file

@ -0,0 +1,128 @@
import React, { useEffect, useState, useContext, useRef } from 'react'
import { withRouter } from 'react-router-dom'
import { unwrapResult } from '@reduxjs/toolkit'
import moment from 'moment'
import ContentEditable from 'react-contenteditable'
import { Link } from 'react-router-dom'
import { getProfileImageSrcString } from '../../squeakimages/images';
import Loader from '../../components/Loader'
import { Form, Input, Checkbox, Relevant, Debug, TextArea, Option } from 'informed';
import Select from 'react-select'
import { useSelector } from 'react-redux'
import { useDispatch } from 'react-redux'
import {
setBuySqueak,
selectBuySqueakStatus,
selectSqueakOffers,
fetchSqueakOffers,
} from '../squeaks/squeaksSlice'
import {
fetchPaymentSummaryForSqueak,
selectPaymentSummaryForSqueak,
} from '../../features/payments/paymentsSlice'
import {
selectSigningProfiles,
fetchSigningProfiles,
} from '../../features/profiles/profilesSlice'
const BuySqueak = (props) => {
console.log(props.squeakHash);
const signingProfiles = useSelector(selectSigningProfiles);
const buySqueakStatus = useSelector(selectBuySqueakStatus);
const squeakOffers = useSelector(selectSqueakOffers);
const dispatch = useDispatch();
const [offer, setOffer] = useState(null);
const buySqueak = (id) => {
const offerId = offer && offer.getOfferId();
if (!offerId) {
return;
}
const values = {
offerId: offerId,
squeakHash: props.squeakHash,
}
console.log('Buy clicked.');
dispatch(setBuySqueak(values))
.then(unwrapResult)
.then(() => {
dispatch(fetchPaymentSummaryForSqueak({squeakHash: props.squeakHash}));
})
.catch((err) => {
alert(err.message);
});
if (props.submittedCallback) {
props.submittedCallback();
}
}
const handleChangeOffer = (e) => {
setOffer(e.value);
}
const optionsFromOffers = (offers) => {
console.log("optionsFromOffers");
console.log(offers);
return offers.map((offer) => {
return { value: offer, label: `${offer.getPriceMsat() / 1000} sats (${offer.getPeerAddress().getHost()}:${offer.getPeerAddress().getPort()})` }
});
}
return (
<>
<div className="Squeak-input-side">
<div className="edit-input-wrap">
{squeakOffers.length} offers
<div className="inner-input-box">
<Select options={optionsFromOffers(squeakOffers)} onChange={handleChangeOffer} />
</div>
{offer &&
<>
<div className="inner-input-box">
<b>Price</b>: {offer.getPriceMsat() / 1000} sats
</div>
<div className="inner-input-box">
<b>Peer</b>: {offer.getPeerAddress().getHost()}:{offer.getPeerAddress().getPort()}
</div>
<div className="inner-input-box">
<b>Lightning Node</b>:&nbsp;
<a href={`https://amboss.space/node/${offer.getNodePubkey()}`}
target="_blank" rel="noopener noreferrer"
style={{color: "blue", fontWeight: 'bold'}}
>
{offer.getNodePubkey()}@{offer.getNodeHost()}:{offer.getNodePort()}
</a>
</div>
</>
}
<div className="inner-input-links">
<div className="input-links-side">
</div>
<div className="squeak-btn-holder">
<div onClick={buySqueak} className={offer ? 'squeak-btn-side squeak-btn-active' : 'squeak-btn-side'}>
Buy
</div>
</div>
</div>
</div>
</div>
</>
)
}
export default withRouter(BuySqueak)

View file

@ -14,6 +14,7 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
import Loader from '../../components/Loader'
import MakeSqueak from '../squeaks/MakeSqueak'
import DeleteSqueak from '../../features/squeaks/DeleteSqueak'
import BuySqueak from '../squeaks/BuySqueak'
import { unwrapResult } from '@reduxjs/toolkit'
@ -58,7 +59,6 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
const currentSqueak = useSelector(selectCurrentSqueak);
const ancestorSqueaks = useSelector(selectAncestorSqueaks);
const replySqueaks = useSelector(selectReplySqueaks);
const squeakOffers = useSelector(selectSqueakOffers);
const loadingCurrentSqueakStatus = useSelector(selectCurrentSqueakStatus)
const loadingAncestorSqueaksStatus = useSelector(selectAncestorSqueaksStatus)
const loadingReplySqueaksStatus = useSelector(selectReplySqueaksStatus)
@ -70,7 +70,6 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
const [buyModalOpen, setBuyModalOpen] = useState(false)
const [spendingModalOpen, setSpendingModalOpen] = useState(false)
const [tab, setTab] = useState('Sent Payments')
const [offer, setOffer] = useState(null)
const replySqueaksLimit = 10;
@ -83,7 +82,6 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
dispatch(fetchSqueak(props.id));
dispatch(fetchAncestorSqueaks(props.id));
dispatch(fetchReplySqueaks({squeakHash: props.id, limit: 9, lastSqueak: null}));
dispatch(fetchSqueakOffers(props.id));
dispatch(fetchPaymentSummaryForSqueak({squeakHash: props.id}));
}, [props.id])
@ -104,6 +102,7 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
const toggleBuyModal = () => {
// load offers on modal open.
if (!buyModalOpen) {
console.log('fetchSqueakOffers', props.id);
dispatch(fetchSqueakOffers(props.id));
}
// if(param === 'edit'){setSaved(false)}
@ -154,30 +153,7 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
}
}
const buySqueak = (id) => {
const offerId = offer && offer.getOfferId();
if (!offerId) {
return;
}
const values = {
offerId: offerId,
squeakHash: props.match.params.id,
}
console.log('Buy clicked.');
dispatch(setBuySqueak(values))
.then(unwrapResult)
.then(() => {
dispatch(fetchPaymentSummaryForSqueak({squeakHash: props.id}));
})
.catch((err) => {
alert(err.message);
});
toggleBuyModal();
}
const handleChangeOffer = (e) => {
setOffer(e.value);
}
// const ancestorSqueaks = [];
@ -186,11 +162,6 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
// const squeakOffers = [];
const optionsFromOffers = (offers) => {
return offers.map((offer) => {
return { value: offer, label: `${offer.getPriceMsat() / 1000} sats (${offer.getPeerAddress().getHost()}:${offer.getPeerAddress().getPort()})` }
});
}
const squeak = currentSqueak;
@ -225,13 +196,15 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
<div className="squeak-content">
{squeak.getContentStr()}
</div> :
<div className="squeak-content locked-content">
<Link>
<div onClick={()=>toggleBuyModal(props.match.params.id)}
className="squeak-content locked-content">
<ICON_LOCKFILL styles={{width:'48px', height:"48px", padding: "5px"}} />
<div onClick={()=>toggleBuyModal(props.match.params.id)}
className='squeak-unlock-button'>
<span>Unlock</span>
</div>
<div>
Locked content
</div>
</div>
</Link>
}
@ -377,57 +350,20 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
</div> : null}
</div>:null}
{squeak ?
<div onClick={()=>toggleBuyModal()} style={{display: buyModalOpen ? 'block' : 'none'}} className="modal-edit">
{buyModalOpen ?
<div style={{minHeight: '379px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
<div className="modal-header">
<div className="modal-closeIcon">
<div onClick={()=>toggleBuyModal()} className="modal-closeIcon-wrap">
<ICON_CLOSE />
</div>
</div>
<p className="modal-title">Buy Squeak</p>
</div>
<div style={{marginTop:'5px'}} className="modal-body">
<div className="Squeak-input-side">
<div className="edit-input-wrap">
{squeakOffers.length} offers.
<div className="inner-input-box">
<Select options={optionsFromOffers(squeakOffers)} onChange={handleChangeOffer} />
</div>
{offer &&
<>
<div className="inner-input-box">
<b>Price</b>: {offer.getPriceMsat() / 1000} sats
</div>
<div className="inner-input-box">
<b>Peer</b>: {offer.getPeerAddress().getHost()}:{offer.getPeerAddress().getPort()}
</div>
<div className="inner-input-box">
<b>Lightning Node</b>:&nbsp;
<a href={`https://amboss.space/node/${offer.getNodePubkey()}`}
target="_blank" rel="noopener noreferrer"
style={{color: "blue", fontWeight: 'bold'}}
>
{offer.getNodePubkey()}@{offer.getNodeHost()}:{offer.getNodePort()}
</a>
</div>
</>
}
<div className="inner-input-links">
<div className="input-links-side">
</div>
<div className="squeak-btn-holder">
<div onClick={buySqueak} className={offer ? 'squeak-btn-side squeak-btn-active' : 'squeak-btn-side'}>
Buy
</div>
</div>
</div>
{squeak ?
<div onClick={()=>toggleBuyModal()} style={{display: buyModalOpen ? 'block' : 'none'}} className="modal-edit">
{buyModalOpen ?
<div style={{minHeight: '379px', height: 'initial'}} onClick={(e)=>handleModalClick(e)} className="modal-content">
<div className="modal-header">
<div className="modal-closeIcon">
<div onClick={()=>toggleBuyModal()} className="modal-closeIcon-wrap">
<ICON_CLOSE />
</div>
</div>
<p className="modal-title">Buy Squeak</p>
</div>
<div style={{marginTop:'5px'}} className="modal-body">
<BuySqueak squeakHash={props.match.params.id} submittedCallback={toggleBuyModal} />
</div>
</div> : null}
</div>:null}
@ -456,29 +392,29 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
</div>
<div className="modal-scroll">
{tab === 'Sent Payments' ?
<>
<SentPayments squeakHash={props.id} />
</>
<>
<SentPayments squeakHash={props.id} />
</>
:
tab === 'Received Payments' ?
<>
<ReceivedPayments squeakHash={props.id} />
</>
: <div className="try-searching">
Nothing to see here ..
<div/>
Try searching for people, usernames, or keywords
<>
<ReceivedPayments squeakHash={props.id} />
</>
: <div className="try-searching">
Nothing to see here ..
<div/>
Try searching for people, usernames, or keywords
</div>
}
</div>
</div>
</div>
</div>}
}
</div>
</div>
</div>
</div>}
</>
}
</>
}
export default withRouter(Squeak)
export default withRouter(Squeak)