mirror of
https://github.com/yzernik/squeaknode.git
synced 2026-08-13 12:33:25 +02:00
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:
parent
96546f0e5c
commit
552cfcbecb
3 changed files with 206 additions and 104 deletions
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
128
frontend/src/features/squeaks/BuySqueak.js
Normal file
128
frontend/src/features/squeaks/BuySqueak.js
Normal 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>:
|
||||
<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)
|
||||
|
|
@ -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>:
|
||||
<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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue