diff --git a/package.json b/package.json index 0ad9919..bade6e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/poap-family", - "version": "1.13.13", + "version": "1.13.14", "author": { "name": "POAP", "url": "https://poap.xyz" diff --git a/src/components/AddressProfile.js b/src/components/AddressProfile.js index fb18575..2e09114 100644 --- a/src/components/AddressProfile.js +++ b/src/components/AddressProfile.js @@ -3,7 +3,7 @@ import { useContext, useEffect, useState } from 'react' import { LazyImage } from 'react-lazy-images' import { clsx } from 'clsx' import { formatMonthYear } from 'utils/date' -import { POAP_SCAN_URL, findInitialPOAPDate } from 'models/poap' +import { findInitialPOAPDate } from 'models/poap' import { DropProps } from 'models/drop' import { INCOMMON_ADDRESSES_LIMIT, @@ -12,7 +12,7 @@ import { import { POAP_PROFILE_LIMIT } from 'models/poap' import { ResolverEnsContext, ReverseEnsContext } from 'stores/ethereum' import { scanAddress } from 'loaders/poap' -import ExternalLink from 'components/ExternalLink' +import LinkToScan from 'components/LinkToScan' import AddressesList from 'components/AddressesList' import TokenImage from 'components/TokenImage' import ButtonLink from 'components/ButtonLink' @@ -183,13 +183,7 @@ function AddressProfile({ )} /> )} - - {address} - + {address in ensNames && ( {ensNames[address]} )} diff --git a/src/components/Button.js b/src/components/Button.js index 36e2516..695016f 100644 --- a/src/components/Button.js +++ b/src/components/Button.js @@ -20,6 +20,7 @@ function Button({ className={clsx('button', { active, disabled, + primary: !secondary, secondary, borderless, })} diff --git a/src/components/ButtonAddressProfile.js b/src/components/ButtonAddressProfile.js index c9197d1..b4612a3 100644 --- a/src/components/ButtonAddressProfile.js +++ b/src/components/ButtonAddressProfile.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types' import { useContext, useState } from 'react' -import ReactModal from 'react-modal' import { ReverseEnsContext } from 'stores/ethereum' import { DropProps } from 'models/drop' +import Modal from 'components/Modal' import Card from 'components/Card' import ButtonClose from 'components/ButtonClose' import ButtonLink from 'components/ButtonLink' @@ -36,15 +36,12 @@ function ButtonAddressProfile({ : {address} } - setShowModal(false)} - shouldCloseOnEsc={true} - shouldCloseOnOverlayClick={true} - contentLabel={address in ensNames ? ensNames[address] : address} - className="button-address-profile-modal" + setShowModal(false)} + title={address in ensNames ? ensNames[address] : address} > -
+
setShowModal(false)} />
- + ) } diff --git a/src/components/CachedEventList.js b/src/components/CachedEventList.js index 21df09e..6efadf0 100644 --- a/src/components/CachedEventList.js +++ b/src/components/CachedEventList.js @@ -1,10 +1,9 @@ import PropTypes from 'prop-types' -import { Link } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' import { CachedEventProps } from 'models/api' import Timestamp from 'components/Timestamp' -import ButtonLink from 'components/ButtonLink' -import TokenImage from 'components/TokenImage' -import 'styles/cached-drops.css' +import TokenImageZoom from 'components/TokenImageZoom' +import 'styles/cached-event-list.css' /** * @param {PropTypes.InferProps} props @@ -15,48 +14,67 @@ function CachedEventList({ tokenImageSize = 48, showCachedTs = true, showInCommonCount = true, - showClear = true, - onClear = - /** - * @param {number} eventId - */ - (eventId) => {}, }) { + const navigate = useNavigate() + return ( -
    +
      {cachedEvents.map((cachedEvent, index) => -
    • -
      -
      - - +
    • { + /** + * @type {HTMLElement | null} + */ + // @ts-ignore + let target = event.target + while ( + target != null && + target.nodeName !== 'A' && + target.nodeName !== 'BUTTON' && + target.nodeName !== 'LI' + ) { + target = target.parentElement + } + if (target != null && target.nodeName === 'LI') { + navigate(`/event/${cachedEvent.id}`) + } + }} + > +
      +
      + + + #{cachedEvent.id}
      -
      -
      -

      {cachedEvent.name}

      - {showClear && ( - onClear != null && onClear(cachedEvent.id)} - > - clear - - )} -
      -
      - {showCachedTs && -

      - Cached -

      } - {showInCommonCount && -

      - - {cachedEvent.in_common_count} - - {' '} - in common -

      } -
      +
      +

      {cachedEvent.name}

      + {(showCachedTs || showInCommonCount) && ( +
      + {showCachedTs && ( +
      + Cached +
      + )} + {showInCommonCount && ( +
      + + {cachedEvent.in_common_count} + + {' '} + in common +
      + )} +
      + )}
    • @@ -73,8 +91,6 @@ CachedEventList.propTypes = { tokenImageSize: PropTypes.number, showCachedTs: PropTypes.bool, showInCommonCount: PropTypes.bool, - showClear: PropTypes.bool, - onClear: PropTypes.func, } export default CachedEventList diff --git a/src/components/EventInfo.js b/src/components/EventInfo.js index 96f809c..bec584e 100644 --- a/src/components/EventInfo.js +++ b/src/components/EventInfo.js @@ -18,7 +18,7 @@ import 'styles/event-info.css' */ function EventInfo({ event, - stats = {}, + stats, highlightStat, buttons = [], children, diff --git a/src/components/LastEvents.js b/src/components/LastEvents.js index 9ee333f..4078e62 100644 --- a/src/components/LastEvents.js +++ b/src/components/LastEvents.js @@ -174,7 +174,6 @@ function LastEvents({ {cachedEvents.length > 0 && !loading && ( )} {error && !loading && diff --git a/src/components/Modal.js b/src/components/Modal.js new file mode 100644 index 0000000..bc74eb8 --- /dev/null +++ b/src/components/Modal.js @@ -0,0 +1,31 @@ +import PropTypes from 'prop-types' +import ReactModal from 'react-modal' +import 'styles/modal.css' + +/** + * @param {PropTypes.InferProps} props + */ +function Modal({ show, onClose, children, title }) { + return ( + onClose()} + shouldCloseOnEsc={true} + shouldCloseOnOverlayClick={true} + contentLabel={title} + className="modal" + overlayClassName="overlay" + > + {children} + + ) +} + +Modal.propTypes = { + show: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + children: PropTypes.node.isRequired, + title: PropTypes.string, +} + +export default Modal diff --git a/src/components/Search.js b/src/components/Search.js index c7bdb29..4789288 100644 --- a/src/components/Search.js +++ b/src/components/Search.js @@ -114,6 +114,9 @@ function Search() { [] // eslint-disable-line react-hooks/exhaustive-deps ) + /** + * @param {number} eventId + */ const findEvent = (eventId) => { const controller = new AbortController() setLoadingById({ @@ -133,7 +136,7 @@ function Search() { if (result) { setEventById(result) } else { - setErrorById(new Error(`Event ${eventId} not found`)) + setErrorById(new Error(`Drop ${eventId} not found`)) } }, (err) => { @@ -152,6 +155,10 @@ function Search() { ) } + /** + * @param {string} value + * @param {number} page + */ const search = (value, page = 1) => { setQueryPage(page) setErrorSearch(null) @@ -340,7 +347,7 @@ function Search() { () => { search(value, 1) if (/^[0-9]+$/.test(value)) { - findEvent(value) + findEvent(parseInt(value)) } else { setErrorById(null) setEventById(null) @@ -460,23 +467,6 @@ function Search() { setErrorSubmit(null) } - const selectedNotInEvents = selectedEvents - .filter( - (selected) => -1 === queryEvents.findIndex( - (queried) => queried.id === selected.id - ) - ) - const selectedNotInCollections = selectedCollections - .filter( - (selected) => -1 === queryCollections.findIndex( - (queried) => queried.id === selected.id - ) - ) - const selectedCollectionsTotalDrops = selectedCollections.reduce( - (total, collection) => total + collection.dropIds.length, - 0 - ) - /** * @param {{ id: number; name: string; description?: string; image_url: string; original_url: string; city: string | null; country: string | null; start_date: string; end_date: string; expiry_date: string }} event */ @@ -498,7 +488,10 @@ function Search() { (selected) => selected.id === event.id )} onChange={(changeEvent) => { - onSelectEventChange(event.id, !!changeEvent.target.checked) + onSelectEventChange( + event.id, + changeEvent.target.checked + ) }} />
@@ -554,7 +547,7 @@ function Search() { onChange={(changeEvent) => { onSelectCollectionChange( collection.id, - !!changeEvent.target.checked + changeEvent.target.checked ) }} /> @@ -563,148 +556,190 @@ function Search() { ) - const pages = Math.ceil( - Math.max(queryTotal, queryTotalCollections) / SEARCH_LIMIT + const selectedNotInEvents = selectedEvents.filter( + (selected) => -1 === queryEvents.findIndex( + (queried) => queried.id === selected.id + ) + ) + const selectedNotInCollections = selectedCollections.filter( + (selected) => -1 === queryCollections.findIndex( + (queried) => queried.id === selected.id + ) ) + const selectedCollectionsTotalDrops = selectedCollections.reduce( + (total, collection) => total + collection.dropIds.length, + 0 + ) + + const maxTotal = Math.max(queryTotal, queryTotalCollections) + const pages = Math.ceil(maxTotal / SEARCH_LIMIT) + + /** + * @param {number} newPage + */ + const onPageChange = (newPage) => { + const value = queryRef.current ? queryRef.current.value : '' + if (value.length > 0) { + search(value, newPage) + } + } return ( - -
{ - event.preventDefault() - onSearch() - }} - > -
- onQueryChange()} - onKeyUp={(event) => onQueryKeyUp(event.keyCode)} - autoComplete="off" - maxLength={256} - size={24} - /> - -
-
- {( - !errorById && - !errorSearch && - !errorSearchCollections && - !errorSubmit && - selectedEvents.length === 0 && - selectedCollections.length === 0 && - queryEvents.length === 0 && - queryCollections.length === 0 && - !loadingById.state && - !loadingSearch.state && - !loadingSearchCollections.state && - !eventById - ) && ( -
- manually enter collections -
- )} - {errorById && queryEvents.length === 0 && queryCollections.length === 0 && ( -
-

{errorById.message}

-
- )} - {errorSearch && !eventById && ( -
-

{errorSearch.message}

-
- )} - {errorSearchCollections && !eventById && ( -
-

{errorSearchCollections.message}

-
- )} - {errorSubmit && ( -
-

{errorSubmit.message}

-
- )} - {( - selectedEvents.length > 0 || - selectedCollections.length > 0 || - queryEvents.length > 0 || - queryCollections.length > 0 - ) && ( -
- {selectedCollections.length > 0 && ( - <> +
+ +
{ + event.preventDefault() + onSearch() + }} + > +
+ onQueryChange()} + onKeyUp={(event) => onQueryKeyUp(event.keyCode)} + autoComplete="off" + maxLength={256} + size={24} + /> + +
+
+ {( + !errorById && + !errorSearch && + !errorSearchCollections && + !errorSubmit && + selectedEvents.length === 0 && + selectedCollections.length === 0 && + queryEvents.length === 0 && + queryCollections.length === 0 && + !loadingById.state && + !loadingSearch.state && + !loadingSearchCollections.state && + !eventById + ) && ( +
+ + manually enter collections + +
+ )} + {errorById && queryEvents.length === 0 && queryCollections.length === 0 && ( +
+

{errorById.message}

+
+ )} + {errorSearch && !eventById && ( +
+

{errorSearch.message}

+
+ )} + {errorSearchCollections && !eventById && ( +
+

{errorSearchCollections.message}

+
+ )} + {errorSubmit && ( +
+

{errorSubmit.message}

+
+ )} + {( + selectedEvents.length > 0 || + selectedCollections.length > 0 || + queryEvents.length > 0 || + queryCollections.length > 0 + ) && ( +
+ {selectedCollections.length > 0 && (

- {selectedCollections.length} collection{selectedCollections.length === 1 ? '' : 's'} w/{selectedCollectionsTotalDrops} drop{selectedCollectionsTotalDrops === 1 ? '' : 's'} = + {selectedCollections.length}{' '} + collection{selectedCollections.length === 1 ? '' : 's'}{' '} + w/{selectedCollectionsTotalDrops}{' '} + drop{selectedCollectionsTotalDrops === 1 ? '' : 's'} =

- - )} -

{selectedEvents.length + selectedCollectionsTotalDrops} drop{selectedEvents.length + selectedCollectionsTotalDrops === 1 ? '' : 's'}

-
- )} - {selectedNotInCollections.length > 0 && ( - <>{selectedNotInCollections.map((collection) => renderCollection(collection))} - )} - {selectedNotInEvents.length > 0 && ( - <>{selectedNotInEvents.map((event) => renderEvent(event))} - )} - {( - selectedNotInCollections.length > 0 || - selectedNotInEvents.length > 0 - ) && ( - queryEvents.length > 0 || - queryCollections.length > 0 || - loadingById.state || - loadingSearch.state || - loadingSearchCollections.state - ) && ( -
- )} - {(loadingById.state || loadingSearch.state || loadingSearchCollections.state) && ( -
-
-
{' '}
-
+ )} +

+ {selectedEvents.length + selectedCollectionsTotalDrops}{' '} + drop{selectedEvents.length + selectedCollectionsTotalDrops === 1 ? '' : 's'} +

-
- )} - {eventById && renderEvent(eventById)} - {queryCollections.length > 0 && ( - queryCollections.map((collection) => renderCollection(collection)) - )} - {queryEvents.length > 0 && ( - queryEvents.map((event) => (!eventById || eventById.id !== event.id) && ( - renderEvent(event) - )) - )} - {queryEvents.length > 0 && pages > 1 && ( -
- { - const value = queryRef.current ? queryRef.current.value : '' - if (value.length > 0) { - search(value, newPage) - } - }} - /> -
- )} - + )} + {selectedNotInCollections.length > 0 && ( + selectedNotInCollections.map( + (collection) => renderCollection(collection) + ) + )} + {selectedNotInEvents.length > 0 && ( + selectedNotInEvents.map( + (event) => renderEvent(event) + ) + )} + {( + selectedNotInCollections.length > 0 || + selectedNotInEvents.length > 0 + ) && ( + queryEvents.length > 0 || + queryCollections.length > 0 || + loadingById.state || + loadingSearch.state || + loadingSearchCollections.state + ) && ( +
+ )} + {( + loadingById.state || + loadingSearch.state || + loadingSearchCollections.state + ) && ( +
+
+
{' '}
+
+
+
+ )} + {eventById && ( + renderEvent(eventById) + )} + {queryCollections.length > 0 && ( + queryCollections.map( + (collection) => renderCollection(collection) + ) + )} + {queryEvents.length > 0 && ( + queryEvents.map((event) => ( + !eventById || + eventById.id !== event.id + ) && ( + renderEvent(event) + )) + )} + {queryEvents.length > 0 && pages > 1 && ( +
+ +
+ )} + +
) } diff --git a/src/components/Status.js b/src/components/Status.js index cd32165..49e4099 100644 --- a/src/components/Status.js +++ b/src/components/Status.js @@ -10,6 +10,9 @@ function Status({ caching, error, }) { + if (!loading && !caching && !error) { + return null + } return (
} props @@ -19,34 +19,7 @@ function TokenImage({ : event.image_url if (resize) { - if (imgix) { - const url = new URL(imageUrl) - url.host = `poap${Math.trunc(getRandomInt(0, 10))}.imgix.net` - if (url.pathname.startsWith('/assets.poap.xyz')) { - url.pathname = url.pathname.substring('/assets.poap.xyz'.length) - } - if (size) { - imageUrl = url.toString() + `?w=${size}&h=${size}` - } else { - imageUrl = url.toString() - } - } else { - let poapSize = 'small' - - if (size <= 64) { - poapSize = 'xsmall' - } else if (size > 64 && size <= 128) { - poapSize = 'small' - } else if (size > 128 && size <= 256) { - poapSize = 'medium' - } else if (size > 256 && size <= 512) { - poapSize = 'large' - } else if (size > 512) { - poapSize = 'xlarge' - } - - imageUrl += `?size=${poapSize}` - } + imageUrl = resizeTokenImageUrl(imageUrl, size, imgix) } return ( @@ -54,8 +27,11 @@ function TokenImage({ src={imageUrl} alt={event.name} title={event.name} - className="poap" - style={{ width: `${imgSize ?? size}px`, height: `${imgSize ?? size}px` }} + className="token-image" + style={{ + width: `${imgSize ?? size}px`, + height: `${imgSize ?? size}px`, + }} /> ) } diff --git a/src/components/TokenImageZoom.js b/src/components/TokenImageZoom.js index a4aa250..f653e00 100644 --- a/src/components/TokenImageZoom.js +++ b/src/components/TokenImageZoom.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types' import { useState } from 'react' import { LazyImage } from 'react-lazy-images' -import ReactModal from 'react-modal' import { DropProps } from 'models/drop' +import Modal from 'components/Modal' import Card from 'components/Card' import ErrorMessage from 'components/ErrorMessage' import TokenImage from 'components/TokenImage' @@ -27,19 +27,16 @@ function TokenImageZoom({ event, size = 128, zoomSize = 512 }) { > - setShowModal(false)} - shouldCloseOnEsc={true} - shouldCloseOnOverlayClick={true} - contentLabel={event.name} - className="token-image-zoom-modal" + setShowModal(false)} + title={event.name} > ( -
+
setShowModal(false)} />