Skip to content

Commit 28ec408

Browse files
authored
Merge pull request #285 from medyo/develop
New Version
2 parents bbb6a6d + d0e5303 commit 28ec408

File tree

22 files changed

+406
-157
lines changed

22 files changed

+406
-157
lines changed

public/web_manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"short_name": "Hackertab.dev",
3-
"name": "All developer news in one tab",
4-
"description": "Hackertab helps developers stay up-to-date with the latest dev trends and tools",
3+
"name": "Hackertab – Dev News & GitHub in New Tab",
4+
"description": "Developer news from GitHub Trending, Hacker News, and more, all in your new tab.",
55
"icons": [
66
{
77
"src": "/logos/logoVector.svg",

src/App.tsx

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
import clsx from 'clsx'
22
import { useEffect, useLayoutEffect } from 'react'
33
import { DNDLayout } from 'src/components/Layout'
4-
import {
5-
identifyAdvBlocked,
6-
setupAnalytics,
7-
setupIdentification,
8-
trackPageView,
9-
} from 'src/lib/analytics'
4+
import { setupAnalytics, setupIdentification, trackPageView } from 'src/lib/analytics'
105
import { useUserPreferences } from 'src/stores/preferences'
116
import { AppContentLayout } from './components/Layout'
12-
import { verifyAdvStatus } from './features/adv/utils/status'
137
import { lazyImport } from './utils/lazyImport'
148
const { OnboardingModal } = lazyImport(() => import('src/features/onboarding'), 'OnboardingModal')
159

@@ -27,7 +21,6 @@ export const App = () => {
2721
const {
2822
maxVisibleCards,
2923
onboardingCompleted,
30-
setAdvStatus,
3124
isDNDModeActive,
3225
layout,
3326
DNDDuration,
@@ -42,12 +35,6 @@ export const App = () => {
4235
document.body.classList.remove('preload')
4336
setupAnalytics()
4437
setupIdentification()
45-
const adVerifier = async () => {
46-
const status = await verifyAdvStatus()
47-
setAdvStatus(status)
48-
identifyAdvBlocked(status)
49-
}
50-
adVerifier()
5138
}, [])
5239

5340
useEffect(() => {

src/assets/App.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ a {
6868
z-index: 1;
6969
}
7070

71+
.AppHeader .upgradeButton {
72+
font-weight: bold;
73+
background-color: var(--tab-positive-button-background) !important;
74+
color: white !important;
75+
&:hover {
76+
opacity: 0.8;
77+
}
78+
}
79+
7180
.AppFooter {
7281
display: flex;
7382
flex-direction: row;

src/components/Layout/Header.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { clsx } from 'clsx'
22
import { useCallback, useEffect, useState } from 'react'
33
import { BsFillBookmarksFill, BsFillGearFill, BsMoonFill } from 'react-icons/bs'
44
import { CgTab } from 'react-icons/cg'
5+
import { FaCrown } from 'react-icons/fa'
56
import { IoMdSunny } from 'react-icons/io'
67
import { MdDoDisturbOff } from 'react-icons/md'
78
import { RiDashboardHorizontalFill } from 'react-icons/ri'
@@ -13,19 +14,25 @@ import HackertabLogo from 'src/assets/logo.svg?react'
1314
import { UserTags } from 'src/components/Elements/UserTags'
1415
import { useAuth } from 'src/features/auth'
1516
import { Changelog } from 'src/features/changelog'
17+
import { useRemoteConfigStore } from 'src/features/remoteConfig'
1618
import {
1719
identifyUserTheme,
1820
trackDNDDisable,
1921
trackDisplayTypeChange,
2022
trackThemeSelect,
2123
} from 'src/lib/analytics'
2224
import { useUserPreferences } from 'src/stores/preferences'
25+
import { lazyImport } from 'src/utils/lazyImport'
2326
import { Button, CircleButton } from '../Elements'
2427
import { SearchEngineBar } from '../Elements/SearchBar/SearchEngineBar'
28+
const { DonateModal } = lazyImport(() => import('src/features/donate'), 'DonateModal')
29+
2530
export const Header = () => {
2631
const { openAuthModal, user, isConnected, isConnecting } = useAuth()
2732

2833
const [themeIcon, setThemeIcon] = useState(<BsMoonFill />)
34+
const [openDonateModal, setOpenDonateModal] = useState(false)
35+
const { paywall } = useRemoteConfigStore()
2936
const { theme, setTheme, setDNDDuration, isDNDModeActive, layout, setLayout } =
3037
useUserPreferences()
3138
const navigate = useNavigate()
@@ -68,6 +75,10 @@ export const Header = () => {
6875
setDNDDuration('never')
6976
}, [setDNDDuration])
7077

78+
const onUpgradeClicked = useCallback(() => {
79+
setOpenDonateModal(true)
80+
}, [])
81+
7182
return (
7283
<>
7384
<header className="AppHeader">
@@ -129,7 +140,14 @@ export const Header = () => {
129140
<AvatarPlaceholder className="avatarPlaceholder" />
130141
)}
131142
</CircleButton>
143+
{paywall?.enabled && (
144+
<Button onClick={onUpgradeClicked} className="upgradeButton">
145+
<FaCrown />
146+
{paywall.headerCta}
147+
</Button>
148+
)}
132149
</div>
150+
{openDonateModal && <DonateModal setModalOpen={setOpenDonateModal} />}
133151
{location.pathname === '/' && <UserTags />}
134152
</header>
135153
</>

src/components/Layout/SettingsContentLayout/settingsContentLayout.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,13 @@
9999
@media only screen and (max-width: 768px) {
100100
.settingsContent {
101101
padding: 16px;
102+
min-height: 100vh;
103+
height: 100vh;
102104
}
103105
.settingsBody {
104106
max-width: 100%;
105107
padding: 0;
108+
flex: 1 1 auto;
109+
min-height: 0;
106110
}
107111
}

src/config/index.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ export const maxCardsPerRow = 4
1717
export const supportLink = 'https://github.com/medyo/hackertab.dev/issues'
1818
export const privacyPolicyLink = 'https://www.hackertab.dev/privacy-policy'
1919
export const termsAndConditionsLink = 'https://www.hackertab.dev/terms-and-conditions'
20-
export const dataSourcesLink = 'https://www.hackertab.dev/data-sources'
2120
export const changeLogLink = 'https://api.github.com/repos/medyo/hackertab.dev/releases'
2221
export const twitterHandle = '@hackertabdev'
23-
export const reportLink = 'https://www.hackertab.dev/report'
2422

2523
export const LS_PREFERENCES_KEY = 'hackerTabPrefs'
2624
export const MAX_ITEMS_PER_CARD = 50

src/features/MarketingBanner/components/MarketingBanner.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import { Campaign, MarketingConfig } from '../types'
1818

1919
export const MarketingBanner = () => {
2020
const { setCampaignClosed, closedCampaigns } = useMarketingConfigStore()
21-
const { isConnected } = useAuth()
22-
const { userSelectedTags, cards, firstSeenDate, advStatus } = useUserPreferences()
21+
const { isConnected, user } = useAuth()
22+
const { userSelectedTags, cards, firstSeenDate } = useUserPreferences()
2323
const [availableCampaigns, setAvailableCampaigns] = useState<Campaign[]>([])
2424
const { data: marketingConfig } = useGetMarketingConfig({
2525
config: {
@@ -39,11 +39,11 @@ export const MarketingBanner = () => {
3939
userTags: userSelectedTags.map((tag) => tag.label),
4040
cards: cards.map((card) => card.name),
4141
firstSeenDate,
42-
adv: advStatus,
4342
isConnected,
43+
isSupported: user?.isSupporter || false,
4444
usageInDays: diffBetweenTwoDatesInDays(firstSeenDate, Date.now()),
4545
}
46-
}, [userSelectedTags, firstSeenDate, cards, advStatus])
46+
}, [userSelectedTags, firstSeenDate, cards, user])
4747

4848
useEffect(() => {
4949
if (marketingConfig && marketingConfig.version === 1) {

src/features/adv/api/getAd.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Ad } from '../types'
55

66
const getAd = async (keywords: string[], feed: boolean = false): Promise<Ad | null> => {
77
let params = { keywords: keywords.join(','), feed: feed ? 'true' : 'false' }
8-
return axios.get('/engine/ads/', { params })
8+
return axios.get('/engine/ads/adaptive', { params })
99
}
1010

1111
type QueryFnType = typeof getAd
@@ -18,7 +18,7 @@ type UseGetAdOptions = {
1818
export const useGetAd = ({ keywords, feed, config }: UseGetAdOptions) => {
1919
return useQuery<ExtractFnReturnType<QueryFnType>>({
2020
...config,
21-
queryKey: ['ad', keywords.join(',')],
21+
queryKey: ['ad', 'v2', keywords.join(',')],
2222
queryFn: () => getAd(keywords, feed),
2323
})
2424
}

src/features/adv/components/AdvBanner.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,21 @@
285285
.advFeed:has(.banneradv) .rowDetails {
286286
margin-left: auto;
287287
}
288+
289+
.houseBanner {
290+
max-width: none;
291+
width: 100%;
292+
img {
293+
border-radius: 8px;
294+
object-fit: contain;
295+
display: block;
296+
margin: 0 auto;
297+
max-width: 100%;
298+
}
299+
a {
300+
width: 100%;
301+
&:hover {
302+
opacity: 0.8;
303+
}
304+
}
305+
}
Lines changed: 16 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { useEffect } from 'react'
22
import { AdPlaceholder } from 'src/components/placeholders'
3-
import { useRemoteConfigStore } from 'src/features/remoteConfig'
3+
import { trackMarketingCampaignOpen } from 'src/lib/analytics'
44
import { useUserPreferences } from 'src/stores/preferences'
5-
import { isWebOrExtensionVersion } from 'src/utils/Environment'
65
import { useGetAd } from '../api/getAd'
7-
import { useDelayedFlag } from '../hooks/useDelayedFlag'
86
import { Ad } from '../types'
97
import './AdvBanner.css'
108

@@ -14,12 +12,8 @@ type AdvBannerProps = {
1412
loadingState?: React.ReactNode
1513
}
1614

17-
export const AdvBanner = ({ feedDisplay = false, loadingState, onAdLoaded }: AdvBannerProps) => {
15+
export const AdvBanner = ({ loadingState, onAdLoaded }: AdvBannerProps) => {
1816
const { userSelectedTags } = useUserPreferences()
19-
const adsFetchDelayMs = useRemoteConfigStore((s) => s.adsFetchDelayMs)
20-
const delay = isWebOrExtensionVersion() === 'extension' ? adsFetchDelayMs : undefined
21-
const isReady = useDelayedFlag(delay)
22-
2317
const {
2418
isSuccess,
2519
data: ad,
@@ -31,7 +25,6 @@ export const AdvBanner = ({ feedDisplay = false, loadingState, onAdLoaded }: Adv
3125
config: {
3226
cacheTime: 0,
3327
staleTime: 0,
34-
enabled: isReady,
3528
useErrorBoundary: false,
3629
},
3730
})
@@ -50,85 +43,22 @@ export const AdvBanner = ({ feedDisplay = false, loadingState, onAdLoaded }: Adv
5043
return null
5144
}
5245

53-
if (ad.largeImage) {
46+
const onAdClick = () => {
47+
if (ad?.id) {
48+
trackMarketingCampaignOpen(ad.id, {
49+
source: 'card',
50+
})
51+
}
52+
}
53+
if (ad.type === 'house-ad-banner') {
5454
return (
55-
<>
56-
<div
57-
className="carbonCoverTarget"
58-
style={
59-
{
60-
'--ad-dynamic-bg-image': `url(${ad.largeImage})`,
61-
'--ad-gradient-color': ad.backgroundColor,
62-
} as React.CSSProperties
63-
}>
64-
<a href={ad.link} className="carbonCover">
65-
<img className="carbonCoverImage" src={ad.largeImage} />
66-
<div className="carbonCoverMain">
67-
<img className="carbonCoverLogo" src={ad.logo} />
68-
<div className="carbonCoverTagline">{ad.companyTagline}</div>
69-
<div className="carbonCoverDescription">{ad.description}</div>
70-
<div className="carbonCoverButton">{ad.callToAction + ' ↗'}</div>
71-
</div>
72-
</a>
73-
</div>
74-
{ad.viewUrl &&
75-
ad.viewUrl
76-
.split('||')
77-
.map((viewUrl, i) => (
78-
<img
79-
key={i}
80-
src={viewUrl.replace('[timestamp]', `${Math.round(Date.now() / 10000) | 0}`)}
81-
className="hidden"
82-
alt=""
83-
/>
84-
))}
85-
</>
55+
<div className="houseBanner">
56+
<a onClick={onAdClick} href={ad.link} target="_blank" title={ad.title}>
57+
<img src={ad.imageUrl} alt={ad.title} />
58+
</a>
59+
</div>
8660
)
8761
}
8862

89-
return (
90-
<>
91-
<div className="banneradv">
92-
<a
93-
href={ad.link}
94-
className="img"
95-
target="_blank"
96-
rel="noopener sponsored noreferrer"
97-
title={ad.title}>
98-
<img
99-
src={ad.imageUrl}
100-
alt={ad.title}
101-
height={!feedDisplay ? '120' : '200'}
102-
width={!feedDisplay ? '156' : '260'}
103-
style={{ border: 0 }}
104-
/>
105-
</a>
106-
107-
<a href={ad.link} className="text" target="_blank" rel="noopener sponsored noreferrer">
108-
{ad.description}
109-
</a>
110-
111-
{!feedDisplay && (
112-
<a
113-
href={ad.provider.link}
114-
className="poweredby"
115-
target="_blank"
116-
rel="noopener sponsored noreferrer">
117-
{ad.provider.title}
118-
</a>
119-
)}
120-
</div>
121-
{ad.viewUrl &&
122-
ad.viewUrl
123-
.split('||')
124-
.map((viewUrl, i) => (
125-
<img
126-
key={i}
127-
src={viewUrl.replace('[timestamp]', `${Math.round(Date.now() / 10000) | 0}`)}
128-
className="hidden"
129-
alt=""
130-
/>
131-
))}
132-
</>
133-
)
63+
return null
13464
}

0 commit comments

Comments
 (0)