Skip to content

Commit c4af437

Browse files
committed
Merge branch 'refactor/typescript-cards' of github.com:medyo/hackertab.dev into refactor/typescript-cards
2 parents 3acb910 + 47b371c commit c4af437

20 files changed

Lines changed: 163 additions & 234 deletions

File tree

src/components/AppContentLayout.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ function MobileCards({ selectedCard }) {
1010
currentCard &&
1111
React.createElement(currentCard.component, {
1212
key: currentCard.value,
13-
label: currentCard.label,
14-
analyticsTag: currentCard.analyticsTag,
13+
meta: currentCard,
1514
withAds: true,
1615
})
1716
)
@@ -22,9 +21,7 @@ function DesktopCards({ cards }) {
2221
const constantCard = SUPPORTED_CARDS.find((c) => c.value === card.name)
2322
return React.createElement(constantCard.component, {
2423
key: card.name,
25-
label: constantCard.label,
26-
icon: constantCard.icon,
27-
analyticsTag: constantCard.analyticsTag,
24+
meta: constantCard,
2825
withAds: index === 0,
2926
})
3027
})
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from 'react'
2+
import { BsBoxArrowInUpRight } from 'react-icons/bs'
3+
import { ref } from 'src/config'
4+
import { useUserPreferences } from 'src/stores/preferences'
5+
import { SupportedCard } from 'src/config'
6+
7+
type CardProps = {
8+
children: React.ReactNode
9+
card: SupportedCard
10+
titleComponent?: React.ReactNode
11+
fullBlock?: boolean
12+
}
13+
export const Card = ({
14+
card: { link, icon, label },
15+
titleComponent,
16+
children,
17+
fullBlock = false,
18+
}: CardProps) => {
19+
const { openLinksNewTab } = useUserPreferences()
20+
21+
const handleHeaderLinkClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
22+
e.preventDefault()
23+
let url = `${link}?${ref}`
24+
window.open(url, openLinksNewTab ? '_blank' : '_self')
25+
}
26+
27+
return (
28+
<div className={'block' + (fullBlock ? ' fullBlock' : '')}>
29+
<div className="blockHeader">
30+
<span className="blockHeaderIcon">{icon}</span> {titleComponent || label}{' '}
31+
{link && (
32+
<a className="blockHeaderLink" href={link} onClick={handleHeaderLinkClick}>
33+
<BsBoxArrowInUpRight />
34+
</a>
35+
)}
36+
</div>
37+
<div className="blockContent scrollable">{children}</div>
38+
</div>
39+
)
40+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./Card"

src/components/List/ListComponent.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { ReactNode } from 'react'
22
import { Placeholder } from 'src/components/placeholders'
33
import { ListComponentPropsType } from './types'
44
import { CarbonAd } from 'src/features/carbonAds'
5+
import { BaseEntry } from 'src/types'
56

6-
export function ListComponent(props: ListComponentPropsType) {
7+
export function ListComponent<T extends BaseEntry>(props: ListComponentPropsType<T>) {
78
const { items, isLoading, error, renderItem, withAds, placeholder = <Placeholder /> } = props
89

910
if (error) {
@@ -14,20 +15,20 @@ export function ListComponent(props: ListComponentPropsType) {
1415
if (!items) {
1516
return
1617
}
17-
18+
1819
return items.map((item, index) => {
1920
let content: ReactNode[] = [renderItem(item, index)]
20-
if (withAds && index === 0) {
21-
content.unshift(<CarbonAd key={'carbonAd0'} />)
22-
}
21+
if (withAds && index === 0) {
22+
content.unshift(<CarbonAd key={'carbonAd0'} />)
23+
}
2324
return content
2425
})
2526
}
2627

2728
function Placeholders() {
2829
return (
2930
<>
30-
{[...Array(5)].map((x, i) => (
31+
{[...Array(7)].map((x, i) => (
3132
<span key={i}>{placeholder}</span>
3233
))}
3334
</>

src/components/List/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import React from 'react'
2-
import { ArticleType, ConferenceType, RepoType } from 'src/types'
2+
import { BaseEntry } from 'src/types'
33

4-
export type ListComponentPropsType = {
5-
items: ArticleType[] | ConferenceType[] | RepoType[],
4+
export type ListComponentPropsType<T extends BaseEntry> = {
5+
items: T[],
66
isLoading: boolean,
7-
renderItem: (item: any, index: number) => React.ReactNode,
7+
renderItem: (item: T, index: number) => React.ReactNode,
88
withAds: boolean,
99
placeholder?: React.ReactNode,
1010
refresh?: boolean,

src/components/ListComponent.js

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/config/index.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,90 +70,102 @@ export const SUPPORTED_SEARCH_ENGINES = [
7070
},
7171
]
7272

73-
type SupportedCard = {
73+
export type SupportedCard = {
7474
value: string
7575
icon: React.ReactNode
7676
analyticsTag: string
7777
label: string
78+
link: string
7879
component: React.FunctionComponent<CardPropsType>
7980
}
8081
export const SUPPORTED_CARDS: SupportedCard[] = [
8182
{
8283
value: 'github',
83-
analyticsTag: 'repos',
84+
analyticsTag: 'github',
8485
label: 'Github repositories',
8586
component: GithubCard,
8687
icon: <SiGithub className="blockHeaderWhite" />,
88+
link: 'https://github.com/',
8789
},
8890
{
8991
value: 'hackernews',
9092
icon: <SiYcombinator color="#FB6720" />,
9193
analyticsTag: 'hackernews',
9294
label: 'Hackernews',
9395
component: HackernewsCard,
96+
link: 'https://news.ycombinator.com/',
9497
},
9598
{
9699
value: 'conferences',
97100
icon: <HiTicket color="#4EC8AF" />,
98101
analyticsTag: 'events',
99102
label: 'Upcoming events',
100103
component: ConferencesCard,
104+
link: 'https://confs.tech/',
101105
},
102106
{
103107
value: 'devto',
104108
icon: <FaDev className="blockHeaderWhite" />,
105109
analyticsTag: 'devto',
106110
label: 'DevTo',
107111
component: DevtoCard,
112+
link: 'https://dev.to/',
108113
},
109114
{
110115
value: 'producthunt',
111116
icon: <SiProducthunt color="#D65736" />,
112117
analyticsTag: 'producthunt',
113118
label: 'Product Hunt',
114119
component: ProductHuntCard,
120+
link: 'https://producthunt.com/',
115121
},
116122
{
117123
value: 'reddit',
118124
icon: <FaReddit color="#FF4500" />,
119125
analyticsTag: 'reddit',
120126
label: 'Reddit',
121127
component: RedditCard,
128+
link: 'https://reddit.com/',
122129
},
123130
{
124131
value: 'lobsters',
125132
icon: <img alt="lobsters" src={LobstersIcon} />,
126133
analyticsTag: 'lobsters',
127134
label: 'Lobsters',
128135
component: LobstersCard,
136+
link: 'https://lobste.rs/',
129137
},
130138
{
131139
value: 'hashnode',
132140
icon: <img alt="hn" src={HashNodeIcon} />,
133141
analyticsTag: 'hashnode',
134142
label: 'Hashnode',
135143
component: HashnodeCard,
144+
link: 'https://hashnode.com/',
136145
},
137146
{
138147
value: 'freecodecamp',
139148
icon: <FaFreeCodeCamp className="blockHeaderWhite" />,
140149
analyticsTag: 'freecodecamp',
141150
label: 'FreeCodeCamp',
142151
component: FreecodecampCard,
152+
link: 'https://freecodecamp.com/news',
143153
},
144154
{
145155
value: 'indiehackers',
146156
icon: <CgIndieHackers className="blockHeaderWhite" />,
147157
analyticsTag: 'indiehackers',
148158
label: 'IndieHackers',
149159
component: IndiehackersCard,
160+
link: 'https://indiehackers.com/',
150161
},
151162
{
152163
value: 'medium',
153164
icon: <FaMediumM />,
154165
analyticsTag: 'medium',
155166
label: 'Medium',
156167
component: MediumCard,
168+
link: 'https://medium.com/',
157169
},
158170
]
159171

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
import CardComponent from 'src/components/CardComponent'
1+
import { Card } from 'src/components/Elements/Card'
22
import { ListComponent } from 'src/components/List'
33
import { useGetConferences } from '../api/getConferences'
44
import { ConferenceType, CardPropsType } from 'src/types'
55
import { useUserPreferences } from 'src/stores/preferences'
66
import { getCardTagsValue } from 'src/utils/DataEnhancement'
77
import ConferenceItem from './ConferenceItem'
88

9-
10-
export function ConferencesCard(props: CardPropsType) {
11-
const { label, icon, withAds } = props
9+
export function ConferencesCard({ meta, withAds }: CardPropsType) {
1210
const { userSelectedTags, listingMode } = useUserPreferences()
1311

1412
const results = useGetConferences({ tags: getCardTagsValue(userSelectedTags, 'devtoValues') })
@@ -25,25 +23,17 @@ export function ConferencesCard(props: CardPropsType) {
2523
}
2624

2725
const renderItem = (item: ConferenceType, index: number) => (
28-
<ConferenceItem
29-
item={item}
30-
key={`cf-${index}`}
31-
index={index}
32-
listingMode={listingMode}
33-
/>
26+
<ConferenceItem item={item} key={`cf-${index}`} index={index} listingMode={listingMode} />
3427
)
3528

3629
return (
37-
<CardComponent
38-
icon={<span className="blockHeaderIcon">{icon}</span>}
39-
link="https://confs.tech/"
40-
title={label}>
30+
<Card card={meta}>
4131
<ListComponent
4232
items={getData()}
4333
isLoading={isLoading}
4434
renderItem={renderItem}
4535
withAds={withAds}
4636
/>
47-
</CardComponent>
37+
</Card>
4838
)
4939
}

src/features/devtoCard/components/DevtoCard.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useEffect } from 'react'
2-
import CardComponent from 'src/components/CardComponent'
2+
import { Card } from 'src/components/Elements/Card'
33
import { ListComponent } from 'src/components/List'
44
import { useGetArticles } from '../api/getArticles'
55
import { ArticleType, CardPropsType } from 'src/types'
@@ -13,16 +13,15 @@ import SelectableCard from 'src/components/SelectableCard'
1313

1414
const DT_MENU_LANGUAGE_ID = 'DT_MENU_LANGUAGE_ID'
1515

16-
export function DevtoCard(props: CardPropsType) {
17-
const { label, icon, withAds } = props
16+
export function DevtoCard({ withAds, meta }: CardPropsType) {
1817
const { userSelectedTags, cardsSettings, setCardSettings, listingMode } = useUserPreferences()
1918
const [selectedTag, setSelectedTag] = useState<Tag>()
2019

2120
useEffect(() => {
2221
if (selectedTag) {
23-
setCardSettings(label.toLowerCase(), { language: selectedTag.label })
22+
setCardSettings(meta.label.toLowerCase(), { language: selectedTag.label })
2423
}
25-
}, [selectedTag])
24+
}, [selectedTag, meta, setCardSettings])
2625

2726
const getQueryTags = () => {
2827
if (!selectedTag) {
@@ -61,15 +60,15 @@ export function DevtoCard(props: CardPropsType) {
6160
const HeaderTitle = () => {
6261
return (
6362
<div style={{ display: 'inline-block', margin: 0, padding: 0 }}>
64-
<span> DevTo </span>
63+
<span> {meta.label} </span>
6564
<SelectableCard
6665
isLanguage={true}
6766
tagId={DT_MENU_LANGUAGE_ID}
6867
selectedTag={selectedTag}
6968
setSelectedTag={setSelectedTag}
7069
fallbackTag={GLOBAL_TAG}
7170
cardSettings={cardsSettings?.devto?.language}
72-
trackEvent={(tag: Tag) => trackCardLanguageSelect('Devto', tag.value)}
71+
trackEvent={(tag: Tag) => trackCardLanguageSelect(meta.analyticsTag, tag.value)}
7372
data={userSelectedTags.map((tag) => ({
7473
label: tag.label,
7574
value: tag.value,
@@ -80,16 +79,13 @@ export function DevtoCard(props: CardPropsType) {
8079
}
8180

8281
return (
83-
<CardComponent
84-
icon={<span className="blockHeaderIcon">{icon}</span>}
85-
link="https://dev.to/"
86-
title={<HeaderTitle />}>
82+
<Card card={meta} titleComponent={<HeaderTitle />}>
8783
<ListComponent
8884
items={getData()}
8985
isLoading={getIsLoading()}
9086
renderItem={renderItem}
9187
withAds={withAds}
9288
/>
93-
</CardComponent>
89+
</Card>
9490
)
9591
}

0 commit comments

Comments
 (0)