Skip to content

Commit 54e39c6

Browse files
committed
initial commit
1 parent be7b103 commit 54e39c6

25 files changed

Lines changed: 606 additions & 108 deletions

File tree

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"country-emoji": "^1.5.4",
1717
"dompurify": "^2.2.7",
1818
"eslint-plugin-react": "^7.28.0",
19+
"htmlparser2": "^8.0.1",
1920
"jsonpath": "^1.1.1",
2021
"localforage": "^1.9.0",
2122
"normalize.css": "^8.0.1",
@@ -35,6 +36,7 @@
3536
"react-spring-bottom-sheet": "^3.4.1",
3637
"react-toggle": "^4.1.1",
3738
"react-tooltip": "^4.2.21",
39+
"rss-to-json": "^2.1.1",
3840
"styled-components": "2",
3941
"timeago.js": "^4.0.2",
4042
"type-fest": "^1.2.0",

src/App.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ function App() {
3030
}
3131
// eslint-disable-next-line react-hooks/exhaustive-deps
3232
}, [onboardingCompleted, firstSeenDate])
33+
const [showRSSInput, setShowRSSInput] = useState(false)
3334

3435
useEffect(() => {
3536
setupAnalytics()
@@ -55,6 +56,8 @@ function App() {
5556
showSideBar={showSideBar}
5657
showSettings={showSettings}
5758
setShowSettings={setShowSettings}
59+
showRSSInput={showRSSInput}
60+
setShowRSSInput={setShowRSSInput}
5861
/>
5962
<ScrollCardsNavigator />
6063
<AppContentLayout setShowSettings={setShowSettings} />

src/components/Elements/Card/Card.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import React from 'react'
22
import { BsBoxArrowInUpRight } from 'react-icons/bs'
33
import { ref } from 'src/config'
44
import { useUserPreferences } from 'src/stores/preferences'
5-
import { SupportedCard } from 'src/config'
5+
import { SupportedCardType } from 'src/types'
66

77
type CardProps = {
88
children: React.ReactNode
9-
card: SupportedCard
9+
card: SupportedCardType
1010
titleComponent?: React.ReactNode
1111
fullBlock?: boolean
1212
}

src/components/Elements/FloatingFilter/FloatingFilter.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import { FiFilter } from 'react-icons/fi'
33
import { BottomSheet } from 'react-spring-bottom-sheet'
44
import 'react-spring-bottom-sheet/dist/style.css'
55
import { ChipsSet } from 'src/components/Elements'
6-
import { dateRanges, GLOBAL_TAG, MY_LANGUAGES_TAG, SupportedCard } from 'src/config'
6+
import { dateRanges, GLOBAL_TAG, MY_LANGUAGES_TAG } from 'src/config'
77
import { trackCardDateRangeSelect, trackCardLanguageSelect } from 'src/lib/analytics'
88
import { useUserPreferences } from 'src/stores/preferences'
9+
import { SupportedCardType } from 'src/types'
910

1011
type ListingFilterMobileProps = {
11-
card: SupportedCard
12+
card: SupportedCardType
1213
filters?: ('datesRange' | 'language')[]
1314
}
1415

src/components/Layout/AppContentLayout.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
import { useState } from 'react'
2-
import { BottomNavigation } from '../Elements'
32
import { isDesktop } from 'react-device-detect'
43
import { useUserPreferences } from 'src/stores/preferences'
5-
import { MobileCards } from './MobileCards'
4+
import { BottomNavigation } from '../Elements'
65
import { DesktopCards } from './DesktopCards'
6+
import { MobileCards } from './MobileCards'
77

88
export const AppContentLayout = ({
99
setShowSettings,
1010
}: {
1111
setShowSettings: (value: boolean | ((prevVar: boolean) => boolean)) => void
1212
}) => {
13-
const { cards } = useUserPreferences()
13+
const { cards, userCustomCards } = useUserPreferences()
1414
const [selectedCard, setSelectedCard] = useState(cards[0])
1515

1616
return (
1717
<>
1818
<main className="AppContent HorizontalScroll">
19-
{isDesktop ? <DesktopCards cards={cards} /> : <MobileCards selectedCard={selectedCard} />}
19+
{isDesktop ? (
20+
<DesktopCards cards={cards} userCustomCards={userCustomCards} />
21+
) : (
22+
<MobileCards selectedCard={selectedCard} />
23+
)}
2024
</main>
2125

2226
<BottomNavigation

src/components/Layout/DesktopCards.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
import React from 'react'
22
import { SUPPORTED_CARDS } from 'src/config'
3-
import { SelectedCard } from 'src/types'
3+
import { CustomRssCard } from 'src/features/cards'
4+
import { SelectedCard, SupportedCardType } from 'src/types'
45

5-
export const DesktopCards = ({ cards }: { cards: SelectedCard[] }) => {
6+
export const DesktopCards = ({
7+
cards,
8+
userCustomCards,
9+
}: {
10+
cards: SelectedCard[]
11+
userCustomCards: SupportedCardType[]
12+
}) => {
13+
const AVAILABLE_CARDS = [...SUPPORTED_CARDS, ...userCustomCards]
614
return (
715
<>
816
{cards.map((card, index) => {
9-
const constantCard = SUPPORTED_CARDS.find((c) => c.value === card.name)
17+
const constantCard = AVAILABLE_CARDS.find((c) => c.value === card.name)
1018
if (!constantCard) {
1119
return null
1220
}
1321

14-
return React.createElement(constantCard.component, {
22+
return React.createElement(constantCard?.component || CustomRssCard, {
1523
key: card.name,
1624
meta: constantCard,
1725
withAds: index === 0,

src/components/Layout/Header.tsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
1-
import React, { useEffect, useRef, useState } from 'react'
2-
import { BsFillGearFill } from 'react-icons/bs'
1+
import { useEffect, useRef, useState } from 'react'
2+
import { BsFillBookmarksFill, BsFillGearFill, BsMoon } from 'react-icons/bs'
33
import { CgTab } from 'react-icons/cg'
4-
import { BsFillBookmarksFill } from 'react-icons/bs'
4+
import { IoMdSunny } from 'react-icons/io'
55
import { ReactComponent as HackertabLogo } from 'src/assets/logo.svg'
6+
import { SearchBar } from 'src/components/Elements/SearchBar'
67
import { UserTags } from 'src/components/Elements/UserTags'
7-
import { SettingsModal } from 'src/features/settings'
8-
import { BsMoon } from 'react-icons/bs'
9-
import { IoMdSunny } from 'react-icons/io'
108
import { Changelog } from 'src/features/changelog'
11-
import { SearchBar } from 'src/components/Elements/SearchBar'
12-
import { useUserPreferences } from 'src/stores/preferences'
9+
import { RSSInputModal } from 'src/features/rssFeed'
10+
import { SettingsModal } from 'src/features/settings'
11+
import { identifyUserTheme, trackThemeSelect } from 'src/lib/analytics'
1312
import { useBookmarks } from 'src/stores/bookmarks'
14-
import { trackThemeSelect, identifyUserTheme } from 'src/lib/analytics'
13+
import { useUserPreferences } from 'src/stores/preferences'
1514

1615
type HeaderProps = {
1716
showSideBar: boolean
1817
setShowSideBar: (show: boolean) => void
1918
showSettings: boolean
2019
setShowSettings: (show: boolean) => void
20+
showRSSInput: boolean
21+
setShowRSSInput: (show: boolean) => void
2122
}
2223

2324
export const Header = ({
2425
showSideBar,
2526
setShowSideBar,
2627
showSettings,
2728
setShowSettings,
29+
showRSSInput,
30+
setShowRSSInput,
2831
}: HeaderProps) => {
2932
const [themeIcon, setThemeIcon] = useState(<BsMoon />)
3033
const isFirstRun = useRef(true)
@@ -75,9 +78,13 @@ export const Header = ({
7578
) : null
7679
}
7780

81+
const onAddSourceClick = () => {
82+
setShowRSSInput(true)
83+
}
7884
return (
7985
<>
8086
<SettingsModal showSettings={showSettings} setShowSettings={setShowSettings} />
87+
<RSSInputModal showRSSInput={showRSSInput} setShowRSSInput={setShowRSSInput} />
8188

8289
<header className="AppHeader">
8390
<span className="AppName">
@@ -99,6 +106,9 @@ export const Header = ({
99106
<BsFillBookmarksFill />
100107
<BookmarksBadgeCount />
101108
</button>
109+
<button className="extraBtn" onClick={onAddSourceClick}>
110+
<BsFillGearFill />
111+
</button>
102112
</div>
103113
<div className="break"></div>
104114
<UserTags onAddClicked={onSettingsClick} />

src/config/index.tsx

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
1-
import React from 'react'
2-
import { SiGithub } from 'react-icons/si'
3-
import { SiYcombinator } from 'react-icons/si'
4-
import { FaDev } from 'react-icons/fa'
5-
import { SiProducthunt } from 'react-icons/si'
6-
import { FaReddit, FaMediumM } from 'react-icons/fa'
1+
import { CgIndieHackers } from 'react-icons/cg'
2+
import { FaDev, FaFreeCodeCamp, FaMediumM, FaReddit } from 'react-icons/fa'
73
import { HiTicket } from 'react-icons/hi'
4+
import { SiGithub, SiProducthunt, SiYcombinator } from 'react-icons/si'
85
import HashNodeIcon from 'src/assets/icon_hashnode.png'
96
import LobstersIcon from 'src/assets/icon_lobsters.png'
10-
import { FaFreeCodeCamp } from 'react-icons/fa'
11-
import { CgIndieHackers } from 'react-icons/cg'
127
import {
13-
HackernewsCard,
14-
ProductHuntCard,
15-
IndiehackersCard,
16-
FreecodecampCard,
178
ConferencesCard,
9+
DevtoCard,
10+
FreecodecampCard,
1811
GithubCard,
19-
MediumCard,
12+
HackernewsCard,
2013
HashnodeCard,
14+
IndiehackersCard,
2115
LobstersCard,
22-
DevtoCard,
23-
RedditCard,
16+
MediumCard,
17+
ProductHuntCard,
18+
RedditCard
2419
} from 'src/features/cards'
25-
import { CardPropsType } from 'src/types'
20+
import { SupportedCardType } from 'src/types'
2621

2722
// Keys
2823
export const ANALYTICS_ENDPOINT = process.env.REACT_APP_AMPLITUDE_URL as string
@@ -71,23 +66,15 @@ export const SUPPORTED_SEARCH_ENGINES = [
7166
url: 'https://www.startpage.com/sp/search?query=',
7267
},
7368
]
74-
75-
export type SupportedCard = {
76-
value: string
77-
icon: React.ReactNode
78-
analyticsTag: string
79-
label: string
80-
link: string
81-
component: React.FunctionComponent<CardPropsType>
82-
}
83-
export const SUPPORTED_CARDS: SupportedCard[] = [
69+
export const SUPPORTED_CARDS: SupportedCardType[] = [
8470
{
8571
value: 'github',
8672
analyticsTag: 'github',
8773
label: 'Github repositories',
8874
component: GithubCard,
8975
icon: <SiGithub className="blockHeaderWhite" />,
9076
link: 'https://github.com/',
77+
type: 'supported',
9178
},
9279
{
9380
value: 'hackernews',
@@ -96,6 +83,7 @@ export const SUPPORTED_CARDS: SupportedCard[] = [
9683
label: 'Hackernews',
9784
component: HackernewsCard,
9885
link: 'https://news.ycombinator.com/',
86+
type: 'supported',
9987
},
10088
{
10189
value: 'conferences',
@@ -104,6 +92,7 @@ export const SUPPORTED_CARDS: SupportedCard[] = [
10492
label: 'Upcoming events',
10593
component: ConferencesCard,
10694
link: 'https://confs.tech/',
95+
type: 'supported',
10796
},
10897
{
10998
value: 'devto',
@@ -112,6 +101,7 @@ export const SUPPORTED_CARDS: SupportedCard[] = [
112101
label: 'DevTo',
113102
component: DevtoCard,
114103
link: 'https://dev.to/',
104+
type: 'supported',
115105
},
116106
{
117107
value: 'producthunt',
@@ -120,6 +110,7 @@ export const SUPPORTED_CARDS: SupportedCard[] = [
120110
label: 'Product Hunt',
121111
component: ProductHuntCard,
122112
link: 'https://producthunt.com/',
113+
type: 'supported',
123114
},
124115
{
125116
value: 'reddit',
@@ -128,6 +119,7 @@ export const SUPPORTED_CARDS: SupportedCard[] = [
128119
label: 'Reddit',
129120
component: RedditCard,
130121
link: 'https://reddit.com/',
122+
type: 'supported',
131123
},
132124
{
133125
value: 'lobsters',
@@ -136,6 +128,7 @@ export const SUPPORTED_CARDS: SupportedCard[] = [
136128
label: 'Lobsters',
137129
component: LobstersCard,
138130
link: 'https://lobste.rs/',
131+
type: 'supported',
139132
},
140133
{
141134
value: 'hashnode',
@@ -144,6 +137,7 @@ export const SUPPORTED_CARDS: SupportedCard[] = [
144137
label: 'Hashnode',
145138
component: HashnodeCard,
146139
link: 'https://hashnode.com/',
140+
type: 'supported',
147141
},
148142
{
149143
value: 'freecodecamp',
@@ -152,6 +146,7 @@ export const SUPPORTED_CARDS: SupportedCard[] = [
152146
label: 'FreeCodeCamp',
153147
component: FreecodecampCard,
154148
link: 'https://freecodecamp.com/news',
149+
type: 'supported',
155150
},
156151
{
157152
value: 'indiehackers',
@@ -160,6 +155,7 @@ export const SUPPORTED_CARDS: SupportedCard[] = [
160155
label: 'IndieHackers',
161156
component: IndiehackersCard,
162157
link: 'https://indiehackers.com/',
158+
type: 'supported',
163159
},
164160
{
165161
value: 'medium',
@@ -168,6 +164,7 @@ export const SUPPORTED_CARDS: SupportedCard[] = [
168164
label: 'Medium',
169165
component: MediumCard,
170166
link: 'https://medium.com/',
167+
type: 'supported',
171168
},
172169
]
173170

src/features/MarketingBanner/components/MarketingBanner.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import DOMPurify from 'dompurify'
2-
import { useMarketingConfigStore } from '../stores/marketingBanner'
3-
import { useUserPreferences } from 'src/stores/preferences'
4-
import { getAppVersion } from 'src/utils/Os'
5-
import { isWebOrExtensionVersion, isProduction, getBrowserName } from 'src/utils/Environment'
6-
import { useMemo, useState, useEffect } from 'react'
7-
import { Campaign, MarketingConfig } from '../types'
8-
import { useGetMarketingConfig } from '../api/getMarketingConfig'
2+
import jsonPath from 'jsonpath'
3+
import { useEffect, useMemo, useState } from 'react'
4+
import { isMobile } from 'react-device-detect'
95
import {
106
trackMarketingCampaignClose,
11-
trackMarketingCampaignView,
127
trackMarketingCampaignOpen,
8+
trackMarketingCampaignView,
139
} from 'src/lib/analytics'
10+
import { useUserPreferences } from 'src/stores/preferences'
1411
import { diffBetweenTwoDatesInDays } from 'src/utils/DateUtils'
15-
import { isMobile } from 'react-device-detect'
16-
import jsonPath from 'jsonpath'
12+
import { getBrowserName, isProduction, isWebOrExtensionVersion } from 'src/utils/Environment'
13+
import { getAppVersion } from 'src/utils/Os'
14+
import { useGetMarketingConfig } from '../api/getMarketingConfig'
15+
import { useMarketingConfigStore } from '../stores/marketingBanner'
16+
import { Campaign, MarketingConfig } from '../types'
1717

1818
export const MarketingBanner = () => {
1919
const { setCampaignClosed, closedCampaigns } = useMarketingConfigStore()
@@ -79,7 +79,6 @@ export const MarketingBanner = () => {
7979

8080
return availableCampaigns
8181
} catch (e) {
82-
console.log('getAvailableCampaigns', e)
8382
return []
8483
}
8584
}

0 commit comments

Comments
 (0)