Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(marketplace): refacto filters and sort, feat(marketplace): add tab sold #532

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/components/Card/Card.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@
z-index: 1;
}

.sold {
font-size: 12px;
color: var(--color-dark-green);
font-weight: bold;

margin-bottom: 2px;
}

@media (max-width: $breakpoint-sm) {
.content {
padding: 5px;
Expand Down
20 changes: 18 additions & 2 deletions src/components/Card/ObjktCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,31 @@ import { Spacing } from "../Layout/Spacing"
import { Objkt } from "../../types/entities/Objkt"
import { getObjktUrl } from "../../utils/objkt"
import { GenTokFlag } from "../../types/entities/GenerativeToken"
import { useContext } from "react"
import React, { useContext, useMemo } from "react"
import { SettingsContext } from "../../context/Theme"
import { DisplayTezos } from "../Display/DisplayTezos"
import { EntityBadge } from "../User/EntityBadge"
import { format } from "date-fns"

interface Props {
objkt: Objkt
showOwner?: boolean
showRarity?: boolean
sold?: {
date: Date
price: number
}
}

export function ObjktCard({
objkt,
sold,
showOwner = true,
showRarity = false,
}: Props) {
const url = getObjktUrl(objkt)
const settings = useContext(SettingsContext)
const soldDate = useMemo(() => sold?.date && new Date(sold.date), [sold])
return (
<Link href={url} passHref>
<AnchorForward style={{ height: "100%" }}>
Expand All @@ -45,6 +52,14 @@ export function ObjktCard({
[WARNING: DUPLICATE]
</div>
)}
{soldDate && (
<div className={style.sold}>
<span>Sold </span>
<time dateTime={format(soldDate, "yyyy/MM/dd")}>
{format(soldDate, "d MMM yyyy @ HH:mm")}
</time>
</div>
)}
<h5 className={styleObjkt.title}>{objkt.name}</h5>
{showOwner && (
<>
Expand All @@ -67,12 +82,13 @@ export function ObjktCard({
<div className={cs(style.bottom)}>
<div className={cs(style.bottom_left)}>
<div className={cs(style.price)}>
{objkt.activeListing && (
{objkt.activeListing && !sold && (
<DisplayTezos
mutez={objkt.activeListing.price!}
formatBig={false}
/>
)}
{sold && <DisplayTezos mutez={sold.price} formatBig={false} />}
</div>
</div>
{objkt.issuer && (
Expand Down
20 changes: 15 additions & 5 deletions src/components/Gallery/GalleryMarketplace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,30 @@ import { SortAndFilters } from "../SortAndFilters/SortAndFilters"
import { getTagsFromFiltersObject } from "../../utils/filters"
import { ExploreTagDef } from "../Exploration/ExploreTags"
import { MarketplaceFilters } from "../../containers/Marketplace/MarketplaceFilters"
import { IOptions } from "../Input/Select"

const ITEMS_PER_PAGE = 40

interface Props {
initialSearchQuery?: string
initialSort?: string
initialFilters?: ListingFilters
defaultFilters?: ListingFilters
onChangeSearch?: (value: string) => void
onChangeFilters?: (updatedFilters: ListingFilters) => void
onChangeSort?: (updatedSort: string) => void
defaultSortOptions?: IOptions[]
}

export const GalleryMarketplace = ({
initialSearchQuery,
initialSort,
initialFilters,
defaultFilters,
onChangeSearch,
onChangeFilters,
onChangeSort,
defaultSortOptions = sortOptionsMarketplace,
}: Props) => {
const [hasNothingToFetch, setHasNothingToFetch] = useState(false)
const {
Expand All @@ -41,7 +46,7 @@ export const GalleryMarketplace = ({
restoreSort,
setSearchSortOptions,
sortOptions,
} = useSort(sortOptionsMarketplace, {
} = useSort(defaultSortOptions, {
defaultSort: initialSort,
defaultWithSearchOptions:
initialFilters && !!initialFilters["searchQuery_eq"],
Expand All @@ -64,6 +69,7 @@ export const GalleryMarketplace = ({
onChangeFilters?.(updatedFilters)
},
initialFilters,
defaultFilters,
})

// reference to an element at the top to scroll back
Expand Down Expand Up @@ -161,7 +167,6 @@ export const GalleryMarketplace = ({
options: sortOptions,
onChange: handleChangeSort,
}

return (
<div ref={topMarkerRef}>
<SortAndFilters
Expand All @@ -183,9 +188,14 @@ export const GalleryMarketplace = ({
<CardsContainer ref={refCardsContainer}>
{listings &&
listings.length > 0 &&
listings.map((offer) => (
<ObjktCard key={offer.id} objkt={offer.objkt} />
))}
listings.map((offer) => {
const sold = offer.acceptedAt
? { date: offer.acceptedAt, price: offer.price }
: undefined
return (
<ObjktCard sold={sold} key={offer.id} objkt={offer.objkt} />
)
})}
{loading &&
CardsLoading({
number: ITEMS_PER_PAGE,
Expand Down
112 changes: 12 additions & 100 deletions src/containers/Marketplace.tsx
Original file line number Diff line number Diff line change
@@ -1,122 +1,34 @@
import { ListingFilters } from "../types/entities/Listing"
import { useRouter } from "next/router"
import {
getSortFromUrlQuery,
getSortOptionsWithSearchOption,
sortOptionsMarketplace,
} from "../utils/sort"
import { GalleryMarketplace } from "../components/Gallery/GalleryMarketplace"
import { useEffect, useState } from "react"
import { buildUrlFromQuery } from "../utils/url"

interface Props {
urlQuery: Record<string, string>
}

// a map of (listing filter) => transformer
// to turn the query parameters into gql-ready variables
type TQueryFilterHandler = {
param: string
transform: (param?: string) => any | undefined
encode: (value?: any) => string | undefined
}
const queryListingFilterHandlers: Record<
keyof ListingFilters,
TQueryFilterHandler
> = {
price_lte: {
param: "price_lte",
transform: (param) => param || undefined,
encode: (value) => (value ? encodeURIComponent(value) : undefined),
},
price_gte: {
param: "price_gte",
transform: (param) => param || undefined,
encode: (value) => (value ? encodeURIComponent(value) : undefined),
},
fullyMinted_eq: {
param: "fullMint",
transform: (param) => (param ? param === "1" : undefined),
encode: (value) =>
value !== undefined ? encodeURIComponent(value ? "1" : "0") : undefined,
},
authorVerified_eq: {
param: "verified",
transform: (param) => (param ? param === "1" : undefined),
encode: (value) =>
value !== undefined ? encodeURIComponent(value ? "1" : "0") : undefined,
},
searchQuery_eq: {
param: "search",
transform: (param) => param || undefined,
encode: (value) => value || undefined,
},
tokenSupply_lte: {
param: "supply_lte",
transform: (param) => (param ? parseInt(param) : undefined),
encode: (value) => (value ? encodeURIComponent(value) : undefined),
},
tokenSupply_gte: {
param: "supply_gte",
transform: (param) => (param ? parseInt(param) : undefined),
encode: (value) => (value ? encodeURIComponent(value) : undefined),
},
}

/**
* Given a record of query parameters, outputs some Listing filters
*/
const getFiltersFromUrlQuery = (urlQuery: Record<string, string>) => {
const loadedFilters: ListingFilters = {}
// go through each prop of the handler and eventually transform query param
for (const K in queryListingFilterHandlers) {
const F = K as keyof ListingFilters
const handler = queryListingFilterHandlers[F]
if (urlQuery[handler.param]) {
loadedFilters[F] = queryListingFilterHandlers[F].transform(
urlQuery[handler.param]
)
}
}
return loadedFilters
}
import {
getFiltersFromUrlQuery,
getUrlQueryFromFilters,
} from "../utils/filters"

const searchSortOptions = getSortOptionsWithSearchOption(sortOptionsMarketplace)
const getSortFromUrlQuery = (urlQuery: Record<string, string>) => {
const { search, sort } = urlQuery

// if there is a sort value in the url, pre-select it in the sort input
// else, select the default value
let defaultSortValue = search ? "relevance-desc" : "createdAt-desc"
if (sort) {
let sortValues = search ? searchSortOptions : sortOptionsMarketplace
return sortValues.map(({ value }) => value).includes(sort)
? sort
: defaultSortValue
}

return defaultSortValue
interface Props {
urlQuery: Record<string, string>
}

export const Marketplace = ({ urlQuery }: Props) => {
const [filters, setFilters] = useState<ListingFilters>(
getFiltersFromUrlQuery(urlQuery)
getFiltersFromUrlQuery<ListingFilters>(urlQuery)
)
const [sort, setSort] = useState(
getSortFromUrlQuery(urlQuery, searchSortOptions, sortOptionsMarketplace)
)
const [sort, setSort] = useState(getSortFromUrlQuery(urlQuery))
const router = useRouter()

useEffect(() => {
const query: any = {}

// go through each prop of the handler and eventually encode query param
for (const K in queryListingFilterHandlers) {
const F = K as keyof ListingFilters
const handler = queryListingFilterHandlers[F]
const encoded = handler.encode(filters[F])
if (encoded) {
query[handler.param] = encoded
}
}

const query = getUrlQueryFromFilters(filters)
query.sort = encodeURIComponent(sort)

router.push(
Expand Down
55 changes: 55 additions & 0 deletions src/containers/Marketplace/MarketplaceSold.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ListingFilters } from "../../types/entities/Listing"
import { useRouter } from "next/router"
import {
getSortFromUrlQuery,
getSortOptionsWithSearchOption,
sortOptionsMarketplaceSold,
} from "../../utils/sort"
import { GalleryMarketplace } from "../../components/Gallery/GalleryMarketplace"
import { useEffect, useState } from "react"
import { buildUrlFromQuery } from "../../utils/url"
import {
getFiltersFromUrlQuery,
getUrlQueryFromFilters,
} from "../../utils/filters"

const searchSortOptions = getSortOptionsWithSearchOption(
sortOptionsMarketplaceSold
)
const defaultFilters = { acceptedAt_exist: true }

interface Props {
urlQuery: Record<string, string>
}
export const MarketplaceSold = ({ urlQuery }: Props) => {
const [filters, setFilters] = useState<ListingFilters>(
getFiltersFromUrlQuery<ListingFilters>(urlQuery)
)
const [sort, setSort] = useState(
getSortFromUrlQuery(urlQuery, searchSortOptions, sortOptionsMarketplaceSold)
)
const router = useRouter()

useEffect(() => {
const query = getUrlQueryFromFilters(filters)
query.sort = encodeURIComponent(sort)

router.push(
{ pathname: router.pathname, query },
buildUrlFromQuery(router.pathname, query),
{ shallow: true }
)
}, [filters, sort])

return (
<GalleryMarketplace
defaultSortOptions={sortOptionsMarketplaceSold}
defaultFilters={defaultFilters}
initialSearchQuery={urlQuery.search}
initialFilters={filters}
initialSort={sort}
onChangeFilters={setFilters}
onChangeSort={setSort}
/>
)
}
Loading