@@ -2,10 +2,12 @@ import React, { useState } from 'react'
22import { VscClose } from 'react-icons/vsc'
33import ReactModal from 'react-modal'
44import Select , { ActionMeta , MultiValue , SingleValue } from 'react-select'
5+ import BeatLoader from 'react-spinners/BeatLoader'
56import Toggle from 'react-toggle'
67import 'react-toggle/style.css'
78import { SUPPORTED_CARDS , SUPPORTED_SEARCH_ENGINES , supportLink } from 'src/config'
89import { Tag , useRemoteConfigStore } from 'src/features/remoteConfig'
10+ import { getRssUrlFeed } from 'src/features/rssFeed/api/getRssFeed'
911import {
1012 identifyUserCards ,
1113 identifyUserLanguages ,
@@ -16,14 +18,17 @@ import {
1618 trackLanguageAdd ,
1719 trackLanguageRemove ,
1820 trackListingModeSelect ,
21+ trackRssSourceAdd ,
22+ trackRssSourceRemove ,
1923 trackSearchEngineSelect ,
2024 trackSourceAdd ,
2125 trackSourceRemove ,
2226 trackTabTarget ,
2327 trackThemeSelect ,
2428} from 'src/lib/analytics'
2529import { useUserPreferences } from 'src/stores/preferences'
26- import { SearchEngineType , SelectedCard } from 'src/types'
30+ import { SearchEngineType , SelectedCard , SupportedCardType } from 'src/types'
31+ import { isValidURL } from 'src/utils/UrlUtils'
2732import './settings.css'
2833
2934type SettingsModalProps = {
@@ -53,9 +58,14 @@ export const SettingsModal = ({ showSettings, setShowSettings }: SettingsModalPr
5358 setCards,
5459 setTags,
5560 userCustomCards,
61+ setUserCustomCards,
5662 } = useUserPreferences ( )
5763 const [ selectedCards , setSelectedCards ] = useState ( cards )
5864
65+ const [ rssUrl , setRssUrl ] = useState ( '' )
66+ const [ rssInputError , setRssInputError ] = useState ( '' )
67+ const [ isRssInputLoading , setIsRssInputLoading ] = useState ( false )
68+
5969 const AVAILABLE_CARDS = [ ...SUPPORTED_CARDS , ...userCustomCards ]
6070
6171 const handleCloseModal = ( ) => {
@@ -135,6 +145,64 @@ export const SettingsModal = ({ showSettings, setShowSettings }: SettingsModalPr
135145 identifyUserTheme ( newTheme )
136146 }
137147
148+ const onRssAddClick = async ( ) => {
149+ if ( ! isValidURL ( rssUrl ) ) {
150+ setRssInputError ( 'Invalid RSS Feed URL. Please check and try again.' )
151+ return
152+ }
153+
154+ // check if card exists
155+ const exists = userCustomCards . find ( ( card ) => card . feedUrl === rssUrl )
156+ if ( exists ) {
157+ setRssInputError ( 'RSS Feed already exists' )
158+ return
159+ }
160+
161+ setIsRssInputLoading ( true )
162+
163+ // get rssUrl Info
164+ try {
165+ const info = await getRssUrlFeed ( rssUrl )
166+ let customCard : SupportedCardType = {
167+ feedUrl : rssUrl . replace ( 'https:' , 'http:' ) ,
168+ label : info . title ,
169+ value : info . title . toLowerCase ( ) ,
170+ analyticsTag : info . title . toLowerCase ( ) ,
171+ link : info . link ,
172+ type : 'rss' ,
173+ // icon: <BsFillRssFill className="blockHeaderWhite" />,
174+ }
175+ // add card to userCustomCards and selected cards
176+ setUserCustomCards ( [ ...userCustomCards , customCard ] )
177+ const newCards = [
178+ ...cards ,
179+ { id : cards . length , name : customCard . value , type : customCard . type } ,
180+ ]
181+ setCards ( newCards )
182+ setSelectedCards ( newCards )
183+ identifyUserCards ( newCards . map ( ( card ) => card . name ) )
184+ trackRssSourceAdd ( customCard . value )
185+ setRssUrl ( '' )
186+ } catch ( err ) {
187+ setRssInputError ( 'rssInputError occured. Please check and try again.' )
188+ } finally {
189+ setIsRssInputLoading ( false )
190+ }
191+ }
192+
193+ const onRssSelectChange = ( newCards : MultiValue < OptionType > , metas : ActionMeta < OptionType > ) => {
194+ if ( metas . action === 'remove-value' ) {
195+ setUserCustomCards ( newCards as SupportedCardType [ ] )
196+ let newSelectedCards = cards . filter (
197+ ( c ) => c . type !== 'rss' || c . name !== metas . removedValue . value
198+ )
199+ setSelectedCards ( newSelectedCards )
200+ setCards ( newSelectedCards )
201+ identifyUserCards ( newSelectedCards . map ( ( card ) => card . name ) )
202+ trackRssSourceRemove ( metas . removedValue . value )
203+ }
204+ }
205+
138206 return (
139207 < ReactModal
140208 isOpen = { showSettings }
@@ -204,6 +272,40 @@ export const SettingsModal = ({ showSettings, setShowSettings }: SettingsModalPr
204272 </ div >
205273 </ div >
206274
275+ < div className = "settingRow" >
276+ < p className = "settingTitle" > Add Custom Source</ p >
277+ < div className = "settingContent" >
278+ < Select
279+ menuIsOpen = { false }
280+ options = { [ ] }
281+ value = { userCustomCards }
282+ onChange = { onRssSelectChange }
283+ isMulti = { true }
284+ isClearable = { false }
285+ isSearchable = { false }
286+ classNamePrefix = { 'hackertab' }
287+ className = { 'rss-sources' }
288+ />
289+ < div className = "rssUrlControl" >
290+ < input
291+ className = "rssUrlInput"
292+ value = { rssUrl }
293+ onChange = { ( e ) => setRssUrl ( e . target . value ) }
294+ />
295+ { isRssInputLoading ? (
296+ < BeatLoader color = { '#A9B2BD' } loading = { isRssInputLoading } size = { 6 } />
297+ ) : (
298+ < button onClick = { onRssAddClick } > ADD</ button >
299+ ) }
300+ </ div >
301+ { rssInputError && (
302+ < div className = "settingHint" >
303+ < p > { rssInputError } </ p >
304+ </ div >
305+ ) }
306+ </ div >
307+ </ div >
308+
207309 < div className = "settingRow" >
208310 < p className = "settingTitle" > Dark Mode</ p >
209311 < div className = "settingContent" >
0 commit comments