|
| 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; |
0 commit comments