Skip to content

Commit 1474688

Browse files
Merge pull request #10 from ElementsProject/list-offers
List Offers #7
2 parents 4881e16 + 089df24 commit 1474688

16 files changed

+373
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@import '../../../styles/constants.scss';
2+
3+
.btc-transactions-tabs {
4+
border-bottom: 0.5px solid $border-color;
5+
}
6+
7+
@include color-mode(dark) {
8+
.btc-transactions-tabs {
9+
border-bottom: 0.5px solid $border-color-dark;
10+
padding: 0 2rem 0 0.25rem;
11+
}
12+
}

apps/frontend/src/components/cln/BTCWallet/BTCWallet.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const BTCWallet = (props) => {
4848
</ButtonGroup>
4949
</Card>
5050
<Card.Body className='px-4 list-scroll-container'>
51-
<div className='text-light'>Transactions</div>
51+
<div className='text-light btc-transactions-tabs'>Transactions</div>
5252
<PerfectScrollbar className='ps-show-always'>
5353
<BTCTransactionsList />
5454
</PerfectScrollbar>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@import '../../../styles/constants.scss';
2+
3+
.cln-offer-placeholder {
4+
transform-origin: top left;
5+
& .cln-offer-detail {
6+
align-items: center;
7+
margin: 0.5rem;
8+
}
9+
& .cln-offer-copy {
10+
display: inline-flex;
11+
align-items: flex-start;
12+
padding: 0;
13+
cursor: pointer;
14+
&:hover {
15+
svg path {
16+
stroke: darken($primary, 10%);
17+
}
18+
}
19+
}
20+
& .cln-offer-open {
21+
display: inline-flex;
22+
align-items: flex-start;
23+
padding: 0;
24+
cursor: pointer;
25+
&:hover {
26+
svg path {
27+
fill: darken($primary, 10%);
28+
}
29+
}
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { render, screen } from '@testing-library/react';
2+
import CLNOffer from './CLNOffer';
3+
4+
describe('CLNOffer component ', () => {
5+
beforeEach(() => render(<CLNOffer />));
6+
7+
it('should be in the document', () => {
8+
// expect(screen.getByTestId('header-context')).toBeInTheDocument();
9+
});
10+
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import './CLNOffer.scss';
2+
import { useContext } from 'react';
3+
import { motion } from 'framer-motion';
4+
import Row from 'react-bootstrap/esm/Row';
5+
import Col from 'react-bootstrap/esm/Col';
6+
7+
import { AppContext } from '../../../store/AppContext';
8+
import { CopySVG } from '../../../svgs/Copy';
9+
import { TRANSITION_DURATION } from '../../../utilities/constants';
10+
import { copyTextToClipboard } from '../../../utilities/data-formatters';
11+
import logger from '../../../services/logger.service';
12+
13+
const OfferDetail = ({offer, copyHandler}) => {
14+
return (
15+
<>
16+
{offer.bolt12 ?
17+
<Row className='cln-offer-detail'>
18+
<Col xs={12} className='fs-7 text-light'>Bolt 12</Col>
19+
<Col xs={11} className='pe-1 fs-7 overflow-x-ellipsis'>{offer.bolt12}</Col>
20+
<Col xs={1} onClick={copyHandler} className='cln-offer-copy' id='Bolt12'><CopySVG id='Bolt12' showTooltip={true} /></Col>
21+
</Row>
22+
:
23+
<></>
24+
}
25+
</>
26+
)
27+
};
28+
29+
const CLNOffer = (props) => {
30+
const appCtx = useContext(AppContext);
31+
32+
const copyHandler = (event) => {
33+
let textToCopy = '';
34+
switch (event.target.id) {
35+
case 'Bolt12':
36+
textToCopy = props.offer.bolt12;
37+
break;
38+
default:
39+
textToCopy = props.offer.bolt12;
40+
break;
41+
}
42+
copyTextToClipboard(textToCopy).then((response) => {
43+
appCtx.setShowToast({show: true, message: (event.target.id + ' Copied Successfully!'), bg: 'success'});
44+
}).catch((err) => {
45+
logger.error(err);
46+
});
47+
}
48+
49+
return (
50+
<motion.div
51+
variants={{ collapsed: { scale: 0.8, opacity: 0 }, open: { scale: 1, opacity: 1 } }}
52+
transition={{ duration: TRANSITION_DURATION }}
53+
className='cln-offer-placeholder pb-2'
54+
>
55+
<OfferDetail offer={props.offer} copyHandler={copyHandler} />
56+
</motion.div>
57+
);
58+
};
59+
60+
export default CLNOffer;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
@import '../../../styles/constants.scss';
2+
3+
.cln-offers-list {
4+
cursor: pointer;
5+
display: flex;
6+
flex-direction: column;
7+
height: 100%;
8+
padding-right: 0.5rem;
9+
transition: all $transition-time ease;
10+
& .cln-offer-header {
11+
display: flex;
12+
flex-direction: column;
13+
border: none;
14+
margin-top: 0.5rem;
15+
border-radius: 0.675rem;
16+
padding: 0.5rem 1rem 0.5rem 0.125rem;
17+
transition: all $transition-time ease;
18+
&.expanded {
19+
border-bottom: 2px dashed rgba($light, 0.2);
20+
border-bottom-left-radius: 0;
21+
border-bottom-right-radius: 0;
22+
}
23+
&:hover {
24+
background-color: $body-bg-light !important;
25+
}
26+
}
27+
& .cln-offer-details {
28+
transition: background-color $theme-transition ease;
29+
background-color: $body-bg-light;
30+
border-bottom-left-radius: 0.675rem;
31+
border-bottom-right-radius: 0.675rem;
32+
}
33+
}
34+
35+
@include color-mode(dark) {
36+
.cln-offers-list {
37+
& .cln-offer-header {
38+
&:hover {
39+
background-color: $body-bg-dark !important;
40+
}
41+
}
42+
& .cln-offer-details {
43+
background-color: $body-bg-dark;
44+
}
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { render, screen } from '@testing-library/react';
2+
import CLNOffersList from './CLNOffersList';
3+
4+
describe('CLNOffersList component ', () => {
5+
beforeEach(() => render(<CLNOffersList />));
6+
7+
it('should be in the document', () => {
8+
// expect(screen.getByTestId('header-context')).toBeInTheDocument();
9+
});
10+
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import './CLNOffersList.scss';
2+
import { useContext, useState } from 'react';
3+
import { motion, AnimatePresence } from 'framer-motion';
4+
import Spinner from 'react-bootstrap/Spinner';
5+
import Alert from 'react-bootstrap/Alert';
6+
import Row from 'react-bootstrap/esm/Row';
7+
import Col from 'react-bootstrap/esm/Col';
8+
9+
import { AppContext } from '../../../store/AppContext';
10+
import { IncomingArrowSVG } from '../../../svgs/IncomingArrow';
11+
import Offer from '../CLNOffer/CLNOffer';
12+
import { ApplicationModes, TRANSITION_DURATION } from '../../../utilities/constants';
13+
import { NoCLNTransactionLightSVG } from '../../../svgs/NoCLNTransactionLight';
14+
import { NoCLNTransactionDarkSVG } from '../../../svgs/NoCLNTransactionDark';
15+
16+
const OfferHeader = ({offer}) => {
17+
return (
18+
<Row className='offer-list-item d-flex justify-content-between align-items-center'>
19+
<Col xs={2}>
20+
<IncomingArrowSVG className='me-1' txStatus={offer.used ? 'used' : 'unused'} />
21+
</Col>
22+
<Col xs={10}>
23+
<Row className='d-flex justify-content-between align-items-center'>
24+
<Col xs={12} className='ps-2 d-flex align-items-center'>
25+
<span className='text-dark fw-bold overflow-x-ellipsis'>{offer.label || offer.offer_id}</span>
26+
</Col>
27+
</Row>
28+
<Row className='d-flex justify-content-between align-items-center'>
29+
<Col xs={8} className='ps-2 pe-0 fs-7 text-light d-flex flex-row align-items-center'>
30+
<span className='text-dark fw-bold overflow-x-ellipsis'>{offer.active ? 'Active' : 'Inactive'}</span>
31+
</Col>
32+
<Col xs={4} className='ps-0 fs-7 text-light d-flex align-items-center justify-content-end'>
33+
<span className='text-dark fw-bold overflow-x-ellipsis'>{offer.single_use ? 'Single Use' : 'Multi Use'}</span>
34+
</Col>
35+
</Row>
36+
</Col>
37+
</Row>
38+
);
39+
};
40+
41+
const CLNOffersAccordion = ({ i, expanded, setExpanded, initExpansions, offer, appConfig, fiatConfig }) => {
42+
return (
43+
<>
44+
<motion.div
45+
className={'cln-offer-header ' + (expanded[i] ? 'expanded' : '')}
46+
initial={false}
47+
animate={{ backgroundColor: ((appConfig.appMode === ApplicationModes.DARK) ? (expanded[i] ? '#0C0C0F' : '#2A2A2C') : (expanded[i] ? '#EBEFF9' : '#FFFFFF')) }}
48+
transition={{ duration: TRANSITION_DURATION }}
49+
onClick={() => { initExpansions[i]=!expanded[i]; return setExpanded(initExpansions); }}>
50+
<OfferHeader offer={offer} />
51+
</motion.div>
52+
<AnimatePresence initial={false}>
53+
{expanded[i] && (
54+
<motion.div
55+
className='cln-offer-details'
56+
key='content'
57+
initial='collapsed'
58+
animate='open'
59+
exit='collapsed'
60+
variants={{
61+
open: { opacity: 1, height: 'auto' },
62+
collapsed: { opacity: 0, height: 0 }
63+
}}
64+
transition={{ duration: TRANSITION_DURATION, ease: [0.4, 0.52, 0.83, 0.98] }}
65+
>
66+
<Offer offer={offer} />
67+
</motion.div>
68+
)}
69+
</AnimatePresence>
70+
</>
71+
);
72+
};
73+
74+
export const CLNOffersList = () => {
75+
const appCtx = useContext(AppContext);
76+
const initExpansions = (appCtx.listOffers.offers?.reduce((acc: boolean[], curr) => [...acc, false], []) || []);
77+
const [expanded, setExpanded] = useState<boolean[]>(initExpansions);
78+
79+
return (
80+
appCtx.listOffers.isLoading ?
81+
<span className='h-100 d-flex justify-content-center align-items-center'>
82+
<Spinner animation='grow' variant='primary' />
83+
</span>
84+
:
85+
appCtx.listOffers.error ?
86+
<Alert className='py-0 px-1 fs-7' variant='danger'>{appCtx.listOffers.error}</Alert> :
87+
appCtx.listOffers?.offers && appCtx.listOffers?.offers.length && appCtx.listOffers?.offers.length > 0 ?
88+
<div className='cln-offers-list'>
89+
{
90+
appCtx.listOffers?.offers?.map((offer, i) => (
91+
<CLNOffersAccordion key={i} i={i} expanded={expanded} setExpanded={setExpanded} initExpansions={initExpansions} offer={offer} appConfig={appCtx.appConfig} fiatConfig={appCtx.fiatConfig} />
92+
))
93+
}
94+
</div>
95+
:
96+
<Row className='text-light fs-6 h-75 mt-2 align-items-center justify-content-center'>
97+
<Row className='d-flex align-items-center justify-content-center'>
98+
{ appCtx.appConfig.appMode === ApplicationModes.DARK ?
99+
<NoCLNTransactionDarkSVG className='no-clntx-dark pb-1' /> :
100+
<NoCLNTransactionLightSVG className='no-clntx-light pb-1' />
101+
}
102+
<Row className='text-center'>No offer found. Click receive to generate new offer!</Row>
103+
</Row>
104+
</Row>
105+
);
106+
};
107+
108+
export default CLNOffersList;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
@import '../../../styles/constants.scss';
2+
3+
.nav.cln-transactions-tabs {
4+
border: none;
5+
border-bottom: 0.5px solid $border-color;
6+
border-left: none;
7+
border-right: none;
8+
border-top: none;
9+
& .nav-item .nav-link {
10+
padding: 0 2rem 0 0.25rem;
11+
color: $light;
12+
&.active, &:hover, &:focus {
13+
background-color: transparent;
14+
border-bottom: 1px solid $primary;
15+
}
16+
}
17+
}
18+
19+
@include color-mode(dark) {
20+
.nav.cln-transactions-tabs {
21+
border-bottom: 0.5px solid $border-color-dark;
22+
& .nav-item .nav-link {
23+
color: $light-dark;
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)