Skip to content

Commit 5e93278

Browse files
committed
devto card
1 parent eb80a55 commit 5e93278

5 files changed

Lines changed: 197 additions & 3 deletions

File tree

src/Constants.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React from 'react'
2-
import DevToCard from './cards/DevToCard'
32
import ConferencesCard from './cards/ConferencesCard'
43
import ReposCard from './cards/ReposCard'
54
import HashNodeCard from './cards/HashNodeCard'
@@ -20,6 +19,8 @@ import { ProductHuntCard } from 'src/features/producthuntCard'
2019
import { IndiehackersCard } from 'src/features/indiehackersCard'
2120
import { RedditCard } from 'src/features/redditCard'
2221
import { LobstersCard } from 'src/features/lobstersCard'
22+
import { DevtoCard } from 'src/features/devtoCard'
23+
2324

2425

2526

@@ -64,7 +65,7 @@ export const SUPPORTED_CARDS = [
6465
icon: <FaDev className="blockHeaderWhite" />,
6566
analyticsTag: 'devto',
6667
label: 'DevTo',
67-
component: DevToCard,
68+
component: DevtoCard,
6869
},
6970
{
7071
value: 'producthunt',
@@ -152,7 +153,7 @@ export const GLOBAL_TAG = {
152153
value: 'global',
153154
label: 'Trending',
154155
githubValues: ['global'],
155-
devtoValues: [''],
156+
devtoValues: ['programming'],
156157
hashnodeValues: ['programming'],
157158
mediumValues: ['programming'],
158159
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useQueries, UseQueryOptions } from '@tanstack/react-query'
2+
import { QueryConfig } from 'src/lib/react-query'
3+
import { ArticleType } from 'src/types'
4+
import { axios } from 'src/lib/axios'
5+
6+
const getArticles = async (tag: string): Promise<ArticleType[]> => {
7+
return axios.get(`/data/v2/devto/${tag}.json`)
8+
}
9+
10+
type QueryFnType = typeof getArticles
11+
12+
type UseGetArticlesOptions = {
13+
config?: QueryConfig<QueryFnType>
14+
tags: string[]
15+
}
16+
17+
export const useGetArticles = ({ config, tags }: UseGetArticlesOptions) => {
18+
return useQueries({
19+
queries: tags.map<UseQueryOptions<ArticleType[]>>((tag) => {
20+
return {
21+
...config,
22+
queryKey: ['devtoArticles', tag],
23+
queryFn: () => getArticles(tag),
24+
}
25+
})
26+
})
27+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { BiCommentDetail } from 'react-icons/bi'
2+
import CardLink from 'src/components/CardLink'
3+
import CardItemWithActions from 'src/components/CardItemWithActions'
4+
import { Attributes } from 'src/lib/analytics'
5+
import { ArticleItemPropsType } from 'src/types'
6+
import { format } from 'timeago.js'
7+
import { MdAccessTime } from 'react-icons/md'
8+
import { AiOutlineLike } from 'react-icons/ai'
9+
import ColoredLanguagesBadge from 'src/components/ColoredLanguagesBadge'
10+
11+
const ArticleItem = (props: ArticleItemPropsType) => {
12+
const { item, index, listingMode, selectedTag } = props
13+
14+
return (
15+
<CardItemWithActions
16+
source={'devto'}
17+
index={index}
18+
key={index}
19+
item={item}
20+
cardItem={
21+
<>
22+
<CardLink
23+
link={item.url}
24+
analyticsAttributes={{
25+
[Attributes.TRIGERED_FROM]: 'card',
26+
[Attributes.POINTS]: item.reactions,
27+
[Attributes.TITLE]: item.title,
28+
[Attributes.LINK]: item.url,
29+
[Attributes.SOURCE]: 'devto',
30+
[Attributes.LANGUAGE]: selectedTag?.value,
31+
}}>
32+
{listingMode === 'compact' && (
33+
<div className="counterWrapper">
34+
<AiOutlineLike />
35+
<span className="value">{item.reactions}</span>
36+
</div>
37+
)}
38+
<div className="subTitle">{item.title}</div>
39+
</CardLink>
40+
41+
{listingMode === 'normal' && (
42+
<>
43+
<p className="rowDescription">
44+
<span className="rowItem">
45+
<MdAccessTime className={'rowTitleIcon'} />
46+
{format(new Date(item.published_at))}
47+
</span>
48+
<span className="rowItem">
49+
<BiCommentDetail className={'rowTitleIcon'} />
50+
{item.comments} comments
51+
</span>
52+
<span className="rowItem">
53+
<AiOutlineLike className={'rowTitleIcon'} />
54+
{item.reactions} reactions
55+
</span>
56+
</p>
57+
<p className="rowDetails">
58+
<ColoredLanguagesBadge languages={item.tags} />
59+
</p>
60+
</>
61+
)}
62+
</>
63+
}
64+
/>
65+
)
66+
}
67+
68+
export default ArticleItem
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { useState, useEffect } from 'react'
2+
import CardComponent from 'src/components/CardComponent'
3+
import { ListComponent } from 'src/components/List'
4+
import { useGetArticles } from '../api/getArticles'
5+
import { ArticleType, CardPropsType } from 'src/types'
6+
import { ProductHuntPlaceholder } from 'src/components/placeholders'
7+
import { useUserPreferences } from 'src/stores/preferences'
8+
import { getCardTagsValue } from 'src/utils/DataEnhancement'
9+
import ArticleItem from './ArticleItem'
10+
import { Tag } from 'src/features/remoteConfig'
11+
import { GLOBAL_TAG, MY_LANGUAGES_TAG } from 'src/Constants'
12+
import { trackCardLanguageSelect } from 'src/lib/analytics'
13+
import SelectableCard from 'src/components/SelectableCard'
14+
15+
const DT_MENU_LANGUAGE_ID = 'DT_MENU_LANGUAGE_ID'
16+
17+
export function DevtoCard(props: CardPropsType) {
18+
const { label, icon, withAds } = props
19+
const { userSelectedTags, cardsSettings, setCardSettings, listingMode } = useUserPreferences()
20+
const [selectedTag, setSelectedTag] = useState<Tag>()
21+
22+
useEffect(() => {
23+
if (selectedTag) {
24+
setCardSettings(label.toLowerCase(), { language: selectedTag.label })
25+
}
26+
}, [selectedTag])
27+
28+
const getQueryTags = () => {
29+
if (!selectedTag) {
30+
return []
31+
}
32+
33+
if (selectedTag.value === MY_LANGUAGES_TAG.devtoValues[0]) {
34+
return getCardTagsValue(userSelectedTags, 'devtoValues')
35+
}
36+
return selectedTag.devtoValues
37+
}
38+
39+
const results = useGetArticles({ tags: getQueryTags() })
40+
41+
const getIsLoading = () => results.some((result) => result.isLoading)
42+
43+
const getData = () => {
44+
return results
45+
.reduce((acc: ArticleType[], curr) => {
46+
if (!curr.data) return acc
47+
return [...acc, ...curr.data]
48+
}, [])
49+
.sort((a, b) => b.reactions - a.reactions)
50+
}
51+
52+
const renderItem = (item: ArticleType, index: number) => (
53+
<ArticleItem
54+
item={item}
55+
key={`at-${index}`}
56+
index={index}
57+
selectedTag={selectedTag}
58+
listingMode={listingMode}
59+
/>
60+
)
61+
62+
const HeaderTitle = () => {
63+
return (
64+
<div style={{ display: 'inline-block', margin: 0, padding: 0 }}>
65+
<span> DevTo </span>
66+
<SelectableCard
67+
isLanguage={true}
68+
tagId={DT_MENU_LANGUAGE_ID}
69+
selectedTag={selectedTag}
70+
setSelectedTag={setSelectedTag}
71+
fallbackTag={GLOBAL_TAG}
72+
cardSettings={cardsSettings?.devto?.language}
73+
trackEvent={(tag: Tag) => trackCardLanguageSelect('Devto', tag.value)}
74+
data={userSelectedTags.map((tag) => ({
75+
label: tag.label,
76+
value: tag.value,
77+
}))}
78+
/>
79+
</div>
80+
)
81+
}
82+
83+
return (
84+
<CardComponent
85+
icon={<span className="blockHeaderIcon">{icon}</span>}
86+
link="https://dev.to/"
87+
title={<HeaderTitle />}>
88+
<ListComponent
89+
items={getData()}
90+
isLoading={getIsLoading()}
91+
renderItem={renderItem}
92+
withAds={withAds}
93+
placeholder={<ProductHuntPlaceholder />}
94+
/>
95+
</CardComponent>
96+
)
97+
}

src/features/devtoCard/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./components/DevtoCard";

0 commit comments

Comments
 (0)