Skip to content

Commit b553a38

Browse files
authored
Merge pull request #116 from medyo/develop
Version 16.0.0
2 parents e5c9d6a + c6ece61 commit b553a38

31 files changed

Lines changed: 439 additions & 215 deletions

File tree

.github/workflows/distribute.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ on:
88
types:
99
- created
1010
env:
11-
REACT_APP_ANALYTICS_ID: ${{ secrets.ANALYTICS_ID }}
12-
REACT_APP_WEB_BUILD: 0
11+
REACT_APP_BUILD_TARGET: "extension"
1312
jobs:
1413

1514
bump-manifest-version:

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
"@testing-library/react": "^11.1.0",
1111
"@testing-library/user-event": "^12.1.10",
1212
"@types/dompurify": "^2.3.4",
13+
"@types/jspath": "^0.4.0",
1314
"axios": "^0.21.2",
1415
"axios-cache-adapter": "^2.7.3",
1516
"country-emoji": "^1.5.4",
1617
"dompurify": "^2.2.7",
1718
"eslint-plugin-react": "^7.28.0",
19+
"jspath": "^0.4.0",
1820
"localforage": "^1.9.0",
1921
"normalize.css": "^8.0.1",
2022
"prop-types": "^15.0.0-0",

public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<link rel="preconnect" href="https://hackertab.dev" />
1010
<script src="./startup.js"></script>
1111

12-
<% if (!!+process.env.REACT_APP_WEB_BUILD) { %>
12+
<% if (process.env.REACT_APP_BUILD_TARGET==='web' ) { %>
1313
<title>Hackertab</title>
1414
<link rel="manifest" href="%PUBLIC_URL%/web_manifest.json" />
1515
<% } else { %>

src/App.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@ import { ScrollCardsNavigator } from './components/Layout'
77
import { AppContentLayout } from './components/Layout'
88
import 'react-contexify/dist/ReactContexify.css'
99
import { setupAnalytics, trackPageView, setupIdentification } from 'src/lib/analytics'
10-
import { useRemoteConfigStore } from 'src/features/remoteConfig'
1110

1211
function App() {
1312
const [showSideBar, setShowSideBar] = useState(false)
1413
const [showSettings, setShowSettings] = useState(false)
1514

16-
const { marketingBannerConfig } = useRemoteConfigStore()
17-
1815
useEffect(() => {
1916
setupAnalytics()
2017
setupIdentification()
@@ -23,7 +20,7 @@ function App() {
2320

2421
return (
2522
<>
26-
<MarketingBanner {...marketingBannerConfig} />
23+
<MarketingBanner />
2724
<div className="App">
2825
<Header
2926
setShowSideBar={setShowSideBar}

src/assets/App.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,7 @@ Producthunt item
921921
animation-duration: 1.5s;
922922
animation-name: cardPlaceholderPulse;
923923
animation-iteration-count: infinite;
924+
scroll-snap-align: start;
924925
}
925926
.mediaCardPlaceholder {
926927
display: flex;
@@ -936,6 +937,7 @@ Producthunt item
936937
}
937938
.cardPlaceholder .cardContent {
938939
display: flex;
940+
flex: auto;
939941
flex-direction: column;
940942
}
941943
.cardPlaceholder .cardUpvote {
@@ -970,6 +972,18 @@ Producthunt item
970972
margin-right: 16px;
971973
border-radius: 4px;
972974
}
975+
.adCardPlaceholder {
976+
width: 300px;
977+
column-gap: 16px;
978+
display: flex;
979+
flex-direction: row;
980+
margin:0 auto;
981+
}
982+
.adCardPlaceholder .image {
983+
background: var(--placeholder-background-color);
984+
flex: 0 0 130px;
985+
height: 100px
986+
}
973987

974988
.floatingFilter {
975989
background: rgb(44, 128, 232);

src/components/List/ListComponent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { ReactNode } from 'react'
22
import { Placeholder } from 'src/components/placeholders'
33
import { MAX_ITEMS_PER_CARD } from 'src/config'
4-
import { CarbonAd } from 'src/features/carbonAds'
4+
import { BannerAd } from 'src/features/ads'
55
import { BaseEntry } from 'src/types'
66

77
type PlaceholdersProps = {
@@ -52,7 +52,7 @@ export function ListComponent<T extends BaseEntry>(props: ListComponentPropsType
5252
return items.slice(0, limit).map((item, index) => {
5353
let content: ReactNode[] = [renderItem(item, index)]
5454
if (withAds && index === 0) {
55-
content.unshift(<CarbonAd key={'carbonAd0'} />)
55+
content.unshift(<BannerAd key={'banner-ad'} />)
5656
}
5757
return content
5858
})
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const AdPlaceholder = ({ className = '' }: { className?: string }) => {
2+
return (
3+
<div className={'cardPlaceholder adCardPlaceholder'}>
4+
<span className="image" />
5+
<div className="cardContent">
6+
<span className="line" />
7+
<span className="smallLine" />
8+
<span className="smallLine" />
9+
<span className="smallLine" />
10+
</div>
11+
</div>
12+
)
13+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./Placeholder"
2-
export * from "./ProductHuntPlaceholder"
2+
export * from "./ProductHuntPlaceholder"
3+
export * from "./AdPlaceholder"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
import { ExtractFnReturnType, QueryConfig } from 'src/lib/react-query';
3+
import { MarketingConfig } from "../types";
4+
import { axios } from 'src/lib/axios';
5+
6+
const getMarketingConfig = async (): Promise<MarketingConfig> => {
7+
return axios.get('/data/marketingConfig.json');
8+
}
9+
10+
type QueryFnType = typeof getMarketingConfig;
11+
12+
type UseGetMarketingConfigOptions = {
13+
config?: QueryConfig<QueryFnType>;
14+
};
15+
export const useGetMarketingConfig = ({ config }: UseGetMarketingConfigOptions = {}) => {
16+
return useQuery<ExtractFnReturnType<QueryFnType>>({
17+
...config,
18+
queryKey: ['marketing-config'],
19+
queryFn: () => getMarketingConfig(),
20+
});
21+
}
Lines changed: 98 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,115 @@
11
import DOMPurify from 'dompurify'
2-
import { useMarketingBanner } from '../stores/marketingBanner'
2+
import { useMarketingConfigStore } from '../stores/marketingBanner'
3+
import JSPath from 'jspath'
4+
import { useUserPreferences } from 'src/stores/preferences'
5+
import { getAppVersion } from 'src/utils/Os'
6+
import { isWebOrExtensionVersion, isProduction, getBrowserName } from 'src/utils/Environment'
7+
import { useMemo, useState, useEffect } from 'react'
8+
import { Campaign, MarketingConfig } from '../types'
9+
import { useGetMarketingConfig } from '../api/getMarketingConfig'
10+
import {
11+
trackMarketingCampaignClose,
12+
trackMarketingCampaignView,
13+
trackMarketingCampaignOpen,
14+
} from 'src/lib/analytics'
15+
import { diffBetweenTwoDatesInDays } from 'src/utils/DateUtils'
316

4-
type MarketingBannerProps = {
5-
show: boolean
6-
campaign_name: string
7-
htmlContent: string
8-
}
9-
export const MarketingBanner = ({ campaign_name, show, htmlContent }: MarketingBannerProps) => {
10-
const { setCampaignClosed, closedCampaigns } = useMarketingBanner()
17+
export const MarketingBanner = () => {
18+
const { setCampaignClosed, closedCampaigns } = useMarketingConfigStore()
19+
const { userSelectedTags, cards, firstSeenDate } = useUserPreferences()
20+
const [availableCampaigns, setAvailableCampaigns] = useState<Campaign[]>([])
21+
const { data: marketingConfig } = useGetMarketingConfig({
22+
config: {
23+
staleTime: 60000,
24+
cacheTime: 3600000,
25+
},
26+
})
27+
28+
const userAtttributes = useMemo(() => {
29+
return {
30+
platform: isWebOrExtensionVersion(),
31+
browser: getBrowserName(),
32+
version: getAppVersion() || '0.0.0',
33+
environment: isProduction() ? 'prod' : 'dev',
34+
userTags: userSelectedTags.map((tag) => tag.label),
35+
cards: cards.map((card) => card.name),
36+
firstSeenDate,
37+
usageInDays: diffBetweenTwoDatesInDays(firstSeenDate, Date.now()),
38+
}
39+
}, [userSelectedTags, firstSeenDate, cards])
40+
41+
useEffect(() => {
42+
if (marketingConfig) {
43+
const availableCampaigns: Campaign[] = getAvailableCampaigns(marketingConfig)
44+
setAvailableCampaigns(availableCampaigns)
45+
}
46+
47+
// eslint-disable-next-line react-hooks/exhaustive-deps
48+
}, [marketingConfig, closedCampaigns, userSelectedTags, cards])
49+
50+
useEffect(() => {
51+
if (availableCampaigns.length) {
52+
trackMarketingCampaignView(availableCampaigns[0].id)
53+
}
54+
}, [availableCampaigns])
55+
56+
if (!marketingConfig) {
57+
return null
58+
}
59+
60+
const getAvailableCampaigns = (config: MarketingConfig) => {
61+
const campaignsWithUserAttr = config.campaigns.map((camp) => {
62+
return { ...camp, userAtttributes: userAtttributes }
63+
})
64+
65+
const lastVisibleAdDate = Math.max(...closedCampaigns.map((camp) => camp.date))
66+
if (lastVisibleAdDate > Date.now() - config.campaigns_interval) {
67+
return []
68+
}
69+
70+
const closedCampaignsSet = new Set(closedCampaigns.map((closedCamp) => closedCamp.id))
71+
const availableCampaigns = campaignsWithUserAttr
72+
.filter((camp) => camp.enabled && !closedCampaignsSet.has(camp.id))
73+
.flatMap((camp) => JSPath.apply(camp.condition, camp))
74+
.sort((a, b) => (a.priority || 0) - (b.priority || 0))
75+
.reverse()
1176

12-
if (!show || closedCampaigns.includes(campaign_name)) {
77+
return availableCampaigns
78+
}
79+
80+
if (!marketingConfig.enabled) {
1381
return null
1482
}
1583

16-
let cleanHtmlContent = DOMPurify.sanitize(htmlContent)
84+
if (!availableCampaigns.length) {
85+
return null
86+
}
1787

18-
const onBannerClick = (e: React.MouseEvent<HTMLElement>) => {
88+
const onBannerClick = (e: React.MouseEvent<HTMLElement>, campaign: Campaign) => {
1989
if (e.target instanceof Element) {
2090
const closeButton = e.target.closest('.close')
91+
const ctaButton = e.target.closest('.cta')
2192
if (closeButton && e.currentTarget.contains(closeButton)) {
22-
setCampaignClosed(campaign_name)
93+
setCampaignClosed(campaign.id)
94+
trackMarketingCampaignClose(campaign.id)
95+
} else if (ctaButton && e.currentTarget.contains(ctaButton)) {
96+
trackMarketingCampaignOpen(campaign.id)
2397
}
2498
}
2599
}
26100

101+
const currentCampaign = availableCampaigns[0]
102+
27103
return (
28-
<div onClick={(e) => onBannerClick(e)} dangerouslySetInnerHTML={{ __html: cleanHtmlContent }} />
104+
<div
105+
id={currentCampaign.id}
106+
onClick={(e) => onBannerClick(e, currentCampaign)}
107+
dangerouslySetInnerHTML={{
108+
__html: DOMPurify.sanitize(currentCampaign.htmlContent, {
109+
ADD_ATTR: ['target'],
110+
USE_PROFILES: { html: true, svg: true },
111+
}),
112+
}}
113+
/>
29114
)
30115
}

0 commit comments

Comments
 (0)