Skip to content

Commit 65543b9

Browse files
committed
add medium card
1 parent c28108e commit 65543b9

6 files changed

Lines changed: 188 additions & 2 deletions

File tree

src/Constants.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React from 'react'
22
import ConferencesCard from './cards/ConferencesCard'
33
import ReposCard from './cards/ReposCard'
44
import FreeCodeCampCard from './cards/FreeCodeCampCard'
5-
import MediumCard from './cards/MediumCard'
65
import { SiGithub } from 'react-icons/si'
76
import { SiYcombinator } from 'react-icons/si'
87
import { FaDev } from 'react-icons/fa'
@@ -20,6 +19,8 @@ import { RedditCard } from 'src/features/redditCard'
2019
import { LobstersCard } from 'src/features/lobstersCard'
2120
import { DevtoCard } from 'src/features/devtoCard'
2221
import { HashnodeCard } from 'src/features/hashnodeCard'
22+
import { MediumCard } from 'src/features/mediumCard'
23+
2324

2425

2526

src/features/hashnodeCard/components/HashnodeCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function HashnodeCard(props: CardPropsType) {
6262
const HeaderTitle = () => {
6363
return (
6464
<div style={{ display: 'inline-block', margin: 0, padding: 0 }}>
65-
<span> DevTo </span>
65+
<span> Hashnode </span>
6666
<SelectableCard
6767
isLanguage={true}
6868
tagId={HN_MENU_LANGUAGE_ID}
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: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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 { MdWavingHand } from 'react-icons/md'
9+
10+
const ArticleItem = (props: ArticleItemPropsType) => {
11+
const { item, index, listingMode, selectedTag } = props
12+
13+
return (
14+
<CardItemWithActions
15+
source={'medium'}
16+
index={index}
17+
key={index}
18+
item={item}
19+
cardItem={
20+
<>
21+
<CardLink
22+
link={item.url}
23+
analyticsAttributes={{
24+
[Attributes.POINTS]: item.reactions,
25+
[Attributes.TRIGERED_FROM]: 'card',
26+
[Attributes.TITLE]: item.title,
27+
[Attributes.LINK]: item.url,
28+
[Attributes.SOURCE]: 'medium',
29+
[Attributes.LANGUAGE]: selectedTag?.value,
30+
}}>
31+
{listingMode === 'compact' && (
32+
<div className="counterWrapper">
33+
<MdWavingHand />
34+
<span className="value">{item.reactions || 0}</span>
35+
</div>
36+
)}
37+
<div className="subTitle">{item.title}</div>
38+
</CardLink>
39+
40+
{listingMode === 'normal' && (
41+
<p className="rowDetails">
42+
<span className="rowItem mediumRowItem">
43+
<MdWavingHand className={'rowItemIcon'} /> {item.reactions || 0} claps
44+
</span>
45+
<span className="rowItem">
46+
<BiCommentDetail className={'rowItemIcon'} /> {item.comments || 0} comments
47+
</span>
48+
<span className="rowItem">
49+
<MdAccessTime className={'rowItemIcon'} />
50+
{format(new Date(item.published_at))}
51+
</span>
52+
</p>
53+
)}
54+
</>
55+
}
56+
/>
57+
)
58+
}
59+
60+
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 MEDIUM_MENU_LANGUAGE_ID = 'MEDIUM_MENU_LANGUAGE_ID'
16+
17+
export function MediumCard(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={`md-${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> Medium </span>
66+
<SelectableCard
67+
isLanguage={true}
68+
tagId={MEDIUM_MENU_LANGUAGE_ID}
69+
selectedTag={selectedTag}
70+
setSelectedTag={setSelectedTag}
71+
fallbackTag={GLOBAL_TAG}
72+
cardSettings={cardsSettings?.medium?.language}
73+
trackEvent={(tag: Tag) => trackCardLanguageSelect('Medium', 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://medium.com/"
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/mediumCard/index.ts

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

0 commit comments

Comments
 (0)