Skip to content

Commit

Permalink
feat: Add support for Rankings FF Variants (#740)
Browse files Browse the repository at this point in the history
  • Loading branch information
juanmahidalgo authored Jul 7, 2022
1 parent ffbb731 commit f1db02a
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 25 deletions.
7 changes: 6 additions & 1 deletion webapp/src/components/HomePage/HomePage.container.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { connect } from 'react-redux'
import { push } from 'connected-react-router'
import { getState } from 'decentraland-dapps/dist/modules/features/selectors'
import { ApplicationName } from 'decentraland-dapps/dist/modules/features/types'
import { RootState } from '../../modules/reducer'
import { fetchAssetsFromRoute } from '../../modules/routing/actions'
import {
getHomepage,
getHomepageLoading
} from '../../modules/ui/asset/homepage/selectors'
import { getRankingsFeatureVariant } from '../../modules/features/selectors'
import { MapStateProps, MapDispatchProps, MapDispatch } from './HomePage.types'
import HomePage from './HomePage'

const mapState = (state: RootState): MapStateProps => ({
homepage: getHomepage(state),
homepageLoading: getHomepageLoading(state)
homepageLoading: getHomepageLoading(state),
features: getState(state).data[ApplicationName.MARKETPLACE],
rankingsVariant: getRankingsFeatureVariant(state)
})

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
Expand Down
129 changes: 108 additions & 21 deletions webapp/src/components/HomePage/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useCallback, useEffect, useMemo } from 'react'
import { Button, Hero, Page } from 'decentraland-ui'
import { isMobile } from 'decentraland-dapps/dist/lib/utils'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { getAnalytics } from 'decentraland-dapps/dist/modules/analytics/utils'
import { Page } from 'decentraland-ui'
import { locations } from '../../modules/routing/locations'
import { VendorName } from '../../modules/vendor/types'
import { SortBy } from '../../modules/routing/types'
Expand All @@ -25,11 +26,38 @@ const HomePage = (props: Props) => {
homepage,
homepageLoading,
onNavigate,
onFetchAssetsFromRoute
onFetchAssetsFromRoute,
rankingsVariant,
features
} = props

useEffect(() => {
if (features) {
getAnalytics().track('feature_flags', {
featureFlags: [
...Object.keys(features.flags).filter(flag => features.flags[flag]),
...Object.keys(features.variants)
.filter(flag => features.variants[flag]?.enabled)
.map(flag => `${flag}:${features.variants[flag].name}`)
]
})
}
}, [features])

const shouldRenderRankingsVariant = useMemo(() => {
if (
rankingsVariant &&
rankingsVariant.name === 'enabled' &&
rankingsVariant.enabled
) {
return true
}
return false
}, [rankingsVariant])

const sections: Partial<Record<View, Section>> = useMemo(
() => ({
[View.HOME_SOLD_ITEMS]: Section.WEARABLES_TRENDING,
[View.HOME_TRENDING_ITEMS]: Section.WEARABLES_TRENDING,
[View.HOME_NEW_ITEMS]: Section.WEARABLES,
[View.HOME_WEARABLES]: Section.WEARABLES,
Expand Down Expand Up @@ -58,6 +86,7 @@ const HomePage = (props: Props) => {
() => ({
[View.HOME_TRENDING_ITEMS]: AssetType.ITEM,
[View.HOME_NEW_ITEMS]: AssetType.ITEM,
[View.HOME_SOLD_ITEMS]: AssetType.ITEM,
[View.HOME_WEARABLES]: AssetType.NFT,
[View.HOME_LAND]: AssetType.NFT,
[View.HOME_ENS]: AssetType.NFT
Expand All @@ -68,6 +97,7 @@ const HomePage = (props: Props) => {
const sort: Partial<Record<View, SortBy>> = useMemo(
() => ({
[View.HOME_NEW_ITEMS]: SortBy.RECENTLY_LISTED,
[View.HOME_SOLD_ITEMS]: SortBy.RECENTLY_SOLD,
[View.HOME_WEARABLES]: SortBy.RECENTLY_LISTED,
[View.HOME_LAND]: SortBy.RECENTLY_LISTED,
[View.HOME_ENS]: SortBy.RECENTLY_LISTED
Expand Down Expand Up @@ -120,11 +150,6 @@ const HomePage = (props: Props) => {
// eslint-disable-next-line
}, [onFetchAssetsFromRoute])

// trending and newest sections
const firstViewsSection = Object.keys(homepage).slice(0, 2) as HomepageView[]
// rest of the sections
const secondViewsSection = Object.keys(homepage).slice(2) as HomepageView[]

const renderSlideshow = (view: HomepageView) => (
<Slideshow
key={view}
Expand All @@ -137,20 +162,82 @@ const HomePage = (props: Props) => {
/>
)

return (
<>
<Navbar isFullscreen />
<Navigation activeTab={NavigationTab.OVERVIEW} />
<Page className="HomePage">
<AnalyticsVolumeDayData />
{firstViewsSection.map(renderSlideshow)}
<RankingsTable />
{secondViewsSection.map(renderSlideshow)}
<RecentlySoldTable />
</Page>
<Footer />
</>
)
const getRankingsHomeView = () => {
const homepageWithoutLatestSales = Object.keys(homepage).filter(
view => view !== View.HOME_SOLD_ITEMS
)
// trending and newest sections
const firstViewsSection = homepageWithoutLatestSales.slice(
0,
2
) as HomepageView[]
// rest of the sections
const secondViewsSection = homepageWithoutLatestSales.slice(
2
) as HomepageView[]
return (
<>
<Navbar isFullscreen />
<Navigation activeTab={NavigationTab.OVERVIEW} />
<Page className="HomePage">
<AnalyticsVolumeDayData />
{firstViewsSection.map(renderSlideshow)}
<RankingsTable />
{secondViewsSection.map(renderSlideshow)}
<RecentlySoldTable />
</Page>
<Footer />
</>
)
}

const handleGetStarted = useCallback(() => {
onNavigate(
locations.browse({
section: Section.WEARABLES,
assetType: AssetType.ITEM
})
)
}, [onNavigate])

const getLegacyHomeView = () => {
const views = (Object.keys(homepage) as HomepageView[]).filter(
view => view !== View.HOME_TRENDING_ITEMS
)
return (
<>
<Navbar isFullscreen isOverlay />
<Hero centered={isMobile()} className="HomePageHero">
<Hero.Header>{t('home_page.title')}</Hero.Header>
<Hero.Description>{t('home_page.subtitle')}</Hero.Description>
<Hero.Content>
<div className="hero-image" />{' '}
</Hero.Content>
<Hero.Actions>
<Button primary onClick={handleGetStarted}>
{t('home_page.get_started')}
</Button>
</Hero.Actions>
</Hero>
<Page className="HomePage">
{views.map(view => (
<Slideshow
key={view}
title={t(`home_page.${view}`)}
assets={homepage[view]}
isLoading={homepageLoading[view]}
onViewAll={() => handleViewAll(view)}
/>
))}
</Page>
<Footer />
</>
)
}

return shouldRenderRankingsVariant
? getRankingsHomeView()
: getLegacyHomeView()
}

export default React.memo(HomePage)
11 changes: 10 additions & 1 deletion webapp/src/components/HomePage/HomePage.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Dispatch } from 'redux'
import { CallHistoryMethodAction } from 'connected-react-router'
import {
ApplicationFeatures,
Variant
} from 'decentraland-dapps/dist/modules/features/types'
import {
getHomepage,
getHomepageLoading
Expand All @@ -12,11 +16,16 @@ import {
export type Props = {
homepage: ReturnType<typeof getHomepage>
homepageLoading: ReturnType<typeof getHomepageLoading>
rankingsVariant: false | Variant
features: ApplicationFeatures
onNavigate: (path: string) => void
onFetchAssetsFromRoute: typeof fetchAssetsFromRoute
}

export type MapStateProps = Pick<Props, 'homepage' | 'homepageLoading'>
export type MapStateProps = Pick<
Props,
'homepage' | 'homepageLoading' | 'rankingsVariant' | 'features'
>
export type MapDispatchProps = Pick<
Props,
'onNavigate' | 'onFetchAssetsFromRoute'
Expand Down
15 changes: 15 additions & 0 deletions webapp/src/modules/features/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { RootState } from '../reducer'
import { getState } from 'decentraland-dapps/dist/modules/features/selectors'
import { ApplicationName } from 'decentraland-dapps/dist/modules/features/types'
import { FeatureName } from './types'

export const getRankingsFeatureVariant = (state: RootState) => {
try {
const features = getState(state)
return features.data[ApplicationName.MARKETPLACE].variants[
`${ApplicationName.MARKETPLACE}-${FeatureName.RANKINGS}`
]
} catch (e) {
return false
}
}
3 changes: 3 additions & 0 deletions webapp/src/modules/features/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum FeatureName {
RANKINGS = 'rankings_variant'
}
4 changes: 3 additions & 1 deletion webapp/src/modules/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { transactionReducer as transaction } from 'decentraland-dapps/dist/modul
import { profileReducer as profile } from 'decentraland-dapps/dist/modules/profile/reducer'
import { authorizationReducer as authorization } from 'decentraland-dapps/dist/modules/authorization/reducer'
import { toastReducer as toast } from 'decentraland-dapps/dist/modules/toast/reducer'
import { featuresReducer as features } from 'decentraland-dapps/dist/modules/features/reducer'

import { accountReducer as account } from './account/reducer'
import { bidReducer as bid } from './bid/reducer'
Expand Down Expand Up @@ -47,7 +48,8 @@ export const createRootReducer = (history: History) =>
store,
sale,
identity,
analytics
analytics,
features
})

export type RootState = ReturnType<ReturnType<typeof createRootReducer>>
10 changes: 9 additions & 1 deletion webapp/src/modules/sagas.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { all } from 'redux-saga/effects'
import { ApplicationName } from 'decentraland-dapps/dist/modules/features/types'
import { authorizationSaga } from 'decentraland-dapps/dist/modules/authorization/sagas'
import { createAnalyticsSaga } from 'decentraland-dapps/dist/modules/analytics/sagas'
import { createProfileSaga } from 'decentraland-dapps/dist/modules/profile/sagas'
import { transactionSaga } from 'decentraland-dapps/dist/modules/transaction/sagas'
import { featuresSaga } from 'decentraland-dapps/dist/modules/features/sagas'
import { CatalystClient } from 'dcl-catalyst-client'

import { analyticsSagas as marketplaceAnalyticsSagas } from './analytics/sagas'
Expand Down Expand Up @@ -53,6 +55,12 @@ export function* rootSaga() {
collectionSaga(),
storeSaga(catalystClient),
identitySaga(),
marketplaceAnalyticsSagas()
marketplaceAnalyticsSagas(),
featuresSaga({
polling: {
apps: [ApplicationName.MARKETPLACE],
delay: 60000 /** 60 seconds */
}
})
])
}
8 changes: 8 additions & 0 deletions webapp/src/modules/ui/asset/homepage/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { View } from '../../types'
export type HomepageUIState = {
[View.HOME_TRENDING_ITEMS]: string[]
[View.HOME_NEW_ITEMS]: string[]
[View.HOME_SOLD_ITEMS]: string[]
[View.HOME_WEARABLES]: string[]
[View.HOME_LAND]: string[]
[View.HOME_ENS]: string[]
Expand All @@ -21,6 +22,7 @@ export type HomepageUIState = {
export const INITIAL_STATE: HomepageUIState = {
[View.HOME_TRENDING_ITEMS]: [],
[View.HOME_NEW_ITEMS]: [],
[View.HOME_SOLD_ITEMS]: [],
[View.HOME_WEARABLES]: [],
[View.HOME_LAND]: [],
[View.HOME_ENS]: []
Expand All @@ -47,6 +49,12 @@ export function homepageReducer(
[View.HOME_NEW_ITEMS]: itemIds
}
}
case View.HOME_SOLD_ITEMS: {
return {
...state,
[View.HOME_SOLD_ITEMS]: itemIds
}
}
default:
return state
}
Expand Down
1 change: 1 addition & 0 deletions webapp/src/modules/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const View = {
ACCOUNT: 'account',
CURRENT_ACCOUNT: 'current_account',
HOME_TRENDING_ITEMS: 'home_trending_items',
HOME_SOLD_ITEMS: 'home_sold_items',
HOME_NEW_ITEMS: 'home_new_items',
HOME_WEARABLES: 'home_wearables',
HOME_LAND: 'home_land',
Expand Down

0 comments on commit f1db02a

Please sign in to comment.