diff --git a/webapp/src/components/ActivityPage/ActivityPage.tsx b/webapp/src/components/ActivityPage/ActivityPage.tsx index c2fa5e8a3..6e6c9db28 100644 --- a/webapp/src/components/ActivityPage/ActivityPage.tsx +++ b/webapp/src/components/ActivityPage/ActivityPage.tsx @@ -1,6 +1,14 @@ -import React, { useCallback, useState } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { Link } from 'react-router-dom' -import { Page, Header, Button, Modal } from 'decentraland-ui' +import { useLocation } from 'react-router-dom' +import { + Page, + Header, + Button, + Modal, + // Icon, + ModalNavigation +} from 'decentraland-ui' import { t, T } from 'decentraland-dapps/dist/modules/translation/utils' import { locations } from '../../modules/routing/locations' @@ -13,11 +21,17 @@ import { Transaction } from './Transaction' import { Props } from './ActivityPage.types' import './ActivityPage.css' import { NavigationTab } from '../Navigation/Navigation.types' +import { RECO_TYPE, Recommendation } from '../Recommendation' const ActivityPage = (props: Props) => { const { address, transactions, onClearHistory } = props - + const location = useLocation() + const boughtItem = useMemo( + () => new URLSearchParams(location.search).get('boughtItem'), + [location.search] + ) const [showConfirmation, setShowConfirmation] = useState(false) + const [isBoughItemOpen, setIsBoughItemOpen] = useState(true) const handleClear = useCallback(() => { if (address) { @@ -44,7 +58,9 @@ const ActivityPage = (props: Props) => { id="wallet.sign_in_required" values={{ sign_in: ( - {t('wallet.sign_in')} + + {t('wallet.sign_in')} + ) }} /> @@ -85,6 +101,21 @@ const ActivityPage = (props: Props) => { {content} + {boughtItem ? ( + + setIsBoughItemOpen(false)} + > + + + + + ) : null} {t('activity_page.clear_history_modal.title')} diff --git a/webapp/src/components/AssetPage/ItemDetail/ItemDetail.module.css b/webapp/src/components/AssetPage/ItemDetail/ItemDetail.module.css index c2d6c0c65..ed51db0f6 100644 --- a/webapp/src/components/AssetPage/ItemDetail/ItemDetail.module.css +++ b/webapp/src/components/AssetPage/ItemDetail/ItemDetail.module.css @@ -7,3 +7,11 @@ flex-direction: column; gap: 8px; } + +.recommendations > div { + margin-top: 30px; +} + +.recommendations > :global(.Slideshow) { + margin-bottom: 0px !important; +} \ No newline at end of file diff --git a/webapp/src/components/AssetPage/ItemDetail/ItemDetail.tsx b/webapp/src/components/AssetPage/ItemDetail/ItemDetail.tsx index 9edf7d89a..2e703d6b1 100644 --- a/webapp/src/components/AssetPage/ItemDetail/ItemDetail.tsx +++ b/webapp/src/components/AssetPage/ItemDetail/ItemDetail.tsx @@ -8,6 +8,7 @@ import { AssetType } from '../../../modules/asset/types' import GenderBadge from '../../GenderBadge' import { AssetImage } from '../../AssetImage' import CampaignBadge from '../../Campaign/CampaignBadge' +import { Recommendation, RECO_TYPE } from '../../Recommendation' import CategoryBadge from '../CategoryBadge' import SmartBadge from '../SmartBadge' import { Description } from '../Description' @@ -18,6 +19,7 @@ import IconBadge from '../IconBadge' import { TransactionHistory } from '../TransactionHistory' import { SaleActionBox } from '../SaleActionBox' import { Props } from './ItemDetail.types' +import styles from './ItemDetail.module.css' const ItemDetail = ({ item }: Props) => { let description = '' @@ -109,7 +111,15 @@ const ItemDetail = ({ item }: Props) => { box={null} showDetails actions={} - below={} + below={ + + + + + + + + } /> ) } diff --git a/webapp/src/components/HomePage/Slideshow/ItemsSection.tsx b/webapp/src/components/HomePage/Slideshow/ItemsSection.tsx index 83bc82865..48ca84661 100644 --- a/webapp/src/components/HomePage/Slideshow/ItemsSection.tsx +++ b/webapp/src/components/HomePage/Slideshow/ItemsSection.tsx @@ -14,7 +14,7 @@ import { Section } from '../../../modules/vendor/decentraland/routing/types' const ItemsSection = (props: { view: HomepageView - viewAllButton: React.ReactNode + viewAllButton?: React.ReactNode onChangeItemSection: (view: HomepageView, section: Section) => void }) => { const { view, viewAllButton, onChangeItemSection } = props @@ -47,9 +47,11 @@ const ItemsSection = (props: { {t(`menu.${Section.EMOTES}`)} - - {viewAllButton} - + {viewAllButton ? ( + + {viewAllButton} + + ) : null} ) diff --git a/webapp/src/components/HomePage/Slideshow/Slideshow.tsx b/webapp/src/components/HomePage/Slideshow/Slideshow.tsx index 89c3e382c..16aa51763 100644 --- a/webapp/src/components/HomePage/Slideshow/Slideshow.tsx +++ b/webapp/src/components/HomePage/Slideshow/Slideshow.tsx @@ -27,6 +27,7 @@ const Slideshow = (props: Props) => { title, subtitle, viewAllTitle, + showViewAll, emptyMessage, assets, isSubHeader, @@ -63,7 +64,7 @@ const Slideshow = (props: Props) => { (asset: Asset) => { getAnalytics().track(events.ASSET_CLICK, { id: asset.id, - section: title + section: title ?? 'Unknown' }) }, [title] @@ -124,25 +125,27 @@ const Slideshow = (props: Props) => { - {title} - {subtitle} + {title ? {title} : null} + {subtitle ? {subtitle} : null} {hasItemsSection ? ( ) : null} - {!hasItemsSection ? ( + {!hasItemsSection && showViewAll ? ( {viewAllButton()} ) : null} - + {isLoading ? ( assets.length === 0 ? ( @@ -159,7 +162,7 @@ const Slideshow = (props: Props) => { className="arrow-container arrow-container-left" {...showArrowsHandlers} > - {showArrows && totalPages > 1 && ( + {showArrows && totalPages > 1 && ( { + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const [items, setItems] = useState([]) + const itemAPI = useMemo( + () => + new ItemAPI(NFT_SERVER_URL, { + retries: 3, + retryDelay: 1000 + }), + [] + ) + + useEffect(() => { + setIsLoading(true) + fetch(`${RECOMMENDER_URL}/recommendation/${itemId}?type=${type}`) + .then(response => { + if (response.ok) { + return response.json() + } else if (response.status === 404) { + throw Error('No recommendation found') + } + }) + // mockedFetch() + .then(itemIDs => itemAPI.get({ ids: itemIDs })) + .then(response => { + setItems(response.data) + setIsLoading(false) + }) + .catch(error => { + setIsLoading(false) + setError(isErrorWithMessage(error) ? error.message : 'Unknown error') + }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( + undefined} + hasItemsSection={false} + showViewAll={false} + emptyMessage={error ?? undefined} + assets={items as Asset[]} + isLoading={isLoading} + /> + ) +} diff --git a/webapp/src/components/Recommendation/Recommendation.types.ts b/webapp/src/components/Recommendation/Recommendation.types.ts new file mode 100644 index 000000000..edb12791b --- /dev/null +++ b/webapp/src/components/Recommendation/Recommendation.types.ts @@ -0,0 +1,4 @@ +export enum RECO_TYPE { + SIMILARITY = 'similarity', + OTHERS_BOUGHT = 'othersBought' +} diff --git a/webapp/src/components/Recommendation/index.ts b/webapp/src/components/Recommendation/index.ts new file mode 100644 index 000000000..379bcdc1e --- /dev/null +++ b/webapp/src/components/Recommendation/index.ts @@ -0,0 +1,2 @@ +export * from './Recommendation' +export * from './Recommendation.types' diff --git a/webapp/src/modules/routing/locations.ts b/webapp/src/modules/routing/locations.ts index fb41b98b7..135f4ee8d 100644 --- a/webapp/src/modules/routing/locations.ts +++ b/webapp/src/modules/routing/locations.ts @@ -106,7 +106,8 @@ export const locations = { contractAddress: string = ':contractAddress', tokenId: string = ':tokenId' ) => `/contracts/${contractAddress}/tokens/${tokenId}/bid`, - activity: () => `/activity` + activity: (boughtItem?: string) => + `/activity${boughtItem ? `?${boughtItem}` : ''}` } function getResource(type: AssetType) { diff --git a/webapp/src/modules/routing/sagas.ts b/webapp/src/modules/routing/sagas.ts index 959eba1b0..e8b8a9115 100644 --- a/webapp/src/modules/routing/sagas.ts +++ b/webapp/src/modules/routing/sagas.ts @@ -26,6 +26,7 @@ import { import { AssetType } from '../asset/types' import { BUY_ITEM_SUCCESS, + BuyItemSuccessAction, fetchItemRequest, fetchItemsRequest, fetchTrendingItemsRequest @@ -601,6 +602,8 @@ function shouldResetOptions(previous: BrowseOptions, current: BrowseOptions) { ) } -function* handleRedirectToActivity() { - yield put(push(locations.activity())) +function* handleRedirectToActivity(action: BuyItemSuccessAction) { + const boughtItem = + action.type === BUY_ITEM_SUCCESS ? action.payload.item.id : undefined + yield put(push(locations.activity(boughtItem))) }