Skip to content
Merged
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
4 changes: 2 additions & 2 deletions public/web_manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"short_name": "Hackertab.dev",
"name": "All developer news in one tab",
"description": "Hackertab helps developers stay up-to-date with the latest dev trends and tools",
"name": "Hackertab – Dev News & GitHub in New Tab",
"description": "Developer news from GitHub Trending, Hacker News, and more, all in your new tab.",
"icons": [
{
"src": "/logos/logoVector.svg",
Expand Down
15 changes: 1 addition & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import clsx from 'clsx'
import { useEffect, useLayoutEffect } from 'react'
import { DNDLayout } from 'src/components/Layout'
import {
identifyAdvBlocked,
setupAnalytics,
setupIdentification,
trackPageView,
} from 'src/lib/analytics'
import { setupAnalytics, setupIdentification, trackPageView } from 'src/lib/analytics'
import { useUserPreferences } from 'src/stores/preferences'
import { AppContentLayout } from './components/Layout'
import { verifyAdvStatus } from './features/adv/utils/status'
import { lazyImport } from './utils/lazyImport'
const { OnboardingModal } = lazyImport(() => import('src/features/onboarding'), 'OnboardingModal')

Expand All @@ -27,7 +21,6 @@ export const App = () => {
const {
maxVisibleCards,
onboardingCompleted,
setAdvStatus,
isDNDModeActive,
layout,
DNDDuration,
Expand All @@ -42,12 +35,6 @@ export const App = () => {
document.body.classList.remove('preload')
setupAnalytics()
setupIdentification()
const adVerifier = async () => {
const status = await verifyAdvStatus()
setAdvStatus(status)
identifyAdvBlocked(status)
}
adVerifier()
}, [])

useEffect(() => {
Expand Down
9 changes: 9 additions & 0 deletions src/assets/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ a {
z-index: 1;
}

.AppHeader .upgradeButton {
font-weight: bold;
background-color: var(--tab-positive-button-background) !important;
color: white !important;
&:hover {
opacity: 0.8;
}
}

.AppFooter {
display: flex;
flex-direction: row;
Expand Down
18 changes: 18 additions & 0 deletions src/components/Layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { clsx } from 'clsx'
import { useCallback, useEffect, useState } from 'react'
import { BsFillBookmarksFill, BsFillGearFill, BsMoonFill } from 'react-icons/bs'
import { CgTab } from 'react-icons/cg'
import { FaCrown } from 'react-icons/fa'
import { IoMdSunny } from 'react-icons/io'
import { MdDoDisturbOff } from 'react-icons/md'
import { RiDashboardHorizontalFill } from 'react-icons/ri'
Expand All @@ -13,19 +14,25 @@ import HackertabLogo from 'src/assets/logo.svg?react'
import { UserTags } from 'src/components/Elements/UserTags'
import { useAuth } from 'src/features/auth'
import { Changelog } from 'src/features/changelog'
import { useRemoteConfigStore } from 'src/features/remoteConfig'
import {
identifyUserTheme,
trackDNDDisable,
trackDisplayTypeChange,
trackThemeSelect,
} from 'src/lib/analytics'
import { useUserPreferences } from 'src/stores/preferences'
import { lazyImport } from 'src/utils/lazyImport'
import { Button, CircleButton } from '../Elements'
import { SearchEngineBar } from '../Elements/SearchBar/SearchEngineBar'
const { DonateModal } = lazyImport(() => import('src/features/donate'), 'DonateModal')

export const Header = () => {
const { openAuthModal, user, isConnected, isConnecting } = useAuth()

const [themeIcon, setThemeIcon] = useState(<BsMoonFill />)
const [openDonateModal, setOpenDonateModal] = useState(false)
const { paywall } = useRemoteConfigStore()
const { theme, setTheme, setDNDDuration, isDNDModeActive, layout, setLayout } =
useUserPreferences()
const navigate = useNavigate()
Expand Down Expand Up @@ -68,6 +75,10 @@ export const Header = () => {
setDNDDuration('never')
}, [setDNDDuration])

const onUpgradeClicked = useCallback(() => {
setOpenDonateModal(true)
}, [])

return (
<>
<header className="AppHeader">
Expand Down Expand Up @@ -129,7 +140,14 @@ export const Header = () => {
<AvatarPlaceholder className="avatarPlaceholder" />
)}
</CircleButton>
{paywall?.enabled && (
<Button onClick={onUpgradeClicked} className="upgradeButton">
<FaCrown />
{paywall.headerCta}
</Button>
)}
</div>
{openDonateModal && <DonateModal setModalOpen={setOpenDonateModal} />}
{location.pathname === '/' && <UserTags />}
</header>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,13 @@
@media only screen and (max-width: 768px) {
.settingsContent {
padding: 16px;
min-height: 100vh;
height: 100vh;
}
.settingsBody {
max-width: 100%;
padding: 0;
flex: 1 1 auto;
min-height: 0;
}
}
2 changes: 0 additions & 2 deletions src/config/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ export const maxCardsPerRow = 4
export const supportLink = 'https://github.com/medyo/hackertab.dev/issues'
export const privacyPolicyLink = 'https://www.hackertab.dev/privacy-policy'
export const termsAndConditionsLink = 'https://www.hackertab.dev/terms-and-conditions'
export const dataSourcesLink = 'https://www.hackertab.dev/data-sources'
export const changeLogLink = 'https://api.github.com/repos/medyo/hackertab.dev/releases'
export const twitterHandle = '@hackertabdev'
export const reportLink = 'https://www.hackertab.dev/report'

export const LS_PREFERENCES_KEY = 'hackerTabPrefs'
export const MAX_ITEMS_PER_CARD = 50
Expand Down
8 changes: 4 additions & 4 deletions src/features/MarketingBanner/components/MarketingBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { Campaign, MarketingConfig } from '../types'

export const MarketingBanner = () => {
const { setCampaignClosed, closedCampaigns } = useMarketingConfigStore()
const { isConnected } = useAuth()
const { userSelectedTags, cards, firstSeenDate, advStatus } = useUserPreferences()
const { isConnected, user } = useAuth()
const { userSelectedTags, cards, firstSeenDate } = useUserPreferences()
const [availableCampaigns, setAvailableCampaigns] = useState<Campaign[]>([])
const { data: marketingConfig } = useGetMarketingConfig({
config: {
Expand All @@ -39,11 +39,11 @@ export const MarketingBanner = () => {
userTags: userSelectedTags.map((tag) => tag.label),
cards: cards.map((card) => card.name),
firstSeenDate,
adv: advStatus,
isConnected,
isSupported: user?.isSupporter || false,
usageInDays: diffBetweenTwoDatesInDays(firstSeenDate, Date.now()),
}
}, [userSelectedTags, firstSeenDate, cards, advStatus])
}, [userSelectedTags, firstSeenDate, cards, user])

useEffect(() => {
if (marketingConfig && marketingConfig.version === 1) {
Expand Down
4 changes: 2 additions & 2 deletions src/features/adv/api/getAd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Ad } from '../types'

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

type QueryFnType = typeof getAd
Expand All @@ -18,7 +18,7 @@ type UseGetAdOptions = {
export const useGetAd = ({ keywords, feed, config }: UseGetAdOptions) => {
return useQuery<ExtractFnReturnType<QueryFnType>>({
...config,
queryKey: ['ad', keywords.join(',')],
queryKey: ['ad', 'v2', keywords.join(',')],
queryFn: () => getAd(keywords, feed),
})
}
18 changes: 18 additions & 0 deletions src/features/adv/components/AdvBanner.css
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,21 @@
.advFeed:has(.banneradv) .rowDetails {
margin-left: auto;
}

.houseBanner {
max-width: none;
width: 100%;
img {
border-radius: 8px;
object-fit: contain;
display: block;
margin: 0 auto;
max-width: 100%;
}
a {
width: 100%;
&:hover {
opacity: 0.8;
}
}
}
102 changes: 16 additions & 86 deletions src/features/adv/components/AdvBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { useEffect } from 'react'
import { AdPlaceholder } from 'src/components/placeholders'
import { useRemoteConfigStore } from 'src/features/remoteConfig'
import { trackMarketingCampaignOpen } from 'src/lib/analytics'
import { useUserPreferences } from 'src/stores/preferences'
import { isWebOrExtensionVersion } from 'src/utils/Environment'
import { useGetAd } from '../api/getAd'
import { useDelayedFlag } from '../hooks/useDelayedFlag'
import { Ad } from '../types'
import './AdvBanner.css'

Expand All @@ -14,12 +12,8 @@ type AdvBannerProps = {
loadingState?: React.ReactNode
}

export const AdvBanner = ({ feedDisplay = false, loadingState, onAdLoaded }: AdvBannerProps) => {
export const AdvBanner = ({ loadingState, onAdLoaded }: AdvBannerProps) => {
const { userSelectedTags } = useUserPreferences()
const adsFetchDelayMs = useRemoteConfigStore((s) => s.adsFetchDelayMs)
const delay = isWebOrExtensionVersion() === 'extension' ? adsFetchDelayMs : undefined
const isReady = useDelayedFlag(delay)

const {
isSuccess,
data: ad,
Expand All @@ -31,7 +25,6 @@ export const AdvBanner = ({ feedDisplay = false, loadingState, onAdLoaded }: Adv
config: {
cacheTime: 0,
staleTime: 0,
enabled: isReady,
useErrorBoundary: false,
},
})
Expand All @@ -50,85 +43,22 @@ export const AdvBanner = ({ feedDisplay = false, loadingState, onAdLoaded }: Adv
return null
}

if (ad.largeImage) {
const onAdClick = () => {
if (ad?.id) {
trackMarketingCampaignOpen(ad.id, {
source: 'card',
})
}
}
if (ad.type === 'house-ad-banner') {
return (
<>
<div
className="carbonCoverTarget"
style={
{
'--ad-dynamic-bg-image': `url(${ad.largeImage})`,
'--ad-gradient-color': ad.backgroundColor,
} as React.CSSProperties
}>
<a href={ad.link} className="carbonCover">
<img className="carbonCoverImage" src={ad.largeImage} />
<div className="carbonCoverMain">
<img className="carbonCoverLogo" src={ad.logo} />
<div className="carbonCoverTagline">{ad.companyTagline}</div>
<div className="carbonCoverDescription">{ad.description}</div>
<div className="carbonCoverButton">{ad.callToAction + ' ↗'}</div>
</div>
</a>
</div>
{ad.viewUrl &&
ad.viewUrl
.split('||')
.map((viewUrl, i) => (
<img
key={i}
src={viewUrl.replace('[timestamp]', `${Math.round(Date.now() / 10000) | 0}`)}
className="hidden"
alt=""
/>
))}
</>
<div className="houseBanner">
<a onClick={onAdClick} href={ad.link} target="_blank" title={ad.title}>
<img src={ad.imageUrl} alt={ad.title} />
</a>
</div>
)
}

return (
<>
<div className="banneradv">
<a
href={ad.link}
className="img"
target="_blank"
rel="noopener sponsored noreferrer"
title={ad.title}>
<img
src={ad.imageUrl}
alt={ad.title}
height={!feedDisplay ? '120' : '200'}
width={!feedDisplay ? '156' : '260'}
style={{ border: 0 }}
/>
</a>

<a href={ad.link} className="text" target="_blank" rel="noopener sponsored noreferrer">
{ad.description}
</a>

{!feedDisplay && (
<a
href={ad.provider.link}
className="poweredby"
target="_blank"
rel="noopener sponsored noreferrer">
{ad.provider.title}
</a>
)}
</div>
{ad.viewUrl &&
ad.viewUrl
.split('||')
.map((viewUrl, i) => (
<img
key={i}
src={viewUrl.replace('[timestamp]', `${Math.round(Date.now() / 10000) | 0}`)}
className="hidden"
alt=""
/>
))}
</>
)
return null
}
14 changes: 13 additions & 1 deletion src/features/adv/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ type NextAdType = {
interval: number
}

export type Ad = {
export type Ad = DefaultAd | HouseAdBanner

export type DefaultAd = {
id?: string
type?: string
title?: string
description: string
imageUrl: string
Expand All @@ -25,3 +29,11 @@ export type Ad = {
callToAction?: string
company?: string
}
export type HouseAdBanner = {
type: 'house-ad-banner'
id: string
title: string
description: string
link: string
imageUrl: string
}
26 changes: 26 additions & 0 deletions src/features/donate/components/DonateModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import ReactModal from 'react-modal'
import { DonateView } from './DonateView'
import './donate.css'

type DonateModalProps = {
setModalOpen: (open: boolean) => void
}
export const DonateModal = ({ setModalOpen }: DonateModalProps) => {
return (
<ReactModal
isOpen={true}
ariaHideApp={false}
shouldCloseOnEsc={false}
shouldCloseOnOverlayClick={false}
shouldFocusAfterRender={false}
className="Modal scrollable donateModal"
style={{
overlay: {
zIndex: 3,
},
}}
overlayClassName="Overlay">
<DonateView setModalOpen={setModalOpen} />
</ReactModal>
)
}
Loading